2 * empathy-call-window.c - Source for EmpathyCallWindow
3 * Copyright (C) 2008-2009 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 <telepathy-farsight/channel.h>
34 #include <gst/farsight/fs-element-added-notifier.h>
36 #include <libempathy/empathy-tp-contact-factory.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-utils.h>
39 #include <libempathy-gtk/empathy-avatar-image.h>
40 #include <libempathy-gtk/empathy-video-widget.h>
41 #include <libempathy-gtk/empathy-audio-src.h>
42 #include <libempathy-gtk/empathy-audio-sink.h>
43 #include <libempathy-gtk/empathy-video-src.h>
44 #include <libempathy-gtk/empathy-ui-utils.h>
45 #include <libempathy-gtk/empathy-sound.h>
46 #include <libempathy-gtk/empathy-geometry.h>
48 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
49 #include <libempathy/empathy-debug.h>
51 #include "empathy-call-window.h"
52 #include "empathy-call-window-fullscreen.h"
53 #include "empathy-sidebar.h"
55 #define BUTTON_ID "empathy-call-dtmf-button-id"
57 #define CONTENT_HBOX_BORDER_WIDTH 6
58 #define CONTENT_HBOX_SPACING 3
59 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
61 #define SELF_VIDEO_SECTION_WIDTH 160
62 #define SELF_VIDEO_SECTION_HEIGTH 120
64 /* The avatar's default width and height are set to the same value because we
65 want a square icon. */
66 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
67 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
68 EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
70 /* If an video input error occurs, the error message will start with "v4l" */
71 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
73 /* The time interval in milliseconds between 2 outgoing rings */
74 #define MS_BETWEEN_RING 500
76 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
85 static guint signals[LAST_SIGNAL] = {0};
89 PROP_CALL_HANDLER = 1,
100 CAMERA_STATE_OFF = 0,
101 CAMERA_STATE_PREVIEW,
105 /* private structure */
106 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
108 struct _EmpathyCallWindowPriv
110 gboolean dispose_has_run;
111 EmpathyCallHandler *handler;
112 EmpathyContact *contact;
117 GtkUIManager *ui_manager;
118 GtkWidget *errors_vbox;
119 GtkWidget *video_output;
120 GtkWidget *video_preview;
121 GtkWidget *remote_user_avatar_widget;
122 GtkWidget *self_user_avatar_widget;
124 GtkWidget *sidebar_button;
125 GtkWidget *statusbar;
126 GtkWidget *volume_button;
127 GtkWidget *redial_button;
128 GtkWidget *mic_button;
132 GtkAction *menu_fullscreen;
133 GtkAction *action_camera;
134 GtkAction *action_camera_preview;
135 GtkWidget *tool_button_camera_off;
136 GtkWidget *tool_button_camera_preview;
137 GtkWidget *tool_button_camera_on;
139 /* The frames and boxes that contain self and remote avatar and video
140 input/output. When we redial, we destroy and re-create the boxes */
141 GtkWidget *remote_user_output_frame;
142 GtkWidget *self_user_output_frame;
143 GtkWidget *remote_user_output_hbox;
144 GtkWidget *self_user_output_hbox;
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 /* This vbox is contained in the content_hbox and it contains the
151 self_user_output_frame and the sidebar button. When toggling fullscreen,
152 it needs to be repacked. We keep a reference on it for easier access. */
155 gulong video_output_motion_handler_id;
156 guint bus_message_source_id;
159 GtkWidget *volume_scale;
160 GtkWidget *volume_progress_bar;
161 GtkAdjustment *audio_input_adj;
163 GtkWidget *dtmf_panel;
165 GstElement *video_input;
166 GstElement *audio_input;
167 GstElement *audio_output;
168 GstElement *pipeline;
169 GstElement *video_tee;
172 GstElement *liveadder;
174 FsElementAddedNotifier *fsnotifier;
181 GtkWidget *video_contrast;
182 GtkWidget *video_brightness;
183 GtkWidget *video_gamma;
186 gboolean call_started;
187 gboolean sending_video;
188 CameraState camera_state;
190 EmpathyCallWindowFullscreen *fullscreen;
191 gboolean is_fullscreen;
193 /* Those fields represent the state of the window before it actually was in
195 gboolean sidebar_was_visible_before_fs;
196 gint original_width_before_fs;
197 gint original_height_before_fs;
200 #define GET_PRIV(o) \
201 (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW, \
202 EmpathyCallWindowPriv))
204 static void empathy_call_window_realized_cb (GtkWidget *widget,
205 EmpathyCallWindow *window);
207 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
208 GdkEvent *event, EmpathyCallWindow *window);
210 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
211 GdkEventWindowState *event, EmpathyCallWindow *window);
213 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
214 EmpathyCallWindow *window);
216 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
219 static void empathy_call_window_mic_toggled_cb (
220 GtkToggleToolButton *toggle, EmpathyCallWindow *window);
222 static void empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
223 EmpathyCallWindow *window);
225 static void empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
226 EmpathyCallWindow *window);
228 static void empathy_call_window_hangup_cb (gpointer object,
229 EmpathyCallWindow *window);
231 static void empathy_call_window_fullscreen_cb (gpointer object,
232 EmpathyCallWindow *window);
234 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
236 static gboolean empathy_call_window_video_button_press_cb (
237 GtkWidget *video_output, GdkEventButton *event, EmpathyCallWindow *window);
239 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
240 GdkEventKey *event, EmpathyCallWindow *window);
242 static gboolean empathy_call_window_video_output_motion_notify (
243 GtkWidget *widget, GdkEventMotion *event, EmpathyCallWindow *window);
245 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
248 static void empathy_call_window_redial_cb (gpointer object,
249 EmpathyCallWindow *window);
251 static void empathy_call_window_restart_call (EmpathyCallWindow *window);
253 static void empathy_call_window_status_message (EmpathyCallWindow *window,
256 static void empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
257 EmpathyCallWindow *window);
259 static gboolean empathy_call_window_bus_message (GstBus *bus,
260 GstMessage *message, gpointer user_data);
263 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
264 gdouble value, EmpathyCallWindow *window);
266 static void block_camera_control_signals (EmpathyCallWindow *self);
267 static void unblock_camera_control_signals (EmpathyCallWindow *self);
270 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
272 EmpathyCallWindowPriv *priv = GET_PRIV (self);
273 GtkToolItem *tool_item;
274 GtkWidget *camera_off_icon;
275 GdkPixbuf *pixbuf, *modded_pixbuf;
277 /* set the icon of the 'camera off' button by greying off the webcam icon */
278 pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
279 GTK_ICON_SIZE_SMALL_TOOLBAR);
281 modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
282 gdk_pixbuf_get_width (pixbuf),
283 gdk_pixbuf_get_height (pixbuf));
285 gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
286 g_object_unref (pixbuf);
288 camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
289 g_object_unref (modded_pixbuf);
290 gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
291 priv->tool_button_camera_off), camera_off_icon);
293 /* Add an empty expanded GtkToolItem so the volume button is at the end of
295 tool_item = gtk_tool_item_new ();
296 gtk_tool_item_set_expand (tool_item, TRUE);
297 gtk_widget_show (GTK_WIDGET (tool_item));
298 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
300 priv->volume_button = gtk_volume_button_new ();
301 /* FIXME listen to the audiosinks signals and update the button according to
302 * that, for now starting out at 1.0 and assuming only the app changes the
304 gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
305 g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
306 G_CALLBACK (empathy_call_window_volume_changed_cb), self);
308 tool_item = gtk_tool_item_new ();
309 gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
310 gtk_widget_show_all (GTK_WIDGET (tool_item));
311 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
315 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
317 EmpathyCallWindowPriv *priv = GET_PRIV (window);
322 g_object_get (priv->handler, "tp-call", &call, NULL);
324 button_quark = g_quark_from_static_string (BUTTON_ID);
325 event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
328 empathy_tp_call_start_tone (call, event);
330 g_object_unref (call);
334 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
336 EmpathyCallWindowPriv *priv = GET_PRIV (window);
339 g_object_get (priv->handler, "tp-call", &call, NULL);
341 empathy_tp_call_stop_tone (call);
343 g_object_unref (call);
347 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
355 } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
356 { "2", TP_DTMF_EVENT_DIGIT_2 },
357 { "3", TP_DTMF_EVENT_DIGIT_3 },
358 { "4", TP_DTMF_EVENT_DIGIT_4 },
359 { "5", TP_DTMF_EVENT_DIGIT_5 },
360 { "6", TP_DTMF_EVENT_DIGIT_6 },
361 { "7", TP_DTMF_EVENT_DIGIT_7 },
362 { "8", TP_DTMF_EVENT_DIGIT_8 },
363 { "9", TP_DTMF_EVENT_DIGIT_9 },
364 { "#", TP_DTMF_EVENT_HASH },
365 { "0", TP_DTMF_EVENT_DIGIT_0 },
366 { "*", TP_DTMF_EVENT_ASTERISK },
369 button_quark = g_quark_from_static_string (BUTTON_ID);
371 table = gtk_table_new (4, 3, TRUE);
373 for (i = 0; dtmfbuttons[i].label != NULL; i++)
375 GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
376 gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
377 i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
379 g_object_set_qdata (G_OBJECT (button), button_quark,
380 GUINT_TO_POINTER (dtmfbuttons[i].event));
382 g_signal_connect (G_OBJECT (button), "pressed",
383 G_CALLBACK (dtmf_button_pressed_cb), self);
384 g_signal_connect (G_OBJECT (button), "released",
385 G_CALLBACK (dtmf_button_released_cb), self);
392 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
393 gchar *label_text, GtkWidget *bin)
395 GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
396 GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
397 GtkWidget *label = gtk_label_new (label_text);
399 gtk_widget_set_sensitive (scale, FALSE);
401 gtk_container_add (GTK_CONTAINER (bin), vbox);
403 gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
404 gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
405 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
411 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
412 EmpathyCallWindow *self)
415 EmpathyCallWindowPriv *priv = GET_PRIV (self);
417 empathy_video_src_set_channel (priv->video_input,
418 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
422 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
423 EmpathyCallWindow *self)
426 EmpathyCallWindowPriv *priv = GET_PRIV (self);
428 empathy_video_src_set_channel (priv->video_input,
429 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
433 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
434 EmpathyCallWindow *self)
437 EmpathyCallWindowPriv *priv = GET_PRIV (self);
439 empathy_video_src_set_channel (priv->video_input,
440 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
445 empathy_call_window_create_video_input (EmpathyCallWindow *self)
447 EmpathyCallWindowPriv *priv = GET_PRIV (self);
450 hbox = gtk_hbox_new (TRUE, 3);
452 priv->video_contrast = empathy_call_window_create_video_input_add_slider (
453 self, _("Contrast"), hbox);
455 priv->video_brightness = empathy_call_window_create_video_input_add_slider (
456 self, _("Brightness"), hbox);
458 priv->video_gamma = empathy_call_window_create_video_input_add_slider (
459 self, _("Gamma"), hbox);
465 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
467 EmpathyCallWindowPriv *priv = GET_PRIV (self);
471 supported = empathy_video_src_get_supported_channels (priv->video_input);
473 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
475 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
477 gtk_adjustment_set_value (adj,
478 empathy_video_src_get_channel (priv->video_input,
479 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
481 g_signal_connect (G_OBJECT (adj), "value-changed",
482 G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
484 gtk_widget_set_sensitive (priv->video_contrast, TRUE);
487 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
489 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
491 gtk_adjustment_set_value (adj,
492 empathy_video_src_get_channel (priv->video_input,
493 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
495 g_signal_connect (G_OBJECT (adj), "value-changed",
496 G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
497 gtk_widget_set_sensitive (priv->video_brightness, TRUE);
500 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
502 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
504 gtk_adjustment_set_value (adj,
505 empathy_video_src_get_channel (priv->video_input,
506 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
508 g_signal_connect (G_OBJECT (adj), "value-changed",
509 G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
510 gtk_widget_set_sensitive (priv->video_gamma, TRUE);
515 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
516 EmpathyCallWindow *self)
518 EmpathyCallWindowPriv *priv = GET_PRIV (self);
521 if (priv->audio_input == NULL)
524 volume = gtk_adjustment_get_value (adj)/100.0;
526 /* Don't store the volume because of muting */
527 if (volume > 0 || gtk_toggle_tool_button_get_active (
528 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
529 priv->volume = volume;
531 /* Ensure that the toggle button is active if the volume is > 0 and inactive
532 * if it's smaller than 0 */
533 if ((volume > 0) != gtk_toggle_tool_button_get_active (
534 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
535 gtk_toggle_tool_button_set_active (
536 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
538 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
543 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
544 gdouble level, EmpathyCallWindow *window)
547 EmpathyCallWindowPriv *priv = GET_PRIV (window);
549 value = CLAMP (pow (10, level / 20), 0.0, 1.0);
550 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
555 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
557 EmpathyCallWindowPriv *priv = GET_PRIV (self);
558 GtkWidget *hbox, *vbox, *label;
560 hbox = gtk_hbox_new (TRUE, 3);
562 vbox = gtk_vbox_new (FALSE, 3);
563 gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
565 priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
566 gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
567 label = gtk_label_new (_("Volume"));
569 priv->audio_input_adj = gtk_range_get_adjustment (
570 GTK_RANGE (priv->volume_scale));
571 priv->volume = empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
572 (priv->audio_input));
573 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
575 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
576 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
578 gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
579 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
581 priv->volume_progress_bar = gtk_progress_bar_new ();
582 gtk_progress_bar_set_orientation (
583 GTK_PROGRESS_BAR (priv->volume_progress_bar),
584 GTK_PROGRESS_BOTTOM_TO_TOP);
585 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
588 gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
595 empathy_call_window_setup_remote_frame (GstBus *bus, EmpathyCallWindow *self)
597 EmpathyCallWindowPriv *priv = GET_PRIV (self);
599 /* Initializing all the content (UI and output gst elements) related to the
601 priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
603 priv->remote_user_avatar_widget = gtk_image_new ();
604 gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
605 priv->remote_user_avatar_widget, TRUE, TRUE, 0);
607 priv->video_output = empathy_video_widget_new (bus);
608 gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
609 priv->video_output, TRUE, TRUE, 0);
611 gtk_widget_add_events (priv->video_output,
612 GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
613 g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
614 G_CALLBACK (empathy_call_window_video_button_press_cb), self);
616 gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
617 priv->remote_user_output_hbox);
619 priv->audio_output = empathy_audio_sink_new ();
620 gst_object_ref (priv->audio_output);
621 gst_object_sink (priv->audio_output);
625 empathy_call_window_setup_self_frame (GstBus *bus, EmpathyCallWindow *self)
627 EmpathyCallWindowPriv *priv = GET_PRIV (self);
629 /* Initializing all the content (UI and input gst elements) related to the
630 self contact, except for the video preview widget. This widget is only
631 initialized when the "show video preview" option is activated */
632 priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
634 priv->self_user_avatar_widget = gtk_image_new ();
635 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
636 priv->self_user_avatar_widget, TRUE, TRUE, 0);
638 gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
639 priv->self_user_output_hbox);
641 priv->video_input = empathy_video_src_new ();
642 gst_object_ref (priv->video_input);
643 gst_object_sink (priv->video_input);
645 priv->audio_input = empathy_audio_src_new ();
646 gst_object_ref (priv->audio_input);
647 gst_object_sink (priv->audio_input);
649 empathy_signal_connect_weak (priv->audio_input, "peak-level-changed",
650 G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
655 empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
657 EmpathyCallWindowPriv *priv = GET_PRIV (window);
659 GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
661 if (priv->video_preview != NULL)
663 /* Since the video preview and the video tee are initialized and freed
664 at the same time, if one is initialized, then the other one should
666 g_assert (priv->video_tee != NULL);
670 DEBUG ("Create video preview");
671 g_assert (priv->video_tee == NULL);
673 priv->video_tee = gst_element_factory_make ("tee", NULL);
674 gst_object_ref (priv->video_tee);
675 gst_object_sink (priv->video_tee);
677 priv->video_preview = empathy_video_widget_new_with_size (bus,
678 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
679 g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
680 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
681 priv->video_preview, TRUE, TRUE, 0);
683 preview = empathy_video_widget_get_element (
684 EMPATHY_VIDEO_WIDGET (priv->video_preview));
685 gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
686 priv->video_tee, preview, NULL);
687 gst_element_link_many (priv->video_input, priv->video_tee,
690 g_object_unref (bus);
692 gst_element_set_state (preview, GST_STATE_PLAYING);
693 gst_element_set_state (priv->video_input, GST_STATE_PLAYING);
694 gst_element_set_state (priv->video_tee, GST_STATE_PLAYING);
698 display_video_preview (EmpathyCallWindow *self,
701 EmpathyCallWindowPriv *priv = GET_PRIV (self);
705 /* Display the preview and hide the self avatar */
706 DEBUG ("Show video preview");
708 if (priv->video_preview == NULL)
709 empathy_call_window_setup_video_preview (self);
710 gtk_widget_show (priv->video_preview);
711 gtk_widget_hide (priv->self_user_avatar_widget);
715 /* Display the self avatar and hide the preview */
716 DEBUG ("Show self avatar");
718 if (priv->video_preview != NULL)
719 gtk_widget_hide (priv->video_preview);
720 gtk_widget_show (priv->self_user_avatar_widget);
725 empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
727 EmpathyCallWindowPriv *priv = GET_PRIV (window);
729 empathy_call_window_status_message (window, _("Connecting…"));
730 priv->call_state = CONNECTING;
733 empathy_sound_start_playing (GTK_WIDGET (window),
734 EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
738 disable_camera (EmpathyCallWindow *self)
740 EmpathyCallWindowPriv *priv = GET_PRIV (self);
742 if (priv->camera_state == CAMERA_STATE_OFF)
745 DEBUG ("Disable camera");
747 display_video_preview (self, FALSE);
749 if (priv->camera_state == CAMERA_STATE_ON)
750 empathy_call_window_set_send_video (self, FALSE);
752 block_camera_control_signals (self);
753 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
754 priv->tool_button_camera_on), FALSE);
755 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
756 priv->tool_button_camera_preview), FALSE);
758 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
759 priv->tool_button_camera_off), TRUE);
760 gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
762 unblock_camera_control_signals (self);
764 priv->camera_state = CAMERA_STATE_OFF;
768 tool_button_camera_off_toggled_cb (GtkToggleToolButton *toggle,
769 EmpathyCallWindow *self)
771 EmpathyCallWindowPriv *priv = GET_PRIV (self);
773 if (!gtk_toggle_tool_button_get_active (toggle))
775 if (priv->camera_state == CAMERA_STATE_OFF)
777 /* We can't change the state by disabling the button */
778 block_camera_control_signals (self);
779 gtk_toggle_tool_button_set_active (toggle, TRUE);
780 unblock_camera_control_signals (self);
786 disable_camera (self);
790 enable_preview (EmpathyCallWindow *self)
792 EmpathyCallWindowPriv *priv = GET_PRIV (self);
794 if (priv->camera_state == CAMERA_STATE_PREVIEW)
797 DEBUG ("Enable preview");
799 if (priv->camera_state == CAMERA_STATE_ON)
800 /* preview is already displayed so we just have to stop sending */
801 empathy_call_window_set_send_video (self, FALSE);
803 display_video_preview (self, TRUE);
805 block_camera_control_signals (self);
806 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
807 priv->tool_button_camera_off), FALSE);
808 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
809 priv->tool_button_camera_on), FALSE);
811 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
812 priv->tool_button_camera_preview), TRUE);
813 gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
814 CAMERA_STATE_PREVIEW);
815 unblock_camera_control_signals (self);
817 priv->camera_state = CAMERA_STATE_PREVIEW;
821 tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
822 EmpathyCallWindow *self)
824 EmpathyCallWindowPriv *priv = GET_PRIV (self);
826 if (!gtk_toggle_tool_button_get_active (toggle))
828 if (priv->camera_state == CAMERA_STATE_PREVIEW)
830 /* We can't change the state by disabling the button */
831 block_camera_control_signals (self);
832 gtk_toggle_tool_button_set_active (toggle, TRUE);
833 unblock_camera_control_signals (self);
839 enable_preview (self);
843 enable_camera (EmpathyCallWindow *self)
845 EmpathyCallWindowPriv *priv = GET_PRIV (self);
847 if (priv->camera_state == CAMERA_STATE_ON)
850 DEBUG ("Enable camera");
852 empathy_call_window_set_send_video (self, TRUE);
854 block_camera_control_signals (self);
855 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
856 priv->tool_button_camera_off), FALSE);
857 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
858 priv->tool_button_camera_preview), FALSE);
860 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
861 priv->tool_button_camera_on), TRUE);
862 gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
864 unblock_camera_control_signals (self);
866 priv->camera_state = CAMERA_STATE_ON;
870 tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
871 EmpathyCallWindow *self)
873 EmpathyCallWindowPriv *priv = GET_PRIV (self);
875 if (!gtk_toggle_tool_button_get_active (toggle))
877 if (priv->camera_state == CAMERA_STATE_ON)
879 /* We can't change the state by disabling the button */
880 block_camera_control_signals (self);
881 gtk_toggle_tool_button_set_active (toggle, TRUE);
882 unblock_camera_control_signals (self);
888 enable_camera (self);
892 action_camera_change_cb (GtkRadioAction *action,
893 GtkRadioAction *current,
894 EmpathyCallWindow *self)
898 state = gtk_radio_action_get_current_value (current);
902 case CAMERA_STATE_OFF:
903 disable_camera (self);
906 case CAMERA_STATE_PREVIEW:
907 enable_preview (self);
910 case CAMERA_STATE_ON:
911 enable_camera (self);
915 g_assert_not_reached ();
920 empathy_call_window_init (EmpathyCallWindow *self)
922 EmpathyCallWindowPriv *priv = GET_PRIV (self);
931 GError *error = NULL;
933 filename = empathy_file_lookup ("empathy-call-window.ui", "src");
934 gui = empathy_builder_get_file (filename,
935 "call_window_vbox", &top_vbox,
936 "errors_vbox", &priv->errors_vbox,
938 "statusbar", &priv->statusbar,
939 "redial", &priv->redial_button,
940 "microphone", &priv->mic_button,
941 "toolbar", &priv->toolbar,
942 "menuredial", &priv->redial,
943 "ui_manager", &priv->ui_manager,
944 "menufullscreen", &priv->menu_fullscreen,
945 "camera_off", &priv->tool_button_camera_off,
946 "camera_preview", &priv->tool_button_camera_preview,
947 "camera_on", &priv->tool_button_camera_on,
948 "action_camera_off", &priv->action_camera,
949 "action_camera_preview", &priv->action_camera_preview,
953 empathy_builder_connect (gui, self,
954 "menuhangup", "activate", empathy_call_window_hangup_cb,
955 "hangup", "clicked", empathy_call_window_hangup_cb,
956 "menuredial", "activate", empathy_call_window_redial_cb,
957 "redial", "clicked", empathy_call_window_redial_cb,
958 "microphone", "toggled", empathy_call_window_mic_toggled_cb,
959 "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
960 "camera_off", "toggled", tool_button_camera_off_toggled_cb,
961 "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
962 "camera_on", "toggled", tool_button_camera_on_toggled_cb,
963 "action_camera_off", "changed", action_camera_change_cb,
966 priv->lock = g_mutex_new ();
968 gtk_container_add (GTK_CONTAINER (self), top_vbox);
970 priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
971 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
972 CONTENT_HBOX_BORDER_WIDTH);
973 gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
975 priv->pipeline = gst_pipeline_new (NULL);
976 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
977 priv->bus_message_source_id = gst_bus_add_watch (bus,
978 empathy_call_window_bus_message, self);
980 priv->fsnotifier = fs_element_added_notifier_new ();
981 fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
983 keyfile = g_key_file_new ();
984 filename = empathy_file_lookup ("element-properties", "data");
985 if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
987 fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
992 g_warning ("Could not load element-properties file: %s", error->message);
993 g_key_file_free (keyfile);
994 g_clear_error (&error);
999 priv->remote_user_output_frame = gtk_frame_new (NULL);
1000 gtk_widget_set_size_request (priv->remote_user_output_frame,
1001 EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
1002 gtk_box_pack_start (GTK_BOX (priv->content_hbox),
1003 priv->remote_user_output_frame, TRUE, TRUE,
1004 CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1005 empathy_call_window_setup_remote_frame (bus, self);
1007 priv->self_user_output_frame = gtk_frame_new (NULL);
1008 gtk_widget_set_size_request (priv->self_user_output_frame,
1009 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
1011 priv->vbox = gtk_vbox_new (FALSE, 3);
1012 gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
1013 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1014 gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
1016 empathy_call_window_setup_self_frame (bus, self);
1018 empathy_call_window_setup_toolbar (self);
1020 g_object_unref (bus);
1022 priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
1023 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1024 g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
1025 G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
1027 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1029 h = gtk_hbox_new (FALSE, 3);
1030 gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
1031 gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
1033 priv->sidebar = empathy_sidebar_new ();
1034 g_signal_connect (G_OBJECT (priv->sidebar),
1035 "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
1036 g_signal_connect (G_OBJECT (priv->sidebar),
1037 "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
1038 gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
1040 page = empathy_call_window_create_audio_input (self);
1041 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
1044 page = empathy_call_window_create_video_input (self);
1045 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
1048 priv->dtmf_panel = empathy_call_window_create_dtmf (self);
1049 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
1052 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1055 gtk_widget_show_all (top_vbox);
1057 gtk_widget_hide (priv->sidebar);
1059 priv->fullscreen = empathy_call_window_fullscreen_new (self);
1060 empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1061 priv->video_output);
1062 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1063 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1065 g_signal_connect (G_OBJECT (self), "realize",
1066 G_CALLBACK (empathy_call_window_realized_cb), self);
1068 g_signal_connect (G_OBJECT (self), "delete-event",
1069 G_CALLBACK (empathy_call_window_delete_cb), self);
1071 g_signal_connect (G_OBJECT (self), "window-state-event",
1072 G_CALLBACK (empathy_call_window_state_event_cb), self);
1074 g_signal_connect (G_OBJECT (self), "key-press-event",
1075 G_CALLBACK (empathy_call_window_key_press_cb), self);
1077 priv->timer = g_timer_new ();
1079 g_object_ref (priv->ui_manager);
1080 g_object_unref (gui);
1082 empathy_geometry_bind (GTK_WINDOW (self), "call-window");
1085 /* Instead of specifying a width and a height, we specify only one size. That's
1086 because we want a square avatar icon. */
1088 init_contact_avatar_with_size (EmpathyContact *contact,
1089 GtkWidget *image_widget,
1092 GdkPixbuf *pixbuf_avatar = NULL;
1094 if (contact != NULL)
1096 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1100 if (pixbuf_avatar == NULL)
1102 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
1106 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1110 set_window_title (EmpathyCallWindow *self)
1112 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1115 /* translators: Call is a noun and %s is the contact name. This string
1116 * is used in the window title */
1117 tmp = g_strdup_printf (_("Call with %s"),
1118 empathy_contact_get_name (priv->contact));
1119 gtk_window_set_title (GTK_WINDOW (self), tmp);
1124 contact_name_changed_cb (EmpathyContact *contact,
1125 GParamSpec *pspec, EmpathyCallWindow *self)
1127 set_window_title (self);
1131 contact_avatar_changed_cb (EmpathyContact *contact,
1132 GParamSpec *pspec, GtkWidget *avatar_widget)
1136 size = avatar_widget->allocation.height;
1140 /* the widget is not allocated yet, set a default size */
1141 size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1142 REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1145 init_contact_avatar_with_size (contact, avatar_widget, size);
1149 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
1150 EmpathyContact *contact, const GError *error, gpointer user_data,
1151 GObject *weak_object)
1153 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1154 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1156 init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1157 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1159 g_signal_connect (contact, "notify::avatar",
1160 G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1164 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1165 EmpathyCallHandler *handler)
1167 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1169 g_object_get (handler, "contact", &(priv->contact), NULL);
1171 if (priv->contact != NULL)
1173 TpConnection *connection;
1174 EmpathyTpContactFactory *factory;
1176 set_window_title (self);
1178 g_signal_connect (priv->contact, "notify::name",
1179 G_CALLBACK (contact_name_changed_cb), self);
1180 g_signal_connect (priv->contact, "notify::avatar",
1181 G_CALLBACK (contact_avatar_changed_cb),
1182 priv->remote_user_avatar_widget);
1184 /* Retreiving the self avatar */
1185 connection = empathy_contact_get_connection (priv->contact);
1186 factory = empathy_tp_contact_factory_dup_singleton (connection);
1187 empathy_tp_contact_factory_get_from_handle (factory,
1188 tp_connection_get_self_handle (connection),
1189 empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1191 g_object_unref (factory);
1195 g_warning ("call handler doesn't have a contact");
1196 /* translators: Call is a noun. This string is used in the window
1198 gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1200 /* Since we can't access the remote contact, we can't get a connection
1201 to it and can't get the self contact (and its avatar). This means
1202 that we have to manually set the self avatar. */
1203 init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1204 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1207 init_contact_avatar_with_size (priv->contact,
1208 priv->remote_user_avatar_widget,
1209 MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1210 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1212 /* The remote avatar is shown by default and will be hidden when we receive
1213 video from the remote side. */
1214 gtk_widget_hide (priv->video_output);
1215 gtk_widget_show (priv->remote_user_avatar_widget);
1219 empathy_call_window_constructed (GObject *object)
1221 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1222 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1223 EmpathyTpCall *call;
1225 g_assert (priv->handler != NULL);
1227 g_object_get (priv->handler, "tp-call", &call, NULL);
1228 priv->outgoing = (call == NULL);
1230 g_object_unref (call);
1232 empathy_call_window_setup_avatars (self, priv->handler);
1233 empathy_call_window_set_state_connecting (self);
1235 if (!empathy_call_handler_has_initial_video (priv->handler))
1237 gtk_toggle_tool_button_set_active (
1238 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1240 /* If call has InitialVideo, the preview will be started once the call has
1241 * been started (start_call()). */
1244 static void empathy_call_window_dispose (GObject *object);
1245 static void empathy_call_window_finalize (GObject *object);
1248 empathy_call_window_set_property (GObject *object,
1249 guint property_id, const GValue *value, GParamSpec *pspec)
1251 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1253 switch (property_id)
1255 case PROP_CALL_HANDLER:
1256 priv->handler = g_value_dup_object (value);
1259 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1264 empathy_call_window_get_property (GObject *object,
1265 guint property_id, GValue *value, GParamSpec *pspec)
1267 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1269 switch (property_id)
1271 case PROP_CALL_HANDLER:
1272 g_value_set_object (value, priv->handler);
1275 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1280 empathy_call_window_class_init (
1281 EmpathyCallWindowClass *empathy_call_window_class)
1283 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1284 GParamSpec *param_spec;
1286 g_type_class_add_private (empathy_call_window_class,
1287 sizeof (EmpathyCallWindowPriv));
1289 object_class->constructed = empathy_call_window_constructed;
1290 object_class->set_property = empathy_call_window_set_property;
1291 object_class->get_property = empathy_call_window_get_property;
1293 object_class->dispose = empathy_call_window_dispose;
1294 object_class->finalize = empathy_call_window_finalize;
1296 param_spec = g_param_spec_object ("handler",
1297 "handler", "The call handler",
1298 EMPATHY_TYPE_CALL_HANDLER,
1299 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1300 g_object_class_install_property (object_class,
1301 PROP_CALL_HANDLER, param_spec);
1305 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1306 GParamSpec *property, EmpathyCallWindow *self)
1308 DEBUG ("video stream changed");
1309 empathy_call_window_update_avatars_visibility (call, self);
1313 empathy_call_window_dispose (GObject *object)
1315 EmpathyTpCall *call;
1316 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1317 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1319 if (priv->dispose_has_run)
1322 priv->dispose_has_run = TRUE;
1324 g_object_get (priv->handler, "tp-call", &call, NULL);
1328 g_signal_handlers_disconnect_by_func (call,
1329 empathy_call_window_video_stream_changed_cb, object);
1330 g_object_unref (call);
1333 if (priv->handler != NULL)
1335 empathy_call_handler_stop_call (priv->handler);
1336 g_object_unref (priv->handler);
1338 priv->handler = NULL;
1340 if (priv->pipeline != NULL)
1341 g_object_unref (priv->pipeline);
1342 priv->pipeline = NULL;
1344 if (priv->video_input != NULL)
1345 g_object_unref (priv->video_input);
1346 priv->video_input = NULL;
1348 if (priv->audio_input != NULL)
1349 g_object_unref (priv->audio_input);
1350 priv->audio_input = NULL;
1352 if (priv->audio_output != NULL)
1353 g_object_unref (priv->audio_output);
1354 priv->audio_output = NULL;
1356 if (priv->video_tee != NULL)
1357 g_object_unref (priv->video_tee);
1358 priv->video_tee = NULL;
1360 if (priv->fsnotifier != NULL)
1361 g_object_unref (priv->fsnotifier);
1362 priv->fsnotifier = NULL;
1364 if (priv->timer_id != 0)
1365 g_source_remove (priv->timer_id);
1368 if (priv->ui_manager != NULL)
1369 g_object_unref (priv->ui_manager);
1370 priv->ui_manager = NULL;
1372 if (priv->contact != NULL)
1374 g_signal_handlers_disconnect_by_func (priv->contact,
1375 contact_name_changed_cb, self);
1376 g_object_unref (priv->contact);
1377 priv->contact = NULL;
1380 /* release any references held by the object here */
1381 if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1382 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1386 empathy_call_window_finalize (GObject *object)
1388 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1389 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1391 if (priv->video_output_motion_handler_id != 0)
1393 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1394 priv->video_output_motion_handler_id);
1395 priv->video_output_motion_handler_id = 0;
1398 if (priv->bus_message_source_id != 0)
1400 g_source_remove (priv->bus_message_source_id);
1401 priv->bus_message_source_id = 0;
1404 /* free any data held directly by the object here */
1405 g_mutex_free (priv->lock);
1407 g_timer_destroy (priv->timer);
1409 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1414 empathy_call_window_new (EmpathyCallHandler *handler)
1416 return EMPATHY_CALL_WINDOW (
1417 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1421 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1422 GstElement *conference, gpointer user_data)
1424 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1425 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1427 gst_bin_add (GST_BIN (priv->pipeline), conference);
1429 gst_element_set_state (conference, GST_STATE_PLAYING);
1433 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1434 FsMediaType type, FsStreamDirection direction, gpointer user_data)
1436 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1437 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1439 if (type != FS_MEDIA_TYPE_VIDEO)
1442 if (direction == FS_DIRECTION_RECV)
1445 /* video and direction is send */
1446 return priv->video_input != NULL;
1450 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1452 GstStateChangeReturn state_change_return;
1453 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1455 if (priv->pipeline == NULL)
1458 if (priv->bus_message_source_id != 0)
1460 g_source_remove (priv->bus_message_source_id);
1461 priv->bus_message_source_id = 0;
1464 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1466 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1467 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1469 if (priv->pipeline != NULL)
1470 g_object_unref (priv->pipeline);
1471 priv->pipeline = NULL;
1473 if (priv->video_input != NULL)
1474 g_object_unref (priv->video_input);
1475 priv->video_input = NULL;
1477 if (priv->audio_input != NULL)
1478 g_object_unref (priv->audio_input);
1479 priv->audio_input = NULL;
1481 g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1482 empathy_call_window_mic_volume_changed_cb, self);
1484 if (priv->audio_output != NULL)
1485 g_object_unref (priv->audio_output);
1486 priv->audio_output = NULL;
1488 if (priv->video_tee != NULL)
1489 g_object_unref (priv->video_tee);
1490 priv->video_tee = NULL;
1492 if (priv->video_preview != NULL)
1493 gtk_widget_destroy (priv->video_preview);
1494 priv->video_preview = NULL;
1496 priv->liveadder = NULL;
1497 priv->funnel = NULL;
1503 g_message ("Error: could not destroy pipeline. Closing call window");
1504 gtk_widget_destroy (GTK_WIDGET (self));
1511 empathy_call_window_disconnected (EmpathyCallWindow *self)
1513 gboolean could_disconnect = FALSE;
1514 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1515 gboolean could_reset_pipeline;
1517 could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1519 if (priv->call_state == CONNECTING)
1520 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1522 if (priv->call_state != REDIALING)
1523 priv->call_state = DISCONNECTED;
1525 if (could_reset_pipeline)
1527 g_mutex_lock (priv->lock);
1529 g_timer_stop (priv->timer);
1531 if (priv->timer_id != 0)
1532 g_source_remove (priv->timer_id);
1535 g_mutex_unlock (priv->lock);
1537 empathy_call_window_status_message (self, _("Disconnected"));
1539 gtk_action_set_sensitive (priv->redial, TRUE);
1540 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1542 /* Reseting the send_video, camera_buton and mic_button to their
1544 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1545 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1546 gtk_toggle_tool_button_set_active (
1547 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1548 gtk_toggle_tool_button_set_active (
1549 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1551 /* FIXME: This is to workaround the fact that the pipeline has been
1552 * destroyed and so we can't display preview until a new call (and so a
1553 * new pipeline) is created. We should fix this properly by refactoring
1554 * the code managing the pipeline. This is bug #602937 */
1555 gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
1556 gtk_action_set_sensitive (priv->action_camera_preview, FALSE);
1558 gtk_progress_bar_set_fraction (
1559 GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1561 gtk_widget_hide (priv->video_output);
1562 gtk_widget_show (priv->remote_user_avatar_widget);
1564 priv->sending_video = FALSE;
1565 priv->call_started = FALSE;
1567 could_disconnect = TRUE;
1569 /* TODO: display the self avatar of the preview (depends if the "Always
1570 * Show Video Preview" is enabled or not) */
1573 return could_disconnect;
1578 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1581 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1582 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1584 if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1585 empathy_call_window_restart_call (self);
1590 empathy_call_window_channel_stream_closed_cb (EmpathyCallHandler *handler,
1591 TfStream *stream, gpointer user_data)
1593 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1594 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1597 g_object_get (stream, "media-type", &media_type, NULL);
1600 * This assumes that there is only one video stream per channel...
1603 if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1605 if (priv->funnel != NULL)
1609 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1610 (priv->video_output));
1612 gst_element_set_state (output, GST_STATE_NULL);
1613 gst_element_set_state (priv->funnel, GST_STATE_NULL);
1615 gst_bin_remove (GST_BIN (priv->pipeline), output);
1616 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1617 priv->funnel = NULL;
1620 else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1622 if (priv->liveadder != NULL)
1624 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1625 gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1627 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1628 gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1629 priv->liveadder = NULL;
1634 /* Called with global lock held */
1636 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1638 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1642 if (priv->funnel == NULL)
1644 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1645 (priv->video_output));
1647 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1651 g_warning ("Could not create fsfunnel");
1655 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
1657 gst_object_unref (priv->funnel);
1658 priv->funnel = NULL;
1659 g_warning ("Could not add funnel to pipeline");
1663 if (!gst_bin_add (GST_BIN (priv->pipeline), output))
1665 g_warning ("Could not add the video output widget to the pipeline");
1669 if (!gst_element_link (priv->funnel, output))
1671 g_warning ("Could not link output sink to funnel");
1672 goto error_output_added;
1675 if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1677 g_warning ("Could not start video sink");
1678 goto error_output_added;
1681 if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1683 g_warning ("Could not start funnel");
1684 goto error_output_added;
1688 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1691 g_warning ("Could not get request pad from funnel");
1698 gst_element_set_locked_state (priv->funnel, TRUE);
1699 gst_element_set_locked_state (output, TRUE);
1701 gst_element_set_state (priv->funnel, GST_STATE_NULL);
1702 gst_element_set_state (output, GST_STATE_NULL);
1704 gst_bin_remove (GST_BIN (priv->pipeline), output);
1705 gst_element_set_locked_state (output, FALSE);
1709 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1710 priv->funnel = NULL;
1715 /* Called with global lock held */
1717 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1719 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1722 GError *gerror = NULL;
1724 if (priv->liveadder == NULL)
1726 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1728 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder))
1730 g_warning ("Could not add liveadder to the pipeline");
1731 goto error_add_liveadder;
1733 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
1735 g_warning ("Could not add audio sink to pipeline");
1736 goto error_add_output;
1739 if (gst_element_set_state (priv->liveadder, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1741 g_warning ("Could not start liveadder");
1745 if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1747 g_warning ("Could not start audio sink");
1751 if (GST_PAD_LINK_FAILED (
1752 gst_element_link (priv->liveadder, priv->audio_output)))
1754 g_warning ("Could not link liveadder to audio output");
1759 filter = gst_parse_bin_from_description (
1760 "audioconvert ! audioresample ! audioconvert", TRUE, &gerror);
1763 g_warning ("Could not make audio conversion filter: %s", gerror->message);
1764 g_clear_error (&gerror);
1768 if (!gst_bin_add (GST_BIN (priv->pipeline), filter))
1770 g_warning ("Could not add audio conversion filter to pipeline");
1771 gst_object_unref (filter);
1775 if (gst_element_set_state (filter, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1777 g_warning ("Could not start audio conversion filter");
1781 if (!gst_element_link (filter, priv->liveadder))
1783 g_warning ("Could not link audio conversion filter to liveadder");
1787 pad = gst_element_get_static_pad (filter, "sink");
1791 g_warning ("Could not get sink pad from filter");
1799 gst_element_set_locked_state (filter, TRUE);
1800 gst_element_set_state (filter, GST_STATE_NULL);
1801 gst_bin_remove (GST_BIN (priv->pipeline), filter);
1805 gst_element_set_locked_state (priv->liveadder, TRUE);
1806 gst_element_set_locked_state (priv->audio_output, TRUE);
1808 gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1809 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1811 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1815 gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1817 gst_element_set_locked_state (priv->liveadder, FALSE);
1818 gst_element_set_locked_state (priv->audio_output, FALSE);
1820 error_add_liveadder:
1822 if (priv->liveadder != NULL)
1824 gst_object_unref (priv->liveadder);
1825 priv->liveadder = NULL;
1832 empathy_call_window_update_timer (gpointer user_data)
1834 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1835 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1839 time_ = g_timer_elapsed (priv->timer, NULL);
1841 /* Translators: number of minutes:seconds the caller has been connected */
1842 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time_ / 60,
1844 empathy_call_window_status_message (self, str);
1851 display_error (EmpathyCallWindow *self,
1852 EmpathyTpCall *call,
1856 const gchar *details)
1858 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1859 GtkWidget *info_bar;
1860 GtkWidget *content_area;
1867 /* Create info bar */
1868 info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1871 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1873 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1875 /* hbox containing the image and the messages vbox */
1876 hbox = gtk_hbox_new (FALSE, 3);
1877 gtk_container_add (GTK_CONTAINER (content_area), hbox);
1880 image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1881 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1883 /* vbox containing the main message and the details expander */
1884 vbox = gtk_vbox_new (FALSE, 3);
1885 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1888 txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
1890 label = gtk_label_new (NULL);
1891 gtk_label_set_markup (GTK_LABEL (label), txt);
1892 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1893 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1896 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1899 if (details != NULL)
1901 GtkWidget *expander;
1903 expander = gtk_expander_new (_("Technical Details"));
1905 txt = g_strdup_printf ("<i>%s</i>", details);
1907 label = gtk_label_new (NULL);
1908 gtk_label_set_markup (GTK_LABEL (label), txt);
1909 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1910 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1913 gtk_container_add (GTK_CONTAINER (expander), label);
1914 gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1917 g_signal_connect (info_bar, "response",
1918 G_CALLBACK (gtk_widget_destroy), NULL);
1920 gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1921 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1922 gtk_widget_show_all (info_bar);
1926 media_stream_error_to_txt (EmpathyCallWindow *self,
1927 EmpathyTpCall *call,
1929 TpMediaStreamError error)
1931 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1938 case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1940 return g_strdup_printf (
1941 _("%s's software does not understand any of the audio formats "
1942 "supported by your computer"),
1943 empathy_contact_get_name (priv->contact));
1945 return g_strdup_printf (
1946 _("%s's software does not understand any of the video formats "
1947 "supported by your computer"),
1948 empathy_contact_get_name (priv->contact));
1950 case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1951 return g_strdup_printf (
1952 _("Can't establish a connection to %s. "
1953 "One of you might be on a network that does not allow "
1954 "direct connections."),
1955 empathy_contact_get_name (priv->contact));
1957 case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
1958 return g_strdup (_("There was a failure on the network"));
1960 case TP_MEDIA_STREAM_ERROR_NO_CODECS:
1962 return g_strdup (_("The audio formats necessary for this call "
1963 "are not installed on your computer"));
1965 return g_strdup (_("The video formats necessary for this call "
1966 "are not installed on your computer"));
1968 case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
1969 cm = empathy_tp_call_get_connection_manager (call);
1971 url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
1972 "product=Telepathy&component=%s", cm);
1974 result = g_strdup_printf (
1975 _("Something unexpected happened in a Telepathy component. "
1976 "Please <a href=\"%s\">report this bug</a> and attach "
1977 "logs gathered from the 'Debug' window in the Help menu."), url);
1982 case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
1983 return g_strdup (_("There was a failure in the call engine"));
1991 empathy_call_window_stream_error (EmpathyCallWindow *self,
1992 EmpathyTpCall *call,
2001 desc = media_stream_error_to_txt (self, call, audio, code);
2004 /* No description, use the error message. That's not great as it's not
2005 * localized but it's better than nothing. */
2006 display_error (self, call, icon, title, msg, NULL);
2010 display_error (self, call, icon, title, desc, msg);
2016 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
2019 EmpathyCallWindow *self)
2021 empathy_call_window_stream_error (self, call, TRUE, code, msg,
2022 "gnome-stock-mic", _("Can't establish audio stream"));
2026 empathy_call_window_video_stream_error (EmpathyTpCall *call,
2029 EmpathyCallWindow *self)
2031 empathy_call_window_stream_error (self, call, FALSE, code, msg,
2032 "camera-web", _("Can't establish video stream"));
2036 empathy_call_window_connected (gpointer user_data)
2038 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2039 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2040 EmpathyTpCall *call;
2041 gboolean can_send_video;
2043 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2045 can_send_video = priv->video_input != NULL && priv->contact != NULL &&
2046 empathy_contact_can_voip_video (priv->contact);
2048 g_object_get (priv->handler, "tp-call", &call, NULL);
2050 g_signal_connect (call, "notify::video-stream",
2051 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
2053 if (empathy_tp_call_has_dtmf (call))
2054 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2056 if (priv->video_input == NULL)
2057 empathy_call_window_set_send_video (self, FALSE);
2059 priv->sending_video = can_send_video ?
2060 empathy_tp_call_is_sending_video (call) : FALSE;
2062 gtk_toggle_tool_button_set_active (
2063 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
2064 priv->sending_video && priv->video_input != NULL);
2065 gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
2067 gtk_action_set_sensitive (priv->redial, FALSE);
2068 gtk_widget_set_sensitive (priv->redial_button, FALSE);
2070 gtk_widget_set_sensitive (priv->mic_button, TRUE);
2072 /* FIXME: this should won't be needed once bug #602937 is fixed
2073 * (see empathy_call_window_disconnected for details) */
2074 gtk_widget_set_sensitive (priv->tool_button_camera_preview, TRUE);
2075 gtk_action_set_sensitive (priv->action_camera_preview, TRUE);
2077 empathy_call_window_update_avatars_visibility (call, self);
2079 g_object_unref (call);
2081 g_mutex_lock (priv->lock);
2083 priv->timer_id = g_timeout_add_seconds (1,
2084 empathy_call_window_update_timer, self);
2086 g_mutex_unlock (priv->lock);
2088 empathy_call_window_update_timer (self);
2094 /* Called from the streaming thread */
2096 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
2097 GstPad *src, guint media_type, gpointer user_data)
2099 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2100 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2101 gboolean retval = FALSE;
2105 g_mutex_lock (priv->lock);
2107 if (priv->call_state != CONNECTED)
2109 g_timer_start (priv->timer);
2110 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
2111 priv->call_state = CONNECTED;
2116 case TP_MEDIA_STREAM_TYPE_AUDIO:
2117 pad = empathy_call_window_get_audio_sink_pad (self);
2119 case TP_MEDIA_STREAM_TYPE_VIDEO:
2120 gtk_widget_hide (priv->remote_user_avatar_widget);
2121 gtk_widget_show (priv->video_output);
2122 pad = empathy_call_window_get_video_sink_pad (self);
2125 g_assert_not_reached ();
2131 if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2132 g_warning ("Could not link %s sink pad",
2133 media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2137 gst_object_unref (pad);
2141 /* If no sink could be linked, try to add fakesink to prevent the whole call
2146 GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2148 if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2150 GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2151 if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2152 GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2154 gst_element_set_locked_state (fakesink, TRUE);
2155 gst_element_set_state (fakesink, GST_STATE_NULL);
2156 gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2160 g_debug ("Could not link real sink, linked fakesink instead");
2162 gst_object_unref (sinkpad);
2166 gst_object_unref (fakesink);
2171 g_mutex_unlock (priv->lock);
2177 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
2178 GstPad *sink, guint media_type, gpointer user_data)
2180 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2181 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2183 gboolean retval = FALSE;
2187 case TP_MEDIA_STREAM_TYPE_AUDIO:
2188 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2190 g_warning ("Could not add audio source to pipeline");
2194 pad = gst_element_get_static_pad (priv->audio_input, "src");
2197 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2198 g_warning ("Could not get source pad from audio source");
2202 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2204 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2205 g_warning ("Could not link audio source to farsight");
2209 if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2211 g_warning ("Could not start audio source");
2212 gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2213 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2219 case TP_MEDIA_STREAM_TYPE_VIDEO:
2220 if (priv->video_input != NULL)
2222 if (priv->video_tee != NULL)
2224 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2225 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2227 g_warning ("Could not link videp soure input pipeline");
2236 g_assert_not_reached ();
2243 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2245 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2246 GstElement *preview;
2248 DEBUG ("remove video input");
2249 preview = empathy_video_widget_get_element (
2250 EMPATHY_VIDEO_WIDGET (priv->video_preview));
2252 gst_element_set_state (priv->video_input, GST_STATE_NULL);
2253 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2254 gst_element_set_state (preview, GST_STATE_NULL);
2256 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2257 priv->video_tee, preview, NULL);
2259 g_object_unref (priv->video_input);
2260 priv->video_input = NULL;
2261 g_object_unref (priv->video_tee);
2262 priv->video_tee = NULL;
2263 gtk_widget_destroy (priv->video_preview);
2264 priv->video_preview = NULL;
2266 gtk_toggle_tool_button_set_active (
2267 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
2268 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
2270 gtk_widget_show (priv->self_user_avatar_widget);
2274 start_call (EmpathyCallWindow *self)
2276 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2278 priv->call_started = TRUE;
2279 empathy_call_handler_start_call (priv->handler);
2280 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2282 if (empathy_call_handler_has_initial_video (priv->handler))
2284 /* Enable 'send video' buttons and display the preview */
2285 gtk_toggle_tool_button_set_active (
2286 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
2291 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2294 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2295 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2298 empathy_call_handler_bus_message (priv->handler, bus, message);
2300 switch (GST_MESSAGE_TYPE (message))
2302 case GST_MESSAGE_STATE_CHANGED:
2303 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2305 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2306 if (newstate == GST_STATE_PAUSED)
2307 empathy_call_window_setup_video_input (self);
2309 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2310 !priv->call_started)
2312 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2313 if (newstate == GST_STATE_PAUSED)
2319 case GST_MESSAGE_ERROR:
2321 GError *error = NULL;
2322 GstElement *gst_error;
2325 gst_message_parse_error (message, &error, &debug);
2326 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2328 g_message ("Element error: %s -- %s\n", error->message, debug);
2330 if (g_str_has_prefix (gst_element_get_name (gst_error),
2331 VIDEO_INPUT_ERROR_PREFIX))
2333 /* Remove the video input and continue */
2334 if (priv->video_input != NULL)
2335 empathy_call_window_remove_video_input (self);
2336 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2340 empathy_call_window_disconnected (self);
2342 g_error_free (error);
2353 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
2354 EmpathyCallWindow *window)
2356 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2358 if (empathy_tp_call_is_receiving_video (call))
2360 gtk_widget_hide (priv->remote_user_avatar_widget);
2361 gtk_widget_show (priv->video_output);
2365 gtk_widget_hide (priv->video_output);
2366 gtk_widget_show (priv->remote_user_avatar_widget);
2371 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
2373 EmpathyCallWindow *self)
2375 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2376 EmpathyTpCall *call;
2378 g_object_get (priv->handler, "tp-call", &call, NULL);
2382 empathy_signal_connect_weak (call, "audio-stream-error",
2383 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
2384 empathy_signal_connect_weak (call, "video-stream-error",
2385 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
2387 g_object_unref (call);
2391 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2393 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2394 EmpathyTpCall *call;
2396 g_signal_connect (priv->handler, "conference-added",
2397 G_CALLBACK (empathy_call_window_conference_added_cb), window);
2398 g_signal_connect (priv->handler, "request-resource",
2399 G_CALLBACK (empathy_call_window_request_resource_cb), window);
2400 g_signal_connect (priv->handler, "closed",
2401 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2402 g_signal_connect (priv->handler, "src-pad-added",
2403 G_CALLBACK (empathy_call_window_src_added_cb), window);
2404 g_signal_connect (priv->handler, "sink-pad-added",
2405 G_CALLBACK (empathy_call_window_sink_added_cb), window);
2406 g_signal_connect (priv->handler, "stream-closed",
2407 G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
2409 g_object_get (priv->handler, "tp-call", &call, NULL);
2412 empathy_signal_connect_weak (call, "audio-stream-error",
2413 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
2414 empathy_signal_connect_weak (call, "video-stream-error",
2415 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
2417 g_object_unref (call);
2421 /* tp-call doesn't exist yet, we'll connect signals once it has been
2423 g_signal_connect (priv->handler, "notify::tp-call",
2424 G_CALLBACK (call_handler_notify_tp_call_cb), window);
2427 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2431 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2432 EmpathyCallWindow *window)
2434 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2436 if (priv->pipeline != NULL)
2438 if (priv->bus_message_source_id != 0)
2440 g_source_remove (priv->bus_message_source_id);
2441 priv->bus_message_source_id = 0;
2444 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2447 if (priv->call_state == CONNECTING)
2448 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2454 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2457 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2459 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2464 gtk_widget_hide (priv->sidebar);
2465 gtk_widget_hide (menu);
2466 gtk_widget_hide (priv->vbox);
2467 gtk_widget_hide (priv->statusbar);
2468 gtk_widget_hide (priv->toolbar);
2472 if (priv->sidebar_was_visible_before_fs)
2473 gtk_widget_show (priv->sidebar);
2475 gtk_widget_show (menu);
2476 gtk_widget_show (priv->vbox);
2477 gtk_widget_show (priv->statusbar);
2478 gtk_widget_show (priv->toolbar);
2480 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2481 priv->original_height_before_fs);
2486 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2488 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2490 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2491 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2492 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2493 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2494 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2495 priv->video_output, TRUE, TRUE,
2496 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2498 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2499 priv->vbox, TRUE, TRUE,
2500 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2505 empathy_call_window_state_event_cb (GtkWidget *widget,
2506 GdkEventWindowState *event, EmpathyCallWindow *window)
2508 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2510 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2511 gboolean set_fullscreen = event->new_window_state &
2512 GDK_WINDOW_STATE_FULLSCREEN;
2516 gboolean sidebar_was_visible;
2517 GtkAllocation allocation;
2518 gint original_width, original_height;
2520 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2521 original_width = allocation.width;
2522 original_height = allocation.height;
2524 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2526 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2527 priv->original_width_before_fs = original_width;
2528 priv->original_height_before_fs = original_height;
2530 if (priv->video_output_motion_handler_id == 0 &&
2531 priv->video_output != NULL)
2533 priv->video_output_motion_handler_id = g_signal_connect (
2534 G_OBJECT (priv->video_output), "motion-notify-event",
2535 G_CALLBACK (empathy_call_window_video_output_motion_notify),
2541 if (priv->video_output_motion_handler_id != 0)
2543 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2544 priv->video_output_motion_handler_id);
2545 priv->video_output_motion_handler_id = 0;
2549 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2551 show_controls (window, set_fullscreen);
2552 show_borders (window, set_fullscreen);
2553 gtk_action_set_stock_id (priv->menu_fullscreen,
2554 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2555 priv->is_fullscreen = set_fullscreen;
2562 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2563 EmpathyCallWindow *window)
2565 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2567 int w, h, handle_size;
2568 GtkAllocation allocation, sidebar_allocation;
2570 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2571 w = allocation.width;
2572 h = allocation.height;
2574 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2576 gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2577 if (gtk_toggle_button_get_active (toggle))
2579 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2580 gtk_widget_show (priv->sidebar);
2581 w += sidebar_allocation.width + handle_size;
2585 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2586 w -= sidebar_allocation.width + handle_size;
2587 gtk_widget_hide (priv->sidebar);
2590 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2593 gtk_window_resize (GTK_WINDOW (window), w, h);
2597 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2600 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2601 EmpathyTpCall *call;
2603 priv->sending_video = send;
2605 /* When we start sending video, we want to show the video preview by
2607 display_video_preview (window, send);
2609 if (priv->call_state != CONNECTED)
2612 g_object_get (priv->handler, "tp-call", &call, NULL);
2613 DEBUG ("%s sending video", send ? "start": "stop");
2614 empathy_tp_call_request_video_stream_direction (call, send);
2615 g_object_unref (call);
2619 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2620 EmpathyCallWindow *window)
2622 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2625 if (priv->audio_input == NULL)
2628 active = (gtk_toggle_tool_button_get_active (toggle));
2632 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2634 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2638 /* TODO, Instead of setting the input volume to 0 we should probably
2639 * stop sending but this would cause the audio call to drop if both
2640 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2641 * in the future. GNOME #574574
2643 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2645 gtk_adjustment_set_value (priv->audio_input_adj, 0);
2650 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2651 EmpathyCallWindow *window)
2653 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2655 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2660 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2661 EmpathyCallWindow *window)
2663 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2665 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2670 empathy_call_window_hangup_cb (gpointer object,
2671 EmpathyCallWindow *window)
2673 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2675 empathy_call_handler_stop_call (priv->handler);
2677 if (empathy_call_window_disconnected (window))
2678 gtk_widget_destroy (GTK_WIDGET (window));
2682 empathy_call_window_restart_call (EmpathyCallWindow *window)
2685 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2687 gtk_widget_destroy (priv->remote_user_output_hbox);
2688 gtk_widget_destroy (priv->self_user_output_hbox);
2690 priv->pipeline = gst_pipeline_new (NULL);
2691 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2692 priv->bus_message_source_id = gst_bus_add_watch (bus,
2693 empathy_call_window_bus_message, window);
2695 empathy_call_window_setup_remote_frame (bus, window);
2696 empathy_call_window_setup_self_frame (bus, window);
2698 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2699 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2701 /* While the call was disconnected, the input volume might have changed.
2702 * However, since the audio_input source was destroyed, its volume has not
2703 * been updated during that time. That's why we manually update it here */
2704 empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2706 g_object_unref (bus);
2708 gtk_widget_show_all (priv->content_hbox);
2710 priv->outgoing = TRUE;
2711 empathy_call_window_set_state_connecting (window);
2713 start_call (window);
2714 empathy_call_window_setup_avatars (window, priv->handler);
2716 gtk_action_set_sensitive (priv->redial, FALSE);
2717 gtk_widget_set_sensitive (priv->redial_button, FALSE);
2721 empathy_call_window_redial_cb (gpointer object,
2722 EmpathyCallWindow *window)
2724 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2726 if (priv->call_state == CONNECTED)
2727 priv->call_state = REDIALING;
2729 empathy_call_handler_stop_call (priv->handler);
2731 if (priv->call_state != CONNECTED)
2732 empathy_call_window_restart_call (window);
2736 empathy_call_window_fullscreen_cb (gpointer object,
2737 EmpathyCallWindow *window)
2739 empathy_call_window_fullscreen_toggle (window);
2743 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2745 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2747 if (priv->is_fullscreen)
2748 gtk_window_unfullscreen (GTK_WINDOW (window));
2750 gtk_window_fullscreen (GTK_WINDOW (window));
2754 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2755 GdkEventButton *event, EmpathyCallWindow *window)
2757 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2759 empathy_call_window_video_menu_popup (window, event->button);
2767 empathy_call_window_key_press_cb (GtkWidget *video_output,
2768 GdkEventKey *event, EmpathyCallWindow *window)
2770 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2772 if (priv->is_fullscreen && event->keyval == GDK_Escape)
2774 /* Since we are in fullscreen mode, toggling will bring us back to
2776 empathy_call_window_fullscreen_toggle (window);
2784 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2785 GdkEventMotion *event, EmpathyCallWindow *window)
2787 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2789 if (priv->is_fullscreen)
2791 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2798 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2802 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2804 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2806 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2807 button, gtk_get_current_event_time ());
2808 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2812 empathy_call_window_status_message (EmpathyCallWindow *window,
2815 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2817 if (priv->context_id == 0)
2819 priv->context_id = gtk_statusbar_get_context_id (
2820 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2824 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2827 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2832 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2833 gdouble value, EmpathyCallWindow *window)
2835 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2837 if (priv->audio_output == NULL)
2840 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2844 /* block all the signals related to camera control widgets. This is useful
2845 * when we are manually updating the UI and so don't want to fire the
2848 block_camera_control_signals (EmpathyCallWindow *self)
2850 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2852 g_signal_handlers_block_by_func (priv->tool_button_camera_off,
2853 tool_button_camera_off_toggled_cb, self);
2854 g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
2855 tool_button_camera_preview_toggled_cb, self);
2856 g_signal_handlers_block_by_func (priv->tool_button_camera_on,
2857 tool_button_camera_on_toggled_cb, self);
2858 g_signal_handlers_block_by_func (priv->action_camera,
2859 action_camera_change_cb, self);
2863 unblock_camera_control_signals (EmpathyCallWindow *self)
2865 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2867 g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
2868 tool_button_camera_off_toggled_cb, self);
2869 g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
2870 tool_button_camera_preview_toggled_cb, self);
2871 g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
2872 tool_button_camera_on_toggled_cb, self);
2873 g_signal_handlers_unblock_by_func (priv->action_camera,
2874 action_camera_change_cb, self);