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 priv->dtmf_panel = empathy_call_window_create_dtmf (self);
1041 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
1044 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1046 page = empathy_call_window_create_audio_input (self);
1047 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
1050 page = empathy_call_window_create_video_input (self);
1051 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
1054 gtk_widget_show_all (top_vbox);
1056 gtk_widget_hide (priv->sidebar);
1058 priv->fullscreen = empathy_call_window_fullscreen_new (self);
1059 empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1060 priv->video_output);
1061 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1062 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1064 g_signal_connect (G_OBJECT (self), "realize",
1065 G_CALLBACK (empathy_call_window_realized_cb), self);
1067 g_signal_connect (G_OBJECT (self), "delete-event",
1068 G_CALLBACK (empathy_call_window_delete_cb), self);
1070 g_signal_connect (G_OBJECT (self), "window-state-event",
1071 G_CALLBACK (empathy_call_window_state_event_cb), self);
1073 g_signal_connect (G_OBJECT (self), "key-press-event",
1074 G_CALLBACK (empathy_call_window_key_press_cb), self);
1076 priv->timer = g_timer_new ();
1078 g_object_ref (priv->ui_manager);
1079 g_object_unref (gui);
1081 empathy_geometry_bind (GTK_WINDOW (self), "call-window");
1084 /* Instead of specifying a width and a height, we specify only one size. That's
1085 because we want a square avatar icon. */
1087 init_contact_avatar_with_size (EmpathyContact *contact,
1088 GtkWidget *image_widget,
1091 GdkPixbuf *pixbuf_avatar = NULL;
1093 if (contact != NULL)
1095 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1099 if (pixbuf_avatar == NULL)
1101 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
1105 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1109 set_window_title (EmpathyCallWindow *self)
1111 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1114 /* translators: Call is a noun and %s is the contact name. This string
1115 * is used in the window title */
1116 tmp = g_strdup_printf (_("Call with %s"),
1117 empathy_contact_get_name (priv->contact));
1118 gtk_window_set_title (GTK_WINDOW (self), tmp);
1123 contact_name_changed_cb (EmpathyContact *contact,
1124 GParamSpec *pspec, EmpathyCallWindow *self)
1126 set_window_title (self);
1130 contact_avatar_changed_cb (EmpathyContact *contact,
1131 GParamSpec *pspec, GtkWidget *avatar_widget)
1135 size = avatar_widget->allocation.height;
1139 /* the widget is not allocated yet, set a default size */
1140 size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1141 REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1144 init_contact_avatar_with_size (contact, avatar_widget, size);
1148 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
1149 EmpathyContact *contact, const GError *error, gpointer user_data,
1150 GObject *weak_object)
1152 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1153 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1155 init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1156 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1158 g_signal_connect (contact, "notify::avatar",
1159 G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1163 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1164 EmpathyCallHandler *handler)
1166 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1168 g_object_get (handler, "contact", &(priv->contact), NULL);
1170 if (priv->contact != NULL)
1172 TpConnection *connection;
1173 EmpathyTpContactFactory *factory;
1175 set_window_title (self);
1177 g_signal_connect (priv->contact, "notify::name",
1178 G_CALLBACK (contact_name_changed_cb), self);
1179 g_signal_connect (priv->contact, "notify::avatar",
1180 G_CALLBACK (contact_avatar_changed_cb),
1181 priv->remote_user_avatar_widget);
1183 /* Retreiving the self avatar */
1184 connection = empathy_contact_get_connection (priv->contact);
1185 factory = empathy_tp_contact_factory_dup_singleton (connection);
1186 empathy_tp_contact_factory_get_from_handle (factory,
1187 tp_connection_get_self_handle (connection),
1188 empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1190 g_object_unref (factory);
1194 g_warning ("call handler doesn't have a contact");
1195 /* translators: Call is a noun. This string is used in the window
1197 gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1199 /* Since we can't access the remote contact, we can't get a connection
1200 to it and can't get the self contact (and its avatar). This means
1201 that we have to manually set the self avatar. */
1202 init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1203 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1206 init_contact_avatar_with_size (priv->contact,
1207 priv->remote_user_avatar_widget,
1208 MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1209 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1211 /* The remote avatar is shown by default and will be hidden when we receive
1212 video from the remote side. */
1213 gtk_widget_hide (priv->video_output);
1214 gtk_widget_show (priv->remote_user_avatar_widget);
1218 empathy_call_window_constructed (GObject *object)
1220 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1221 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1222 EmpathyTpCall *call;
1224 g_assert (priv->handler != NULL);
1226 g_object_get (priv->handler, "tp-call", &call, NULL);
1227 priv->outgoing = (call == NULL);
1229 g_object_unref (call);
1231 empathy_call_window_setup_avatars (self, priv->handler);
1232 empathy_call_window_set_state_connecting (self);
1234 if (empathy_call_handler_has_initial_video (priv->handler))
1236 /* Enable 'send video' buttons and display the preview */
1237 gtk_toggle_tool_button_set_active (
1238 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
1240 display_video_preview (self, TRUE);
1244 gtk_toggle_tool_button_set_active (
1245 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1249 static void empathy_call_window_dispose (GObject *object);
1250 static void empathy_call_window_finalize (GObject *object);
1253 empathy_call_window_set_property (GObject *object,
1254 guint property_id, const GValue *value, GParamSpec *pspec)
1256 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1258 switch (property_id)
1260 case PROP_CALL_HANDLER:
1261 priv->handler = g_value_dup_object (value);
1264 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1269 empathy_call_window_get_property (GObject *object,
1270 guint property_id, GValue *value, GParamSpec *pspec)
1272 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1274 switch (property_id)
1276 case PROP_CALL_HANDLER:
1277 g_value_set_object (value, priv->handler);
1280 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1285 empathy_call_window_class_init (
1286 EmpathyCallWindowClass *empathy_call_window_class)
1288 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1289 GParamSpec *param_spec;
1291 g_type_class_add_private (empathy_call_window_class,
1292 sizeof (EmpathyCallWindowPriv));
1294 object_class->constructed = empathy_call_window_constructed;
1295 object_class->set_property = empathy_call_window_set_property;
1296 object_class->get_property = empathy_call_window_get_property;
1298 object_class->dispose = empathy_call_window_dispose;
1299 object_class->finalize = empathy_call_window_finalize;
1301 param_spec = g_param_spec_object ("handler",
1302 "handler", "The call handler",
1303 EMPATHY_TYPE_CALL_HANDLER,
1304 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1305 g_object_class_install_property (object_class,
1306 PROP_CALL_HANDLER, param_spec);
1310 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1311 GParamSpec *property, EmpathyCallWindow *self)
1313 DEBUG ("video stream changed");
1314 empathy_call_window_update_avatars_visibility (call, self);
1318 empathy_call_window_dispose (GObject *object)
1320 EmpathyTpCall *call;
1321 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1322 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1324 if (priv->dispose_has_run)
1327 priv->dispose_has_run = TRUE;
1329 g_object_get (priv->handler, "tp-call", &call, NULL);
1333 g_signal_handlers_disconnect_by_func (call,
1334 empathy_call_window_video_stream_changed_cb, object);
1335 g_object_unref (call);
1338 if (priv->handler != NULL)
1339 g_object_unref (priv->handler);
1340 priv->handler = NULL;
1342 if (priv->pipeline != NULL)
1343 g_object_unref (priv->pipeline);
1344 priv->pipeline = NULL;
1346 if (priv->video_input != NULL)
1347 g_object_unref (priv->video_input);
1348 priv->video_input = NULL;
1350 if (priv->audio_input != NULL)
1351 g_object_unref (priv->audio_input);
1352 priv->audio_input = NULL;
1354 if (priv->audio_output != NULL)
1355 g_object_unref (priv->audio_output);
1356 priv->audio_output = NULL;
1358 if (priv->video_tee != NULL)
1359 g_object_unref (priv->video_tee);
1360 priv->video_tee = NULL;
1362 if (priv->fsnotifier != NULL)
1363 g_object_unref (priv->fsnotifier);
1364 priv->fsnotifier = NULL;
1366 if (priv->timer_id != 0)
1367 g_source_remove (priv->timer_id);
1370 if (priv->ui_manager != NULL)
1371 g_object_unref (priv->ui_manager);
1372 priv->ui_manager = NULL;
1374 if (priv->contact != NULL)
1376 g_signal_handlers_disconnect_by_func (priv->contact,
1377 contact_name_changed_cb, self);
1378 g_object_unref (priv->contact);
1379 priv->contact = NULL;
1382 /* release any references held by the object here */
1383 if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1384 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1388 empathy_call_window_finalize (GObject *object)
1390 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1391 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1393 if (priv->video_output_motion_handler_id != 0)
1395 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1396 priv->video_output_motion_handler_id);
1397 priv->video_output_motion_handler_id = 0;
1400 if (priv->bus_message_source_id != 0)
1402 g_source_remove (priv->bus_message_source_id);
1403 priv->bus_message_source_id = 0;
1406 /* free any data held directly by the object here */
1407 g_mutex_free (priv->lock);
1409 g_timer_destroy (priv->timer);
1411 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1416 empathy_call_window_new (EmpathyCallHandler *handler)
1418 return EMPATHY_CALL_WINDOW (
1419 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1423 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1424 GstElement *conference, gpointer user_data)
1426 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1427 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1429 gst_bin_add (GST_BIN (priv->pipeline), conference);
1431 gst_element_set_state (conference, GST_STATE_PLAYING);
1435 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1436 FsMediaType type, FsStreamDirection direction, gpointer user_data)
1438 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1439 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1441 if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1444 if (direction == FS_DIRECTION_RECV)
1447 /* video and direction is send */
1448 return priv->video_input != NULL;
1452 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1454 GstStateChangeReturn state_change_return;
1455 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1457 if (priv->pipeline == NULL)
1460 if (priv->bus_message_source_id != 0)
1462 g_source_remove (priv->bus_message_source_id);
1463 priv->bus_message_source_id = 0;
1466 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1468 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1469 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1471 if (priv->pipeline != NULL)
1472 g_object_unref (priv->pipeline);
1473 priv->pipeline = NULL;
1475 if (priv->video_input != NULL)
1476 g_object_unref (priv->video_input);
1477 priv->video_input = NULL;
1479 if (priv->audio_input != NULL)
1480 g_object_unref (priv->audio_input);
1481 priv->audio_input = NULL;
1483 g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1484 empathy_call_window_mic_volume_changed_cb, self);
1486 if (priv->audio_output != NULL)
1487 g_object_unref (priv->audio_output);
1488 priv->audio_output = NULL;
1490 if (priv->video_tee != NULL)
1491 g_object_unref (priv->video_tee);
1492 priv->video_tee = NULL;
1494 if (priv->video_preview != NULL)
1495 gtk_widget_destroy (priv->video_preview);
1496 priv->video_preview = NULL;
1498 priv->liveadder = NULL;
1499 priv->funnel = NULL;
1505 g_message ("Error: could not destroy pipeline. Closing call window");
1506 gtk_widget_destroy (GTK_WIDGET (self));
1513 empathy_call_window_disconnected (EmpathyCallWindow *self)
1515 gboolean could_disconnect = FALSE;
1516 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1517 gboolean 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);
1641 if (priv->funnel == NULL)
1645 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1646 (priv->video_output));
1648 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1650 gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1651 gst_bin_add (GST_BIN (priv->pipeline), output);
1653 gst_element_link (priv->funnel, output);
1655 gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1656 gst_element_set_state (output, GST_STATE_PLAYING);
1659 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1664 /* Called with global lock held */
1666 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1668 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1671 if (priv->liveadder == NULL)
1673 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1675 gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1676 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1678 gst_element_link (priv->liveadder, priv->audio_output);
1680 gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1681 gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1684 pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1690 empathy_call_window_update_timer (gpointer user_data)
1692 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1693 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1697 time_ = g_timer_elapsed (priv->timer, NULL);
1699 /* Translators: number of minutes:seconds the caller has been connected */
1700 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time_ / 60,
1702 empathy_call_window_status_message (self, str);
1709 display_error (EmpathyCallWindow *self,
1710 EmpathyTpCall *call,
1714 const gchar *details)
1716 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1717 GtkWidget *info_bar;
1718 GtkWidget *content_area;
1725 /* Create info bar */
1726 info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1729 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1731 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1733 /* hbox containing the image and the messages vbox */
1734 hbox = gtk_hbox_new (FALSE, 3);
1735 gtk_container_add (GTK_CONTAINER (content_area), hbox);
1738 image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1739 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1741 /* vbox containing the main message and the details expander */
1742 vbox = gtk_vbox_new (FALSE, 3);
1743 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1746 txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
1748 label = gtk_label_new (NULL);
1749 gtk_label_set_markup (GTK_LABEL (label), txt);
1750 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1751 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1754 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1757 if (details != NULL)
1759 GtkWidget *expander;
1761 expander = gtk_expander_new (_("Technical Details"));
1763 txt = g_strdup_printf ("<i>%s</i>", details);
1765 label = gtk_label_new (NULL);
1766 gtk_label_set_markup (GTK_LABEL (label), txt);
1767 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1768 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1771 gtk_container_add (GTK_CONTAINER (expander), label);
1772 gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1775 g_signal_connect (info_bar, "response",
1776 G_CALLBACK (gtk_widget_destroy), NULL);
1778 gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1779 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1780 gtk_widget_show_all (info_bar);
1784 media_stream_error_to_txt (EmpathyCallWindow *self,
1785 EmpathyTpCall *call,
1787 TpMediaStreamError error)
1789 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1796 case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1798 return g_strdup_printf (
1799 _("%s's software does not understand any of the audio formats "
1800 "supported by your computer"),
1801 empathy_contact_get_name (priv->contact));
1803 return g_strdup_printf (
1804 _("%s's software does not understand any of the video formats "
1805 "supported by your computer"),
1806 empathy_contact_get_name (priv->contact));
1808 case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1809 return g_strdup_printf (
1810 _("Can't establish a connection to %s. "
1811 "One of you might be on a network that does not allow "
1812 "direct connections."),
1813 empathy_contact_get_name (priv->contact));
1815 case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
1816 return g_strdup (_("There was a failure on the network"));
1818 case TP_MEDIA_STREAM_ERROR_NO_CODECS:
1820 return g_strdup (_("The audio formats necessary for this call "
1821 "are not installed on your computer"));
1823 return g_strdup (_("The video formats necessary for this call "
1824 "are not installed on your computer"));
1826 case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
1827 cm = empathy_tp_call_get_connection_manager (call);
1829 url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
1830 "product=Telepathy&component=%s", cm);
1832 result = g_strdup_printf (
1833 _("Something not expected happened in a Telepathy component. "
1834 "Please <a href=\"%s\">report this bug</a> and attach "
1835 "logs gathered from the 'Debug' window in the Help menu."), url);
1840 case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
1841 return g_strdup (_("There was a failure in the call engine"));
1849 empathy_call_window_stream_error (EmpathyCallWindow *self,
1850 EmpathyTpCall *call,
1859 desc = media_stream_error_to_txt (self, call, audio, code);
1862 /* No description, use the error message. That's not great as it's not
1863 * localized but it's better than nothing. */
1864 display_error (self, call, icon, title, msg, NULL);
1868 display_error (self, call, icon, title, desc, msg);
1874 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
1877 EmpathyCallWindow *self)
1879 empathy_call_window_stream_error (self, call, TRUE, code, msg,
1880 "gnome-stock-mic", _("Can't establish audio stream"));
1884 empathy_call_window_video_stream_error (EmpathyTpCall *call,
1887 EmpathyCallWindow *self)
1889 empathy_call_window_stream_error (self, call, FALSE, code, msg,
1890 "camera-web", _("Can't establish video stream"));
1894 empathy_call_window_connected (gpointer user_data)
1896 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1897 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1898 EmpathyTpCall *call;
1899 gboolean can_send_video;
1901 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1903 can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1904 empathy_contact_can_voip_video (priv->contact);
1906 g_object_get (priv->handler, "tp-call", &call, NULL);
1908 g_signal_connect (call, "notify::video-stream",
1909 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1911 if (empathy_tp_call_has_dtmf (call))
1912 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1914 if (priv->video_input == NULL)
1915 empathy_call_window_set_send_video (self, FALSE);
1917 priv->sending_video = can_send_video ?
1918 empathy_tp_call_is_sending_video (call) : FALSE;
1920 gtk_toggle_tool_button_set_active (
1921 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
1922 priv->sending_video && priv->video_input != NULL);
1923 gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
1925 gtk_action_set_sensitive (priv->redial, FALSE);
1926 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1928 gtk_widget_set_sensitive (priv->mic_button, TRUE);
1930 /* FIXME: this should won't be needed once bug #602937 is fixed
1931 * (see empathy_call_window_disconnected for details) */
1932 gtk_widget_set_sensitive (priv->tool_button_camera_preview, TRUE);
1933 gtk_action_set_sensitive (priv->action_camera_preview, TRUE);
1935 empathy_call_window_update_avatars_visibility (call, self);
1937 g_object_unref (call);
1939 g_mutex_lock (priv->lock);
1941 priv->timer_id = g_timeout_add_seconds (1,
1942 empathy_call_window_update_timer, self);
1944 g_mutex_unlock (priv->lock);
1946 empathy_call_window_update_timer (self);
1952 /* Called from the streaming thread */
1954 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1955 GstPad *src, guint media_type, gpointer user_data)
1957 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1958 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1962 g_mutex_lock (priv->lock);
1964 if (priv->call_state != CONNECTED)
1966 g_timer_start (priv->timer);
1967 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
1968 priv->call_state = CONNECTED;
1973 case TP_MEDIA_STREAM_TYPE_AUDIO:
1974 pad = empathy_call_window_get_audio_sink_pad (self);
1976 case TP_MEDIA_STREAM_TYPE_VIDEO:
1977 gtk_widget_hide (priv->remote_user_avatar_widget);
1978 gtk_widget_show (priv->video_output);
1979 pad = empathy_call_window_get_video_sink_pad (self);
1982 g_assert_not_reached ();
1985 gst_pad_link (src, pad);
1986 gst_object_unref (pad);
1988 g_mutex_unlock (priv->lock);
1991 /* Called from the streaming thread */
1993 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1994 GstPad *sink, guint media_type, gpointer user_data)
1996 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1997 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2002 case TP_MEDIA_STREAM_TYPE_AUDIO:
2003 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
2005 pad = gst_element_get_static_pad (priv->audio_input, "src");
2006 gst_pad_link (pad, sink);
2008 gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
2010 case TP_MEDIA_STREAM_TYPE_VIDEO:
2011 if (priv->video_input != NULL)
2013 if (priv->video_tee != NULL)
2015 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2016 gst_pad_link (pad, sink);
2021 g_assert_not_reached ();
2027 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2029 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2030 GstElement *preview;
2032 DEBUG ("remove video input");
2033 preview = empathy_video_widget_get_element (
2034 EMPATHY_VIDEO_WIDGET (priv->video_preview));
2036 gst_element_set_state (priv->video_input, GST_STATE_NULL);
2037 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2038 gst_element_set_state (preview, GST_STATE_NULL);
2040 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2041 priv->video_tee, preview, NULL);
2043 g_object_unref (priv->video_input);
2044 priv->video_input = NULL;
2045 g_object_unref (priv->video_tee);
2046 priv->video_tee = NULL;
2047 gtk_widget_destroy (priv->video_preview);
2048 priv->video_preview = NULL;
2050 gtk_toggle_tool_button_set_active (
2051 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
2052 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
2054 gtk_widget_show (priv->self_user_avatar_widget);
2059 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2062 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2063 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2066 empathy_call_handler_bus_message (priv->handler, bus, message);
2068 switch (GST_MESSAGE_TYPE (message))
2070 case GST_MESSAGE_STATE_CHANGED:
2071 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2073 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2074 if (newstate == GST_STATE_PAUSED)
2075 empathy_call_window_setup_video_input (self);
2077 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2078 !priv->call_started)
2080 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2081 if (newstate == GST_STATE_PAUSED)
2083 priv->call_started = TRUE;
2084 empathy_call_handler_start_call (priv->handler);
2085 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2089 case GST_MESSAGE_ERROR:
2091 GError *error = NULL;
2092 GstElement *gst_error;
2095 gst_message_parse_error (message, &error, &debug);
2096 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2098 g_message ("Element error: %s -- %s\n", error->message, debug);
2100 if (g_str_has_prefix (gst_element_get_name (gst_error),
2101 VIDEO_INPUT_ERROR_PREFIX))
2103 /* Remove the video input and continue */
2104 if (priv->video_input != NULL)
2105 empathy_call_window_remove_video_input (self);
2106 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2110 empathy_call_window_disconnected (self);
2112 g_error_free (error);
2123 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
2124 EmpathyCallWindow *window)
2126 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2128 if (empathy_tp_call_is_receiving_video (call))
2130 gtk_widget_hide (priv->remote_user_avatar_widget);
2131 gtk_widget_show (priv->video_output);
2135 gtk_widget_hide (priv->video_output);
2136 gtk_widget_show (priv->remote_user_avatar_widget);
2141 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
2143 EmpathyCallWindow *self)
2145 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2146 EmpathyTpCall *call;
2148 g_object_get (priv->handler, "tp-call", &call, NULL);
2152 empathy_signal_connect_weak (call, "audio-stream-error",
2153 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
2154 empathy_signal_connect_weak (call, "video-stream-error",
2155 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
2157 g_object_unref (call);
2161 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2163 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2164 EmpathyTpCall *call;
2166 g_signal_connect (priv->handler, "conference-added",
2167 G_CALLBACK (empathy_call_window_conference_added_cb), window);
2168 g_signal_connect (priv->handler, "request-resource",
2169 G_CALLBACK (empathy_call_window_request_resource_cb), window);
2170 g_signal_connect (priv->handler, "closed",
2171 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2172 g_signal_connect (priv->handler, "src-pad-added",
2173 G_CALLBACK (empathy_call_window_src_added_cb), window);
2174 g_signal_connect (priv->handler, "sink-pad-added",
2175 G_CALLBACK (empathy_call_window_sink_added_cb), window);
2176 g_signal_connect (priv->handler, "stream-closed",
2177 G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
2179 g_object_get (priv->handler, "tp-call", &call, NULL);
2182 empathy_signal_connect_weak (call, "audio-stream-error",
2183 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
2184 empathy_signal_connect_weak (call, "video-stream-error",
2185 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
2187 g_object_unref (call);
2191 /* tp-call doesn't exist yet, we'll connect signals once it has been
2193 g_signal_connect (priv->handler, "notify::tp-call",
2194 G_CALLBACK (call_handler_notify_tp_call_cb), window);
2197 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2201 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2202 EmpathyCallWindow *window)
2204 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2206 if (priv->pipeline != NULL)
2208 if (priv->bus_message_source_id != 0)
2210 g_source_remove (priv->bus_message_source_id);
2211 priv->bus_message_source_id = 0;
2214 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2217 if (priv->call_state == CONNECTING)
2218 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2224 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2227 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2229 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2234 gtk_widget_hide (priv->sidebar);
2235 gtk_widget_hide (menu);
2236 gtk_widget_hide (priv->vbox);
2237 gtk_widget_hide (priv->statusbar);
2238 gtk_widget_hide (priv->toolbar);
2242 if (priv->sidebar_was_visible_before_fs)
2243 gtk_widget_show (priv->sidebar);
2245 gtk_widget_show (menu);
2246 gtk_widget_show (priv->vbox);
2247 gtk_widget_show (priv->statusbar);
2248 gtk_widget_show (priv->toolbar);
2250 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2251 priv->original_height_before_fs);
2256 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2258 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2260 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2261 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2262 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2263 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2264 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2265 priv->video_output, TRUE, TRUE,
2266 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2268 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2269 priv->vbox, TRUE, TRUE,
2270 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2275 empathy_call_window_state_event_cb (GtkWidget *widget,
2276 GdkEventWindowState *event, EmpathyCallWindow *window)
2278 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2280 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2281 gboolean set_fullscreen = event->new_window_state &
2282 GDK_WINDOW_STATE_FULLSCREEN;
2286 gboolean sidebar_was_visible;
2287 GtkAllocation allocation;
2288 gint original_width, original_height;
2290 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2291 original_width = allocation.width;
2292 original_height = allocation.height;
2294 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2296 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2297 priv->original_width_before_fs = original_width;
2298 priv->original_height_before_fs = original_height;
2300 if (priv->video_output_motion_handler_id == 0 &&
2301 priv->video_output != NULL)
2303 priv->video_output_motion_handler_id = g_signal_connect (
2304 G_OBJECT (priv->video_output), "motion-notify-event",
2305 G_CALLBACK (empathy_call_window_video_output_motion_notify),
2311 if (priv->video_output_motion_handler_id != 0)
2313 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2314 priv->video_output_motion_handler_id);
2315 priv->video_output_motion_handler_id = 0;
2319 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2321 show_controls (window, set_fullscreen);
2322 show_borders (window, set_fullscreen);
2323 gtk_action_set_stock_id (priv->menu_fullscreen,
2324 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2325 priv->is_fullscreen = set_fullscreen;
2332 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2333 EmpathyCallWindow *window)
2335 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2337 int w, h, handle_size;
2338 GtkAllocation allocation, sidebar_allocation;
2340 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2341 w = allocation.width;
2342 h = allocation.height;
2344 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2346 gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2347 if (gtk_toggle_button_get_active (toggle))
2349 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2350 gtk_widget_show (priv->sidebar);
2351 w += sidebar_allocation.width + handle_size;
2355 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2356 w -= sidebar_allocation.width + handle_size;
2357 gtk_widget_hide (priv->sidebar);
2360 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2363 gtk_window_resize (GTK_WINDOW (window), w, h);
2367 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2370 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2371 EmpathyTpCall *call;
2373 priv->sending_video = send;
2375 /* When we start sending video, we want to show the video preview by
2377 display_video_preview (window, send);
2379 if (priv->call_state != CONNECTED)
2382 g_object_get (priv->handler, "tp-call", &call, NULL);
2383 DEBUG ("%s sending video", send ? "start": "stop");
2384 empathy_tp_call_request_video_stream_direction (call, send);
2385 g_object_unref (call);
2389 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2390 EmpathyCallWindow *window)
2392 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2395 if (priv->audio_input == NULL)
2398 active = (gtk_toggle_tool_button_get_active (toggle));
2402 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2404 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2408 /* TODO, Instead of setting the input volume to 0 we should probably
2409 * stop sending but this would cause the audio call to drop if both
2410 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2411 * in the future. GNOME #574574
2413 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2415 gtk_adjustment_set_value (priv->audio_input_adj, 0);
2420 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2421 EmpathyCallWindow *window)
2423 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2425 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2430 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2431 EmpathyCallWindow *window)
2433 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2435 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2440 empathy_call_window_hangup_cb (gpointer object,
2441 EmpathyCallWindow *window)
2443 if (empathy_call_window_disconnected (window))
2444 gtk_widget_destroy (GTK_WIDGET (window));
2448 empathy_call_window_restart_call (EmpathyCallWindow *window)
2451 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2453 gtk_widget_destroy (priv->remote_user_output_hbox);
2454 gtk_widget_destroy (priv->self_user_output_hbox);
2456 priv->pipeline = gst_pipeline_new (NULL);
2457 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2458 priv->bus_message_source_id = gst_bus_add_watch (bus,
2459 empathy_call_window_bus_message, window);
2461 empathy_call_window_setup_remote_frame (bus, window);
2462 empathy_call_window_setup_self_frame (bus, window);
2464 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2465 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2467 /* While the call was disconnected, the input volume might have changed.
2468 * However, since the audio_input source was destroyed, its volume has not
2469 * been updated during that time. That's why we manually update it here */
2470 empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2472 g_object_unref (bus);
2474 gtk_widget_show_all (priv->content_hbox);
2476 priv->outgoing = TRUE;
2477 empathy_call_window_set_state_connecting (window);
2479 priv->call_started = TRUE;
2480 empathy_call_handler_start_call (priv->handler);
2481 empathy_call_window_setup_avatars (window, priv->handler);
2482 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2484 gtk_action_set_sensitive (priv->redial, FALSE);
2485 gtk_widget_set_sensitive (priv->redial_button, FALSE);
2489 empathy_call_window_redial_cb (gpointer object,
2490 EmpathyCallWindow *window)
2492 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2494 if (priv->call_state == CONNECTED)
2495 priv->call_state = REDIALING;
2497 empathy_call_handler_stop_call (priv->handler);
2499 if (priv->call_state != CONNECTED)
2500 empathy_call_window_restart_call (window);
2504 empathy_call_window_fullscreen_cb (gpointer object,
2505 EmpathyCallWindow *window)
2507 empathy_call_window_fullscreen_toggle (window);
2511 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2513 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2515 if (priv->is_fullscreen)
2516 gtk_window_unfullscreen (GTK_WINDOW (window));
2518 gtk_window_fullscreen (GTK_WINDOW (window));
2522 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2523 GdkEventButton *event, EmpathyCallWindow *window)
2525 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2527 empathy_call_window_video_menu_popup (window, event->button);
2535 empathy_call_window_key_press_cb (GtkWidget *video_output,
2536 GdkEventKey *event, EmpathyCallWindow *window)
2538 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2540 if (priv->is_fullscreen && event->keyval == GDK_Escape)
2542 /* Since we are in fullscreen mode, toggling will bring us back to
2544 empathy_call_window_fullscreen_toggle (window);
2552 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2553 GdkEventMotion *event, EmpathyCallWindow *window)
2555 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2557 if (priv->is_fullscreen)
2559 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2566 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2570 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2572 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2574 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2575 button, gtk_get_current_event_time ());
2576 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2580 empathy_call_window_status_message (EmpathyCallWindow *window,
2583 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2585 if (priv->context_id == 0)
2587 priv->context_id = gtk_statusbar_get_context_id (
2588 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2592 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2595 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2600 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2601 gdouble value, EmpathyCallWindow *window)
2603 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2605 if (priv->audio_output == NULL)
2608 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2612 /* block all the signals related to camera control widgets. This is useful
2613 * when we are manually updating the UI and so don't want to fire the
2616 block_camera_control_signals (EmpathyCallWindow *self)
2618 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2620 g_signal_handlers_block_by_func (priv->tool_button_camera_off,
2621 tool_button_camera_off_toggled_cb, self);
2622 g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
2623 tool_button_camera_preview_toggled_cb, self);
2624 g_signal_handlers_block_by_func (priv->tool_button_camera_on,
2625 tool_button_camera_on_toggled_cb, self);
2626 g_signal_handlers_block_by_func (priv->action_camera,
2627 action_camera_change_cb, self);
2631 unblock_camera_control_signals (EmpathyCallWindow *self)
2633 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2635 g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
2636 tool_button_camera_off_toggled_cb, self);
2637 g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
2638 tool_button_camera_preview_toggled_cb, self);
2639 g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
2640 tool_button_camera_on_toggled_cb, self);
2641 g_signal_handlers_unblock_by_func (priv->action_camera,
2642 action_camera_change_cb, self);