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 create_pipeline (EmpathyCallWindow *self)
922 EmpathyCallWindowPriv *priv = GET_PRIV (self);
925 g_assert (priv->pipeline == NULL);
927 priv->pipeline = gst_pipeline_new (NULL);
928 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
929 priv->bus_message_source_id = gst_bus_add_watch (bus,
930 empathy_call_window_bus_message, self);
932 empathy_call_window_setup_remote_frame (bus, self);
933 empathy_call_window_setup_self_frame (bus, self);
935 g_object_unref (bus);
940 empathy_call_window_init (EmpathyCallWindow *self)
942 EmpathyCallWindowPriv *priv = GET_PRIV (self);
950 GError *error = NULL;
952 filename = empathy_file_lookup ("empathy-call-window.ui", "src");
953 gui = empathy_builder_get_file (filename,
954 "call_window_vbox", &top_vbox,
955 "errors_vbox", &priv->errors_vbox,
957 "statusbar", &priv->statusbar,
958 "redial", &priv->redial_button,
959 "microphone", &priv->mic_button,
960 "toolbar", &priv->toolbar,
961 "menuredial", &priv->redial,
962 "ui_manager", &priv->ui_manager,
963 "menufullscreen", &priv->menu_fullscreen,
964 "camera_off", &priv->tool_button_camera_off,
965 "camera_preview", &priv->tool_button_camera_preview,
966 "camera_on", &priv->tool_button_camera_on,
967 "action_camera_off", &priv->action_camera,
968 "action_camera_preview", &priv->action_camera_preview,
972 empathy_builder_connect (gui, self,
973 "menuhangup", "activate", empathy_call_window_hangup_cb,
974 "hangup", "clicked", empathy_call_window_hangup_cb,
975 "menuredial", "activate", empathy_call_window_redial_cb,
976 "redial", "clicked", empathy_call_window_redial_cb,
977 "microphone", "toggled", empathy_call_window_mic_toggled_cb,
978 "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
979 "camera_off", "toggled", tool_button_camera_off_toggled_cb,
980 "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
981 "camera_on", "toggled", tool_button_camera_on_toggled_cb,
982 "action_camera_off", "changed", action_camera_change_cb,
985 priv->lock = g_mutex_new ();
987 gtk_container_add (GTK_CONTAINER (self), top_vbox);
989 priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
990 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
991 CONTENT_HBOX_BORDER_WIDTH);
992 gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
994 priv->remote_user_output_frame = gtk_frame_new (NULL);
995 gtk_widget_set_size_request (priv->remote_user_output_frame,
996 EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
997 gtk_box_pack_start (GTK_BOX (priv->content_hbox),
998 priv->remote_user_output_frame, TRUE, TRUE,
999 CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1001 priv->self_user_output_frame = gtk_frame_new (NULL);
1002 gtk_widget_set_size_request (priv->self_user_output_frame,
1003 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
1005 create_pipeline (self);
1007 priv->fsnotifier = fs_element_added_notifier_new ();
1008 fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
1010 keyfile = g_key_file_new ();
1011 filename = empathy_file_lookup ("element-properties", "data");
1012 if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
1014 fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
1019 g_warning ("Could not load element-properties file: %s", error->message);
1020 g_key_file_free (keyfile);
1021 g_clear_error (&error);
1025 priv->vbox = gtk_vbox_new (FALSE, 3);
1026 gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
1027 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1028 gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
1031 empathy_call_window_setup_toolbar (self);
1033 priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
1034 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1035 g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
1036 G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
1038 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1040 h = gtk_hbox_new (FALSE, 3);
1041 gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
1042 gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
1044 priv->sidebar = empathy_sidebar_new ();
1045 g_signal_connect (G_OBJECT (priv->sidebar),
1046 "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
1047 g_signal_connect (G_OBJECT (priv->sidebar),
1048 "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
1049 gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
1051 page = empathy_call_window_create_audio_input (self);
1052 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
1055 page = empathy_call_window_create_video_input (self);
1056 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
1059 priv->dtmf_panel = empathy_call_window_create_dtmf (self);
1060 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
1063 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1066 gtk_widget_show_all (top_vbox);
1068 gtk_widget_hide (priv->sidebar);
1070 priv->fullscreen = empathy_call_window_fullscreen_new (self);
1071 empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1072 priv->video_output);
1073 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1074 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1076 g_signal_connect (G_OBJECT (self), "realize",
1077 G_CALLBACK (empathy_call_window_realized_cb), self);
1079 g_signal_connect (G_OBJECT (self), "delete-event",
1080 G_CALLBACK (empathy_call_window_delete_cb), self);
1082 g_signal_connect (G_OBJECT (self), "window-state-event",
1083 G_CALLBACK (empathy_call_window_state_event_cb), self);
1085 g_signal_connect (G_OBJECT (self), "key-press-event",
1086 G_CALLBACK (empathy_call_window_key_press_cb), self);
1088 priv->timer = g_timer_new ();
1090 g_object_ref (priv->ui_manager);
1091 g_object_unref (gui);
1093 empathy_geometry_bind (GTK_WINDOW (self), "call-window");
1096 /* Instead of specifying a width and a height, we specify only one size. That's
1097 because we want a square avatar icon. */
1099 init_contact_avatar_with_size (EmpathyContact *contact,
1100 GtkWidget *image_widget,
1103 GdkPixbuf *pixbuf_avatar = NULL;
1105 if (contact != NULL)
1107 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1111 if (pixbuf_avatar == NULL)
1113 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
1117 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1121 set_window_title (EmpathyCallWindow *self)
1123 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1126 /* translators: Call is a noun and %s is the contact name. This string
1127 * is used in the window title */
1128 tmp = g_strdup_printf (_("Call with %s"),
1129 empathy_contact_get_name (priv->contact));
1130 gtk_window_set_title (GTK_WINDOW (self), tmp);
1135 contact_name_changed_cb (EmpathyContact *contact,
1136 GParamSpec *pspec, EmpathyCallWindow *self)
1138 set_window_title (self);
1142 contact_avatar_changed_cb (EmpathyContact *contact,
1143 GParamSpec *pspec, GtkWidget *avatar_widget)
1147 size = avatar_widget->allocation.height;
1151 /* the widget is not allocated yet, set a default size */
1152 size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1153 REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1156 init_contact_avatar_with_size (contact, avatar_widget, size);
1160 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
1161 EmpathyContact *contact, const GError *error, gpointer user_data,
1162 GObject *weak_object)
1164 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1165 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1167 init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1168 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1170 g_signal_connect (contact, "notify::avatar",
1171 G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1175 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1176 EmpathyCallHandler *handler)
1178 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1180 g_object_get (handler, "contact", &(priv->contact), NULL);
1182 if (priv->contact != NULL)
1184 TpConnection *connection;
1185 EmpathyTpContactFactory *factory;
1187 set_window_title (self);
1189 g_signal_connect (priv->contact, "notify::name",
1190 G_CALLBACK (contact_name_changed_cb), self);
1191 g_signal_connect (priv->contact, "notify::avatar",
1192 G_CALLBACK (contact_avatar_changed_cb),
1193 priv->remote_user_avatar_widget);
1195 /* Retreiving the self avatar */
1196 connection = empathy_contact_get_connection (priv->contact);
1197 factory = empathy_tp_contact_factory_dup_singleton (connection);
1198 empathy_tp_contact_factory_get_from_handle (factory,
1199 tp_connection_get_self_handle (connection),
1200 empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1202 g_object_unref (factory);
1206 g_warning ("call handler doesn't have a contact");
1207 /* translators: Call is a noun. This string is used in the window
1209 gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1211 /* Since we can't access the remote contact, we can't get a connection
1212 to it and can't get the self contact (and its avatar). This means
1213 that we have to manually set the self avatar. */
1214 init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1215 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1218 init_contact_avatar_with_size (priv->contact,
1219 priv->remote_user_avatar_widget,
1220 MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1221 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1223 /* The remote avatar is shown by default and will be hidden when we receive
1224 video from the remote side. */
1225 gtk_widget_hide (priv->video_output);
1226 gtk_widget_show (priv->remote_user_avatar_widget);
1230 empathy_call_window_constructed (GObject *object)
1232 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1233 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1234 EmpathyTpCall *call;
1236 g_assert (priv->handler != NULL);
1238 g_object_get (priv->handler, "tp-call", &call, NULL);
1239 priv->outgoing = (call == NULL);
1241 g_object_unref (call);
1243 empathy_call_window_setup_avatars (self, priv->handler);
1244 empathy_call_window_set_state_connecting (self);
1246 if (!empathy_call_handler_has_initial_video (priv->handler))
1248 gtk_toggle_tool_button_set_active (
1249 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1251 /* If call has InitialVideo, the preview will be started once the call has
1252 * been started (start_call()). */
1255 static void empathy_call_window_dispose (GObject *object);
1256 static void empathy_call_window_finalize (GObject *object);
1259 empathy_call_window_set_property (GObject *object,
1260 guint property_id, const GValue *value, GParamSpec *pspec)
1262 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1264 switch (property_id)
1266 case PROP_CALL_HANDLER:
1267 priv->handler = g_value_dup_object (value);
1270 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1275 empathy_call_window_get_property (GObject *object,
1276 guint property_id, GValue *value, GParamSpec *pspec)
1278 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1280 switch (property_id)
1282 case PROP_CALL_HANDLER:
1283 g_value_set_object (value, priv->handler);
1286 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1291 empathy_call_window_class_init (
1292 EmpathyCallWindowClass *empathy_call_window_class)
1294 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1295 GParamSpec *param_spec;
1297 g_type_class_add_private (empathy_call_window_class,
1298 sizeof (EmpathyCallWindowPriv));
1300 object_class->constructed = empathy_call_window_constructed;
1301 object_class->set_property = empathy_call_window_set_property;
1302 object_class->get_property = empathy_call_window_get_property;
1304 object_class->dispose = empathy_call_window_dispose;
1305 object_class->finalize = empathy_call_window_finalize;
1307 param_spec = g_param_spec_object ("handler",
1308 "handler", "The call handler",
1309 EMPATHY_TYPE_CALL_HANDLER,
1310 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1311 g_object_class_install_property (object_class,
1312 PROP_CALL_HANDLER, param_spec);
1316 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1317 GParamSpec *property, EmpathyCallWindow *self)
1319 DEBUG ("video stream changed");
1320 empathy_call_window_update_avatars_visibility (call, self);
1324 empathy_call_window_dispose (GObject *object)
1326 EmpathyTpCall *call;
1327 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1328 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1330 if (priv->dispose_has_run)
1333 priv->dispose_has_run = TRUE;
1335 g_object_get (priv->handler, "tp-call", &call, NULL);
1339 g_signal_handlers_disconnect_by_func (call,
1340 empathy_call_window_video_stream_changed_cb, object);
1341 g_object_unref (call);
1344 if (priv->handler != NULL)
1346 empathy_call_handler_stop_call (priv->handler);
1347 g_object_unref (priv->handler);
1349 priv->handler = NULL;
1351 if (priv->pipeline != NULL)
1352 g_object_unref (priv->pipeline);
1353 priv->pipeline = NULL;
1355 if (priv->video_input != NULL)
1356 g_object_unref (priv->video_input);
1357 priv->video_input = NULL;
1359 if (priv->audio_input != NULL)
1360 g_object_unref (priv->audio_input);
1361 priv->audio_input = NULL;
1363 if (priv->audio_output != NULL)
1364 g_object_unref (priv->audio_output);
1365 priv->audio_output = NULL;
1367 if (priv->video_tee != NULL)
1368 g_object_unref (priv->video_tee);
1369 priv->video_tee = NULL;
1371 if (priv->fsnotifier != NULL)
1372 g_object_unref (priv->fsnotifier);
1373 priv->fsnotifier = NULL;
1375 if (priv->timer_id != 0)
1376 g_source_remove (priv->timer_id);
1379 if (priv->ui_manager != NULL)
1380 g_object_unref (priv->ui_manager);
1381 priv->ui_manager = NULL;
1383 if (priv->contact != NULL)
1385 g_signal_handlers_disconnect_by_func (priv->contact,
1386 contact_name_changed_cb, self);
1387 g_object_unref (priv->contact);
1388 priv->contact = NULL;
1391 /* release any references held by the object here */
1392 if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1393 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1397 empathy_call_window_finalize (GObject *object)
1399 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1400 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1402 if (priv->video_output_motion_handler_id != 0)
1404 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1405 priv->video_output_motion_handler_id);
1406 priv->video_output_motion_handler_id = 0;
1409 if (priv->bus_message_source_id != 0)
1411 g_source_remove (priv->bus_message_source_id);
1412 priv->bus_message_source_id = 0;
1415 /* free any data held directly by the object here */
1416 g_mutex_free (priv->lock);
1418 g_timer_destroy (priv->timer);
1420 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1425 empathy_call_window_new (EmpathyCallHandler *handler)
1427 return EMPATHY_CALL_WINDOW (
1428 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1432 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1433 GstElement *conference, gpointer user_data)
1435 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1436 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1438 gst_bin_add (GST_BIN (priv->pipeline), conference);
1440 gst_element_set_state (conference, GST_STATE_PLAYING);
1444 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1445 FsMediaType type, FsStreamDirection direction, gpointer user_data)
1447 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1448 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1450 if (type != FS_MEDIA_TYPE_VIDEO)
1453 if (direction == FS_DIRECTION_RECV)
1456 /* video and direction is send */
1457 return priv->video_input != NULL;
1461 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1463 GstStateChangeReturn state_change_return;
1464 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1466 if (priv->pipeline == NULL)
1469 if (priv->bus_message_source_id != 0)
1471 g_source_remove (priv->bus_message_source_id);
1472 priv->bus_message_source_id = 0;
1475 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1477 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1478 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1480 if (priv->pipeline != NULL)
1481 g_object_unref (priv->pipeline);
1482 priv->pipeline = NULL;
1484 if (priv->video_input != NULL)
1485 g_object_unref (priv->video_input);
1486 priv->video_input = NULL;
1488 if (priv->audio_input != NULL)
1489 g_object_unref (priv->audio_input);
1490 priv->audio_input = NULL;
1492 g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1493 empathy_call_window_mic_volume_changed_cb, self);
1495 if (priv->audio_output != NULL)
1496 g_object_unref (priv->audio_output);
1497 priv->audio_output = NULL;
1499 if (priv->video_tee != NULL)
1500 g_object_unref (priv->video_tee);
1501 priv->video_tee = NULL;
1503 if (priv->video_preview != NULL)
1504 gtk_widget_destroy (priv->video_preview);
1505 priv->video_preview = NULL;
1507 priv->liveadder = NULL;
1508 priv->funnel = NULL;
1514 g_message ("Error: could not destroy pipeline. Closing call window");
1515 gtk_widget_destroy (GTK_WIDGET (self));
1522 empathy_call_window_disconnected (EmpathyCallWindow *self)
1524 gboolean could_disconnect = FALSE;
1525 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1526 gboolean could_reset_pipeline;
1528 could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1530 if (priv->call_state == CONNECTING)
1531 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1533 if (priv->call_state != REDIALING)
1534 priv->call_state = DISCONNECTED;
1536 if (could_reset_pipeline)
1538 g_mutex_lock (priv->lock);
1540 g_timer_stop (priv->timer);
1542 if (priv->timer_id != 0)
1543 g_source_remove (priv->timer_id);
1546 g_mutex_unlock (priv->lock);
1548 empathy_call_window_status_message (self, _("Disconnected"));
1550 gtk_action_set_sensitive (priv->redial, TRUE);
1551 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1553 /* Reseting the send_video, camera_buton and mic_button to their
1555 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1556 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1557 gtk_toggle_tool_button_set_active (
1558 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1559 gtk_toggle_tool_button_set_active (
1560 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1562 /* FIXME: This is to workaround the fact that the pipeline has been
1563 * destroyed and so we can't display preview until a new call (and so a
1564 * new pipeline) is created. We should fix this properly by refactoring
1565 * the code managing the pipeline. This is bug #602937 */
1566 gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
1567 gtk_action_set_sensitive (priv->action_camera_preview, FALSE);
1569 gtk_progress_bar_set_fraction (
1570 GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1572 gtk_widget_hide (priv->video_output);
1573 gtk_widget_show (priv->remote_user_avatar_widget);
1575 priv->sending_video = FALSE;
1576 priv->call_started = FALSE;
1578 could_disconnect = TRUE;
1580 /* TODO: display the self avatar of the preview (depends if the "Always
1581 * Show Video Preview" is enabled or not) */
1584 return could_disconnect;
1589 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1592 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1593 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1595 if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1596 empathy_call_window_restart_call (self);
1601 empathy_call_window_channel_stream_closed_cb (EmpathyCallHandler *handler,
1602 TfStream *stream, gpointer user_data)
1604 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1605 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1608 g_object_get (stream, "media-type", &media_type, NULL);
1611 * This assumes that there is only one video stream per channel...
1614 if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1616 if (priv->funnel != NULL)
1620 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1621 (priv->video_output));
1623 gst_element_set_state (output, GST_STATE_NULL);
1624 gst_element_set_state (priv->funnel, GST_STATE_NULL);
1626 gst_bin_remove (GST_BIN (priv->pipeline), output);
1627 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1628 priv->funnel = NULL;
1631 else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1633 if (priv->liveadder != NULL)
1635 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1636 gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1638 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1639 gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1640 priv->liveadder = NULL;
1645 /* Called with global lock held */
1647 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1649 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1653 if (priv->funnel == NULL)
1655 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1656 (priv->video_output));
1658 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1662 g_warning ("Could not create fsfunnel");
1666 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
1668 gst_object_unref (priv->funnel);
1669 priv->funnel = NULL;
1670 g_warning ("Could not add funnel to pipeline");
1674 if (!gst_bin_add (GST_BIN (priv->pipeline), output))
1676 g_warning ("Could not add the video output widget to the pipeline");
1680 if (!gst_element_link (priv->funnel, output))
1682 g_warning ("Could not link output sink to funnel");
1683 goto error_output_added;
1686 if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1688 g_warning ("Could not start video sink");
1689 goto error_output_added;
1692 if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1694 g_warning ("Could not start funnel");
1695 goto error_output_added;
1699 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1702 g_warning ("Could not get request pad from funnel");
1709 gst_element_set_locked_state (priv->funnel, TRUE);
1710 gst_element_set_locked_state (output, TRUE);
1712 gst_element_set_state (priv->funnel, GST_STATE_NULL);
1713 gst_element_set_state (output, GST_STATE_NULL);
1715 gst_bin_remove (GST_BIN (priv->pipeline), output);
1716 gst_element_set_locked_state (output, FALSE);
1720 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1721 priv->funnel = NULL;
1726 /* Called with global lock held */
1728 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1730 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1733 GError *gerror = NULL;
1735 if (priv->liveadder == NULL)
1737 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1739 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder))
1741 g_warning ("Could not add liveadder to the pipeline");
1742 goto error_add_liveadder;
1744 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
1746 g_warning ("Could not add audio sink to pipeline");
1747 goto error_add_output;
1750 if (gst_element_set_state (priv->liveadder, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1752 g_warning ("Could not start liveadder");
1756 if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1758 g_warning ("Could not start audio sink");
1762 if (GST_PAD_LINK_FAILED (
1763 gst_element_link (priv->liveadder, priv->audio_output)))
1765 g_warning ("Could not link liveadder to audio output");
1770 filter = gst_parse_bin_from_description (
1771 "audioconvert ! audioresample ! audioconvert", TRUE, &gerror);
1774 g_warning ("Could not make audio conversion filter: %s", gerror->message);
1775 g_clear_error (&gerror);
1779 if (!gst_bin_add (GST_BIN (priv->pipeline), filter))
1781 g_warning ("Could not add audio conversion filter to pipeline");
1782 gst_object_unref (filter);
1786 if (gst_element_set_state (filter, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1788 g_warning ("Could not start audio conversion filter");
1792 if (!gst_element_link (filter, priv->liveadder))
1794 g_warning ("Could not link audio conversion filter to liveadder");
1798 pad = gst_element_get_static_pad (filter, "sink");
1802 g_warning ("Could not get sink pad from filter");
1810 gst_element_set_locked_state (filter, TRUE);
1811 gst_element_set_state (filter, GST_STATE_NULL);
1812 gst_bin_remove (GST_BIN (priv->pipeline), filter);
1816 gst_element_set_locked_state (priv->liveadder, TRUE);
1817 gst_element_set_locked_state (priv->audio_output, TRUE);
1819 gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1820 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1822 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1826 gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1828 gst_element_set_locked_state (priv->liveadder, FALSE);
1829 gst_element_set_locked_state (priv->audio_output, FALSE);
1831 error_add_liveadder:
1833 if (priv->liveadder != NULL)
1835 gst_object_unref (priv->liveadder);
1836 priv->liveadder = NULL;
1843 empathy_call_window_update_timer (gpointer user_data)
1845 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1846 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1850 time_ = g_timer_elapsed (priv->timer, NULL);
1852 /* Translators: number of minutes:seconds the caller has been connected */
1853 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time_ / 60,
1855 empathy_call_window_status_message (self, str);
1862 display_error (EmpathyCallWindow *self,
1863 EmpathyTpCall *call,
1867 const gchar *details)
1869 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1870 GtkWidget *info_bar;
1871 GtkWidget *content_area;
1878 /* Create info bar */
1879 info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1882 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1884 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1886 /* hbox containing the image and the messages vbox */
1887 hbox = gtk_hbox_new (FALSE, 3);
1888 gtk_container_add (GTK_CONTAINER (content_area), hbox);
1891 image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1892 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1894 /* vbox containing the main message and the details expander */
1895 vbox = gtk_vbox_new (FALSE, 3);
1896 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1899 txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
1901 label = gtk_label_new (NULL);
1902 gtk_label_set_markup (GTK_LABEL (label), txt);
1903 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1904 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1907 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1910 if (details != NULL)
1912 GtkWidget *expander;
1914 expander = gtk_expander_new (_("Technical Details"));
1916 txt = g_strdup_printf ("<i>%s</i>", details);
1918 label = gtk_label_new (NULL);
1919 gtk_label_set_markup (GTK_LABEL (label), txt);
1920 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1921 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1924 gtk_container_add (GTK_CONTAINER (expander), label);
1925 gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1928 g_signal_connect (info_bar, "response",
1929 G_CALLBACK (gtk_widget_destroy), NULL);
1931 gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1932 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1933 gtk_widget_show_all (info_bar);
1937 media_stream_error_to_txt (EmpathyCallWindow *self,
1938 EmpathyTpCall *call,
1940 TpMediaStreamError error)
1942 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1949 case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1951 return g_strdup_printf (
1952 _("%s's software does not understand any of the audio formats "
1953 "supported by your computer"),
1954 empathy_contact_get_name (priv->contact));
1956 return g_strdup_printf (
1957 _("%s's software does not understand any of the video formats "
1958 "supported by your computer"),
1959 empathy_contact_get_name (priv->contact));
1961 case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1962 return g_strdup_printf (
1963 _("Can't establish a connection to %s. "
1964 "One of you might be on a network that does not allow "
1965 "direct connections."),
1966 empathy_contact_get_name (priv->contact));
1968 case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
1969 return g_strdup (_("There was a failure on the network"));
1971 case TP_MEDIA_STREAM_ERROR_NO_CODECS:
1973 return g_strdup (_("The audio formats necessary for this call "
1974 "are not installed on your computer"));
1976 return g_strdup (_("The video formats necessary for this call "
1977 "are not installed on your computer"));
1979 case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
1980 cm = empathy_tp_call_get_connection_manager (call);
1982 url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
1983 "product=Telepathy&component=%s", cm);
1985 result = g_strdup_printf (
1986 _("Something unexpected happened in a Telepathy component. "
1987 "Please <a href=\"%s\">report this bug</a> and attach "
1988 "logs gathered from the 'Debug' window in the Help menu."), url);
1993 case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
1994 return g_strdup (_("There was a failure in the call engine"));
2002 empathy_call_window_stream_error (EmpathyCallWindow *self,
2003 EmpathyTpCall *call,
2012 desc = media_stream_error_to_txt (self, call, audio, code);
2015 /* No description, use the error message. That's not great as it's not
2016 * localized but it's better than nothing. */
2017 display_error (self, call, icon, title, msg, NULL);
2021 display_error (self, call, icon, title, desc, msg);
2027 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
2030 EmpathyCallWindow *self)
2032 empathy_call_window_stream_error (self, call, TRUE, code, msg,
2033 "gnome-stock-mic", _("Can't establish audio stream"));
2037 empathy_call_window_video_stream_error (EmpathyTpCall *call,
2040 EmpathyCallWindow *self)
2042 empathy_call_window_stream_error (self, call, FALSE, code, msg,
2043 "camera-web", _("Can't establish video stream"));
2047 empathy_call_window_connected (gpointer user_data)
2049 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2050 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2051 EmpathyTpCall *call;
2052 gboolean can_send_video;
2054 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2056 can_send_video = priv->video_input != NULL && priv->contact != NULL &&
2057 empathy_contact_can_voip_video (priv->contact);
2059 g_object_get (priv->handler, "tp-call", &call, NULL);
2061 g_signal_connect (call, "notify::video-stream",
2062 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
2064 if (empathy_tp_call_has_dtmf (call))
2065 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2067 if (priv->video_input == NULL)
2068 empathy_call_window_set_send_video (self, FALSE);
2070 priv->sending_video = can_send_video ?
2071 empathy_tp_call_is_sending_video (call) : FALSE;
2073 gtk_toggle_tool_button_set_active (
2074 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
2075 priv->sending_video && priv->video_input != NULL);
2076 gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
2078 gtk_action_set_sensitive (priv->redial, FALSE);
2079 gtk_widget_set_sensitive (priv->redial_button, FALSE);
2081 gtk_widget_set_sensitive (priv->mic_button, TRUE);
2083 /* FIXME: this should won't be needed once bug #602937 is fixed
2084 * (see empathy_call_window_disconnected for details) */
2085 gtk_widget_set_sensitive (priv->tool_button_camera_preview, TRUE);
2086 gtk_action_set_sensitive (priv->action_camera_preview, TRUE);
2088 empathy_call_window_update_avatars_visibility (call, self);
2090 g_object_unref (call);
2092 g_mutex_lock (priv->lock);
2094 priv->timer_id = g_timeout_add_seconds (1,
2095 empathy_call_window_update_timer, self);
2097 g_mutex_unlock (priv->lock);
2099 empathy_call_window_update_timer (self);
2105 /* Called from the streaming thread */
2107 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
2108 GstPad *src, guint media_type, gpointer user_data)
2110 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2111 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2112 gboolean retval = FALSE;
2116 g_mutex_lock (priv->lock);
2118 if (priv->call_state != CONNECTED)
2120 g_timer_start (priv->timer);
2121 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
2122 priv->call_state = CONNECTED;
2127 case TP_MEDIA_STREAM_TYPE_AUDIO:
2128 pad = empathy_call_window_get_audio_sink_pad (self);
2130 case TP_MEDIA_STREAM_TYPE_VIDEO:
2131 gtk_widget_hide (priv->remote_user_avatar_widget);
2132 gtk_widget_show (priv->video_output);
2133 pad = empathy_call_window_get_video_sink_pad (self);
2136 g_assert_not_reached ();
2142 if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2143 g_warning ("Could not link %s sink pad",
2144 media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2148 gst_object_unref (pad);
2152 /* If no sink could be linked, try to add fakesink to prevent the whole call
2157 GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2159 if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2161 GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2162 if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2163 GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2165 gst_element_set_locked_state (fakesink, TRUE);
2166 gst_element_set_state (fakesink, GST_STATE_NULL);
2167 gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2171 g_debug ("Could not link real sink, linked fakesink instead");
2173 gst_object_unref (sinkpad);
2177 gst_object_unref (fakesink);
2182 g_mutex_unlock (priv->lock);
2188 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
2189 GstPad *sink, guint media_type, gpointer user_data)
2191 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2192 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2194 gboolean retval = FALSE;
2198 case TP_MEDIA_STREAM_TYPE_AUDIO:
2199 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2201 g_warning ("Could not add audio source to pipeline");
2205 pad = gst_element_get_static_pad (priv->audio_input, "src");
2208 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2209 g_warning ("Could not get source pad from audio source");
2213 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2215 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2216 g_warning ("Could not link audio source to farsight");
2220 if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2222 g_warning ("Could not start audio source");
2223 gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2224 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2230 case TP_MEDIA_STREAM_TYPE_VIDEO:
2231 if (priv->video_input != NULL)
2233 if (priv->video_tee != NULL)
2235 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2236 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2238 g_warning ("Could not link videp soure input pipeline");
2247 g_assert_not_reached ();
2254 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2256 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2257 GstElement *preview;
2259 DEBUG ("remove video input");
2260 preview = empathy_video_widget_get_element (
2261 EMPATHY_VIDEO_WIDGET (priv->video_preview));
2263 gst_element_set_state (priv->video_input, GST_STATE_NULL);
2264 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2265 gst_element_set_state (preview, GST_STATE_NULL);
2267 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2268 priv->video_tee, preview, NULL);
2270 g_object_unref (priv->video_input);
2271 priv->video_input = NULL;
2272 g_object_unref (priv->video_tee);
2273 priv->video_tee = NULL;
2274 gtk_widget_destroy (priv->video_preview);
2275 priv->video_preview = NULL;
2277 gtk_toggle_tool_button_set_active (
2278 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
2279 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
2281 gtk_widget_show (priv->self_user_avatar_widget);
2285 start_call (EmpathyCallWindow *self)
2287 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2289 priv->call_started = TRUE;
2290 empathy_call_handler_start_call (priv->handler);
2291 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2293 if (empathy_call_handler_has_initial_video (priv->handler))
2295 /* Enable 'send video' buttons and display the preview */
2296 gtk_toggle_tool_button_set_active (
2297 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
2302 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2305 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2306 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2309 empathy_call_handler_bus_message (priv->handler, bus, message);
2311 switch (GST_MESSAGE_TYPE (message))
2313 case GST_MESSAGE_STATE_CHANGED:
2314 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2316 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2317 if (newstate == GST_STATE_PAUSED)
2318 empathy_call_window_setup_video_input (self);
2320 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2321 !priv->call_started)
2323 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2324 if (newstate == GST_STATE_PAUSED)
2330 case GST_MESSAGE_ERROR:
2332 GError *error = NULL;
2333 GstElement *gst_error;
2336 gst_message_parse_error (message, &error, &debug);
2337 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2339 g_message ("Element error: %s -- %s\n", error->message, debug);
2341 if (g_str_has_prefix (gst_element_get_name (gst_error),
2342 VIDEO_INPUT_ERROR_PREFIX))
2344 /* Remove the video input and continue */
2345 if (priv->video_input != NULL)
2346 empathy_call_window_remove_video_input (self);
2347 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2351 empathy_call_window_disconnected (self);
2353 g_error_free (error);
2364 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
2365 EmpathyCallWindow *window)
2367 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2369 if (empathy_tp_call_is_receiving_video (call))
2371 gtk_widget_hide (priv->remote_user_avatar_widget);
2372 gtk_widget_show (priv->video_output);
2376 gtk_widget_hide (priv->video_output);
2377 gtk_widget_show (priv->remote_user_avatar_widget);
2382 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
2384 EmpathyCallWindow *self)
2386 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2387 EmpathyTpCall *call;
2389 g_object_get (priv->handler, "tp-call", &call, NULL);
2393 empathy_signal_connect_weak (call, "audio-stream-error",
2394 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
2395 empathy_signal_connect_weak (call, "video-stream-error",
2396 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
2398 g_object_unref (call);
2402 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2404 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2405 EmpathyTpCall *call;
2407 g_signal_connect (priv->handler, "conference-added",
2408 G_CALLBACK (empathy_call_window_conference_added_cb), window);
2409 g_signal_connect (priv->handler, "request-resource",
2410 G_CALLBACK (empathy_call_window_request_resource_cb), window);
2411 g_signal_connect (priv->handler, "closed",
2412 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2413 g_signal_connect (priv->handler, "src-pad-added",
2414 G_CALLBACK (empathy_call_window_src_added_cb), window);
2415 g_signal_connect (priv->handler, "sink-pad-added",
2416 G_CALLBACK (empathy_call_window_sink_added_cb), window);
2417 g_signal_connect (priv->handler, "stream-closed",
2418 G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
2420 g_object_get (priv->handler, "tp-call", &call, NULL);
2423 empathy_signal_connect_weak (call, "audio-stream-error",
2424 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
2425 empathy_signal_connect_weak (call, "video-stream-error",
2426 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
2428 g_object_unref (call);
2432 /* tp-call doesn't exist yet, we'll connect signals once it has been
2434 g_signal_connect (priv->handler, "notify::tp-call",
2435 G_CALLBACK (call_handler_notify_tp_call_cb), window);
2438 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2442 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2443 EmpathyCallWindow *window)
2445 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2447 if (priv->pipeline != NULL)
2449 if (priv->bus_message_source_id != 0)
2451 g_source_remove (priv->bus_message_source_id);
2452 priv->bus_message_source_id = 0;
2455 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2458 if (priv->call_state == CONNECTING)
2459 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2465 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2468 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2470 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2475 gtk_widget_hide (priv->sidebar);
2476 gtk_widget_hide (menu);
2477 gtk_widget_hide (priv->vbox);
2478 gtk_widget_hide (priv->statusbar);
2479 gtk_widget_hide (priv->toolbar);
2483 if (priv->sidebar_was_visible_before_fs)
2484 gtk_widget_show (priv->sidebar);
2486 gtk_widget_show (menu);
2487 gtk_widget_show (priv->vbox);
2488 gtk_widget_show (priv->statusbar);
2489 gtk_widget_show (priv->toolbar);
2491 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2492 priv->original_height_before_fs);
2497 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2499 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2501 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2502 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2503 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2504 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2505 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2506 priv->video_output, TRUE, TRUE,
2507 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2509 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2510 priv->vbox, TRUE, TRUE,
2511 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2516 empathy_call_window_state_event_cb (GtkWidget *widget,
2517 GdkEventWindowState *event, EmpathyCallWindow *window)
2519 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2521 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2522 gboolean set_fullscreen = event->new_window_state &
2523 GDK_WINDOW_STATE_FULLSCREEN;
2527 gboolean sidebar_was_visible;
2528 GtkAllocation allocation;
2529 gint original_width, original_height;
2531 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2532 original_width = allocation.width;
2533 original_height = allocation.height;
2535 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2537 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2538 priv->original_width_before_fs = original_width;
2539 priv->original_height_before_fs = original_height;
2541 if (priv->video_output_motion_handler_id == 0 &&
2542 priv->video_output != NULL)
2544 priv->video_output_motion_handler_id = g_signal_connect (
2545 G_OBJECT (priv->video_output), "motion-notify-event",
2546 G_CALLBACK (empathy_call_window_video_output_motion_notify),
2552 if (priv->video_output_motion_handler_id != 0)
2554 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2555 priv->video_output_motion_handler_id);
2556 priv->video_output_motion_handler_id = 0;
2560 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2562 show_controls (window, set_fullscreen);
2563 show_borders (window, set_fullscreen);
2564 gtk_action_set_stock_id (priv->menu_fullscreen,
2565 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2566 priv->is_fullscreen = set_fullscreen;
2573 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2574 EmpathyCallWindow *window)
2576 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2578 int w, h, handle_size;
2579 GtkAllocation allocation, sidebar_allocation;
2581 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2582 w = allocation.width;
2583 h = allocation.height;
2585 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2587 gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2588 if (gtk_toggle_button_get_active (toggle))
2590 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2591 gtk_widget_show (priv->sidebar);
2592 w += sidebar_allocation.width + handle_size;
2596 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2597 w -= sidebar_allocation.width + handle_size;
2598 gtk_widget_hide (priv->sidebar);
2601 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2604 gtk_window_resize (GTK_WINDOW (window), w, h);
2608 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2611 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2612 EmpathyTpCall *call;
2614 priv->sending_video = send;
2616 /* When we start sending video, we want to show the video preview by
2618 display_video_preview (window, send);
2620 if (priv->call_state != CONNECTED)
2623 g_object_get (priv->handler, "tp-call", &call, NULL);
2624 DEBUG ("%s sending video", send ? "start": "stop");
2625 empathy_tp_call_request_video_stream_direction (call, send);
2626 g_object_unref (call);
2630 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2631 EmpathyCallWindow *window)
2633 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2636 if (priv->audio_input == NULL)
2639 active = (gtk_toggle_tool_button_get_active (toggle));
2643 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2645 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2649 /* TODO, Instead of setting the input volume to 0 we should probably
2650 * stop sending but this would cause the audio call to drop if both
2651 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2652 * in the future. GNOME #574574
2654 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2656 gtk_adjustment_set_value (priv->audio_input_adj, 0);
2661 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2662 EmpathyCallWindow *window)
2664 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2666 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2671 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2672 EmpathyCallWindow *window)
2674 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2676 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2681 empathy_call_window_hangup_cb (gpointer object,
2682 EmpathyCallWindow *window)
2684 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2686 empathy_call_handler_stop_call (priv->handler);
2688 if (empathy_call_window_disconnected (window))
2689 gtk_widget_destroy (GTK_WIDGET (window));
2693 empathy_call_window_restart_call (EmpathyCallWindow *window)
2695 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2697 gtk_widget_destroy (priv->remote_user_output_hbox);
2698 gtk_widget_destroy (priv->self_user_output_hbox);
2700 create_pipeline (window);
2702 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2703 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2705 /* While the call was disconnected, the input volume might have changed.
2706 * However, since the audio_input source was destroyed, its volume has not
2707 * been updated during that time. That's why we manually update it here */
2708 empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2710 gtk_widget_show_all (priv->content_hbox);
2712 priv->outgoing = TRUE;
2713 empathy_call_window_set_state_connecting (window);
2715 start_call (window);
2716 empathy_call_window_setup_avatars (window, priv->handler);
2718 gtk_action_set_sensitive (priv->redial, FALSE);
2719 gtk_widget_set_sensitive (priv->redial_button, FALSE);
2723 empathy_call_window_redial_cb (gpointer object,
2724 EmpathyCallWindow *window)
2726 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2728 if (priv->call_state == CONNECTED)
2729 priv->call_state = REDIALING;
2731 empathy_call_handler_stop_call (priv->handler);
2733 if (priv->call_state != CONNECTED)
2734 empathy_call_window_restart_call (window);
2738 empathy_call_window_fullscreen_cb (gpointer object,
2739 EmpathyCallWindow *window)
2741 empathy_call_window_fullscreen_toggle (window);
2745 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2747 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2749 if (priv->is_fullscreen)
2750 gtk_window_unfullscreen (GTK_WINDOW (window));
2752 gtk_window_fullscreen (GTK_WINDOW (window));
2756 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2757 GdkEventButton *event, EmpathyCallWindow *window)
2759 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2761 empathy_call_window_video_menu_popup (window, event->button);
2769 empathy_call_window_key_press_cb (GtkWidget *video_output,
2770 GdkEventKey *event, EmpathyCallWindow *window)
2772 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2774 if (priv->is_fullscreen && event->keyval == GDK_Escape)
2776 /* Since we are in fullscreen mode, toggling will bring us back to
2778 empathy_call_window_fullscreen_toggle (window);
2786 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2787 GdkEventMotion *event, EmpathyCallWindow *window)
2789 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2791 if (priv->is_fullscreen)
2793 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2800 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2804 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2806 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2808 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2809 button, gtk_get_current_event_time ());
2810 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2814 empathy_call_window_status_message (EmpathyCallWindow *window,
2817 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2819 if (priv->context_id == 0)
2821 priv->context_id = gtk_statusbar_get_context_id (
2822 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2826 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2829 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2834 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2835 gdouble value, EmpathyCallWindow *window)
2837 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2839 if (priv->audio_output == NULL)
2842 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2846 /* block all the signals related to camera control widgets. This is useful
2847 * when we are manually updating the UI and so don't want to fire the
2850 block_camera_control_signals (EmpathyCallWindow *self)
2852 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2854 g_signal_handlers_block_by_func (priv->tool_button_camera_off,
2855 tool_button_camera_off_toggled_cb, self);
2856 g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
2857 tool_button_camera_preview_toggled_cb, self);
2858 g_signal_handlers_block_by_func (priv->tool_button_camera_on,
2859 tool_button_camera_on_toggled_cb, self);
2860 g_signal_handlers_block_by_func (priv->action_camera,
2861 action_camera_change_cb, self);
2865 unblock_camera_control_signals (EmpathyCallWindow *self)
2867 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2869 g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
2870 tool_button_camera_off_toggled_cb, self);
2871 g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
2872 tool_button_camera_preview_toggled_cb, self);
2873 g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
2874 tool_button_camera_on_toggled_cb, self);
2875 g_signal_handlers_unblock_by_func (priv->action_camera,
2876 action_camera_change_cb, self);