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>
47 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
48 #include <libempathy/empathy-debug.h>
50 #include "empathy-call-window.h"
51 #include "empathy-call-window-fullscreen.h"
52 #include "empathy-sidebar.h"
54 #define BUTTON_ID "empathy-call-dtmf-button-id"
56 #define CONTENT_HBOX_BORDER_WIDTH 6
57 #define CONTENT_HBOX_SPACING 3
58 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
60 #define SELF_VIDEO_SECTION_WIDTH 160
61 #define SELF_VIDEO_SECTION_HEIGTH 120
63 /* The avatar's default width and height are set to the same value because we
64 want a square icon. */
65 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
66 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
67 EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
69 /* If an video input error occurs, the error message will start with "v4l" */
70 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
72 /* The time interval in milliseconds between 2 outgoing rings */
73 #define MS_BETWEEN_RING 500
75 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
84 static guint signals[LAST_SIGNAL] = {0};
88 PROP_CALL_HANDLER = 1,
100 CAMERA_STATE_PREVIEW,
104 /* private structure */
105 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
107 struct _EmpathyCallWindowPriv
109 gboolean dispose_has_run;
110 EmpathyCallHandler *handler;
111 EmpathyContact *contact;
116 GtkUIManager *ui_manager;
117 GtkWidget *errors_vbox;
118 GtkWidget *video_output;
119 GtkWidget *video_preview;
120 GtkWidget *remote_user_avatar_widget;
121 GtkWidget *self_user_avatar_widget;
123 GtkWidget *sidebar_button;
124 GtkWidget *statusbar;
125 GtkWidget *volume_button;
126 GtkWidget *redial_button;
127 GtkWidget *mic_button;
130 GtkAction *send_video;
132 GtkAction *menu_fullscreen;
133 GtkWidget *tool_button_camera_off;
134 GtkWidget *tool_button_camera_preview;
135 GtkWidget *tool_button_camera_on;
137 /* The frames and boxes that contain self and remote avatar and video
138 input/output. When we redial, we destroy and re-create the boxes */
139 GtkWidget *remote_user_output_frame;
140 GtkWidget *self_user_output_frame;
141 GtkWidget *remote_user_output_hbox;
142 GtkWidget *self_user_output_hbox;
144 /* We keep a reference on the hbox which contains the main content so we can
145 easilly repack everything when toggling fullscreen */
146 GtkWidget *content_hbox;
148 /* This vbox is contained in the content_hbox and it contains the
149 self_user_output_frame and the sidebar button. When toggling fullscreen,
150 it needs to be repacked. We keep a reference on it for easier access. */
153 gulong video_output_motion_handler_id;
154 guint bus_message_source_id;
157 GtkWidget *volume_scale;
158 GtkWidget *volume_progress_bar;
159 GtkAdjustment *audio_input_adj;
161 GtkWidget *dtmf_panel;
163 GstElement *video_input;
164 GstElement *audio_input;
165 GstElement *audio_output;
166 GstElement *pipeline;
167 GstElement *video_tee;
170 GstElement *liveadder;
172 FsElementAddedNotifier *fsnotifier;
179 GtkWidget *video_contrast;
180 GtkWidget *video_brightness;
181 GtkWidget *video_gamma;
184 gboolean call_started;
185 gboolean sending_video;
186 CameraState camera_state;
188 EmpathyCallWindowFullscreen *fullscreen;
189 gboolean is_fullscreen;
191 /* Those fields represent the state of the window before it actually was in
193 gboolean sidebar_was_visible_before_fs;
194 gint original_width_before_fs;
195 gint original_height_before_fs;
198 #define GET_PRIV(o) \
199 (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW, \
200 EmpathyCallWindowPriv))
202 static void empathy_call_window_realized_cb (GtkWidget *widget,
203 EmpathyCallWindow *window);
205 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
206 GdkEvent *event, EmpathyCallWindow *window);
208 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
209 GdkEventWindowState *event, EmpathyCallWindow *window);
211 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
212 EmpathyCallWindow *window);
214 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
217 static void empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
218 EmpathyCallWindow *window);
220 static void empathy_call_window_mic_toggled_cb (
221 GtkToggleToolButton *toggle, EmpathyCallWindow *window);
223 static void empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
224 EmpathyCallWindow *window);
226 static void empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
227 EmpathyCallWindow *window);
229 static void empathy_call_window_hangup_cb (gpointer object,
230 EmpathyCallWindow *window);
232 static void empathy_call_window_fullscreen_cb (gpointer object,
233 EmpathyCallWindow *window);
235 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
237 static gboolean empathy_call_window_video_button_press_cb (
238 GtkWidget *video_output, GdkEventButton *event, EmpathyCallWindow *window);
240 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
241 GdkEventKey *event, EmpathyCallWindow *window);
243 static gboolean empathy_call_window_video_output_motion_notify (
244 GtkWidget *widget, GdkEventMotion *event, EmpathyCallWindow *window);
246 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
249 static void empathy_call_window_redial_cb (gpointer object,
250 EmpathyCallWindow *window);
252 static void empathy_call_window_restart_call (EmpathyCallWindow *window);
254 static void empathy_call_window_status_message (EmpathyCallWindow *window,
257 static void empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
258 EmpathyCallWindow *window);
260 static gboolean empathy_call_window_bus_message (GstBus *bus,
261 GstMessage *message, gpointer user_data);
264 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
265 gdouble value, EmpathyCallWindow *window);
267 static void block_camera_control_signals (EmpathyCallWindow *self);
268 static void unblock_camera_control_signals (EmpathyCallWindow *self);
271 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
273 EmpathyCallWindowPriv *priv = GET_PRIV (self);
274 GtkToolItem *tool_item;
275 GtkWidget *camera_off_icon;
276 GdkPixbuf *pixbuf, *modded_pixbuf;
278 /* set the icon of the 'camera off' button by greying off the webcam icon */
279 pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
280 GTK_ICON_SIZE_SMALL_TOOLBAR);
282 modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
283 gdk_pixbuf_get_width (pixbuf),
284 gdk_pixbuf_get_height (pixbuf));
286 gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
287 g_object_unref (pixbuf);
289 camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
290 g_object_unref (modded_pixbuf);
291 gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
292 priv->tool_button_camera_off), camera_off_icon);
294 /* Add an empty expanded GtkToolItem so the volume button is at the end of
296 tool_item = gtk_tool_item_new ();
297 gtk_tool_item_set_expand (tool_item, TRUE);
298 gtk_widget_show (GTK_WIDGET (tool_item));
299 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
301 priv->volume_button = gtk_volume_button_new ();
302 /* FIXME listen to the audiosinks signals and update the button according to
303 * that, for now starting out at 1.0 and assuming only the app changes the
305 gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
306 g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
307 G_CALLBACK (empathy_call_window_volume_changed_cb), self);
309 tool_item = gtk_tool_item_new ();
310 gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
311 gtk_widget_show_all (GTK_WIDGET (tool_item));
312 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
316 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
318 EmpathyCallWindowPriv *priv = GET_PRIV (window);
323 g_object_get (priv->handler, "tp-call", &call, NULL);
325 button_quark = g_quark_from_static_string (BUTTON_ID);
326 event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
329 empathy_tp_call_start_tone (call, event);
331 g_object_unref (call);
335 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
337 EmpathyCallWindowPriv *priv = GET_PRIV (window);
340 g_object_get (priv->handler, "tp-call", &call, NULL);
342 empathy_tp_call_stop_tone (call);
344 g_object_unref (call);
348 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
356 } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
357 { "2", TP_DTMF_EVENT_DIGIT_2 },
358 { "3", TP_DTMF_EVENT_DIGIT_3 },
359 { "4", TP_DTMF_EVENT_DIGIT_4 },
360 { "5", TP_DTMF_EVENT_DIGIT_5 },
361 { "6", TP_DTMF_EVENT_DIGIT_6 },
362 { "7", TP_DTMF_EVENT_DIGIT_7 },
363 { "8", TP_DTMF_EVENT_DIGIT_8 },
364 { "9", TP_DTMF_EVENT_DIGIT_9 },
365 { "#", TP_DTMF_EVENT_HASH },
366 { "0", TP_DTMF_EVENT_DIGIT_0 },
367 { "*", TP_DTMF_EVENT_ASTERISK },
370 button_quark = g_quark_from_static_string (BUTTON_ID);
372 table = gtk_table_new (4, 3, TRUE);
374 for (i = 0; dtmfbuttons[i].label != NULL; i++)
376 GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
377 gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
378 i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
380 g_object_set_qdata (G_OBJECT (button), button_quark,
381 GUINT_TO_POINTER (dtmfbuttons[i].event));
383 g_signal_connect (G_OBJECT (button), "pressed",
384 G_CALLBACK (dtmf_button_pressed_cb), self);
385 g_signal_connect (G_OBJECT (button), "released",
386 G_CALLBACK (dtmf_button_released_cb), self);
393 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
394 gchar *label_text, GtkWidget *bin)
396 GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
397 GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
398 GtkWidget *label = gtk_label_new (label_text);
400 gtk_widget_set_sensitive (scale, FALSE);
402 gtk_container_add (GTK_CONTAINER (bin), vbox);
404 gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
405 gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
406 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
412 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
413 EmpathyCallWindow *self)
416 EmpathyCallWindowPriv *priv = GET_PRIV (self);
418 empathy_video_src_set_channel (priv->video_input,
419 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
423 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
424 EmpathyCallWindow *self)
427 EmpathyCallWindowPriv *priv = GET_PRIV (self);
429 empathy_video_src_set_channel (priv->video_input,
430 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
434 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
435 EmpathyCallWindow *self)
438 EmpathyCallWindowPriv *priv = GET_PRIV (self);
440 empathy_video_src_set_channel (priv->video_input,
441 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
446 empathy_call_window_create_video_input (EmpathyCallWindow *self)
448 EmpathyCallWindowPriv *priv = GET_PRIV (self);
451 hbox = gtk_hbox_new (TRUE, 3);
453 priv->video_contrast = empathy_call_window_create_video_input_add_slider (
454 self, _("Contrast"), hbox);
456 priv->video_brightness = empathy_call_window_create_video_input_add_slider (
457 self, _("Brightness"), hbox);
459 priv->video_gamma = empathy_call_window_create_video_input_add_slider (
460 self, _("Gamma"), hbox);
466 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
468 EmpathyCallWindowPriv *priv = GET_PRIV (self);
472 supported = empathy_video_src_get_supported_channels (priv->video_input);
474 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
476 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
478 gtk_adjustment_set_value (adj,
479 empathy_video_src_get_channel (priv->video_input,
480 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
482 g_signal_connect (G_OBJECT (adj), "value-changed",
483 G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
485 gtk_widget_set_sensitive (priv->video_contrast, TRUE);
488 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
490 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
492 gtk_adjustment_set_value (adj,
493 empathy_video_src_get_channel (priv->video_input,
494 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
496 g_signal_connect (G_OBJECT (adj), "value-changed",
497 G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
498 gtk_widget_set_sensitive (priv->video_brightness, TRUE);
501 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
503 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
505 gtk_adjustment_set_value (adj,
506 empathy_video_src_get_channel (priv->video_input,
507 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
509 g_signal_connect (G_OBJECT (adj), "value-changed",
510 G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
511 gtk_widget_set_sensitive (priv->video_gamma, TRUE);
516 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
517 EmpathyCallWindow *self)
519 EmpathyCallWindowPriv *priv = GET_PRIV (self);
522 if (priv->audio_input == NULL)
525 volume = gtk_adjustment_get_value (adj)/100.0;
527 /* Don't store the volume because of muting */
528 if (volume > 0 || gtk_toggle_tool_button_get_active (
529 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
530 priv->volume = volume;
532 /* Ensure that the toggle button is active if the volume is > 0 and inactive
533 * if it's smaller than 0 */
534 if ((volume > 0) != gtk_toggle_tool_button_get_active (
535 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
536 gtk_toggle_tool_button_set_active (
537 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
539 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
544 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
545 gdouble level, EmpathyCallWindow *window)
548 EmpathyCallWindowPriv *priv = GET_PRIV (window);
550 value = CLAMP (pow (10, level / 20), 0.0, 1.0);
551 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
556 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
558 EmpathyCallWindowPriv *priv = GET_PRIV (self);
559 GtkWidget *hbox, *vbox, *label;
561 hbox = gtk_hbox_new (TRUE, 3);
563 vbox = gtk_vbox_new (FALSE, 3);
564 gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
566 priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
567 gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
568 label = gtk_label_new (_("Volume"));
570 priv->audio_input_adj = gtk_range_get_adjustment (
571 GTK_RANGE (priv->volume_scale));
572 priv->volume = empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
573 (priv->audio_input));
574 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
576 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
577 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
579 gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
580 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
582 priv->volume_progress_bar = gtk_progress_bar_new ();
583 gtk_progress_bar_set_orientation (
584 GTK_PROGRESS_BAR (priv->volume_progress_bar),
585 GTK_PROGRESS_BOTTOM_TO_TOP);
586 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
589 gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
596 empathy_call_window_setup_remote_frame (GstBus *bus, EmpathyCallWindow *self)
598 EmpathyCallWindowPriv *priv = GET_PRIV (self);
600 /* Initializing all the content (UI and output gst elements) related to the
602 priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
604 priv->remote_user_avatar_widget = gtk_image_new ();
605 gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
606 priv->remote_user_avatar_widget, TRUE, TRUE, 0);
608 priv->video_output = empathy_video_widget_new (bus);
609 gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
610 priv->video_output, TRUE, TRUE, 0);
612 gtk_widget_add_events (priv->video_output,
613 GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
614 g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
615 G_CALLBACK (empathy_call_window_video_button_press_cb), self);
617 gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
618 priv->remote_user_output_hbox);
620 priv->audio_output = empathy_audio_sink_new ();
621 gst_object_ref (priv->audio_output);
622 gst_object_sink (priv->audio_output);
626 empathy_call_window_setup_self_frame (GstBus *bus, EmpathyCallWindow *self)
628 EmpathyCallWindowPriv *priv = GET_PRIV (self);
630 /* Initializing all the content (UI and input gst elements) related to the
631 self contact, except for the video preview widget. This widget is only
632 initialized when the "show video preview" option is activated */
633 priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
635 priv->self_user_avatar_widget = gtk_image_new ();
636 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
637 priv->self_user_avatar_widget, TRUE, TRUE, 0);
639 gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
640 priv->self_user_output_hbox);
642 priv->video_input = empathy_video_src_new ();
643 gst_object_ref (priv->video_input);
644 gst_object_sink (priv->video_input);
646 priv->audio_input = empathy_audio_src_new ();
647 gst_object_ref (priv->audio_input);
648 gst_object_sink (priv->audio_input);
650 empathy_signal_connect_weak (priv->audio_input, "peak-level-changed",
651 G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
656 empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
658 EmpathyCallWindowPriv *priv = GET_PRIV (window);
660 GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
662 if (priv->video_preview != NULL)
664 /* Since the video preview and the video tee are initialized and freed
665 at the same time, if one is initialized, then the other one should
667 g_assert (priv->video_tee != NULL);
671 DEBUG ("Create video preview");
672 g_assert (priv->video_tee == NULL);
674 priv->video_tee = gst_element_factory_make ("tee", NULL);
675 gst_object_ref (priv->video_tee);
676 gst_object_sink (priv->video_tee);
678 priv->video_preview = empathy_video_widget_new_with_size (bus,
679 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
680 g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
681 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
682 priv->video_preview, TRUE, TRUE, 0);
684 preview = empathy_video_widget_get_element (
685 EMPATHY_VIDEO_WIDGET (priv->video_preview));
686 gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
687 priv->video_tee, preview, NULL);
688 gst_element_link_many (priv->video_input, priv->video_tee,
691 g_object_unref (bus);
693 gst_element_set_state (preview, GST_STATE_PLAYING);
694 gst_element_set_state (priv->video_input, GST_STATE_PLAYING);
695 gst_element_set_state (priv->video_tee, GST_STATE_PLAYING);
699 display_video_preview (EmpathyCallWindow *self,
702 EmpathyCallWindowPriv *priv = GET_PRIV (self);
706 /* Display the preview and hide the self avatar */
707 DEBUG ("Show video preview");
709 if (priv->video_preview == NULL)
710 empathy_call_window_setup_video_preview (self);
711 gtk_widget_show (priv->video_preview);
712 gtk_widget_hide (priv->self_user_avatar_widget);
716 /* Display the self avatar and hide the preview */
717 DEBUG ("Show self avatar");
719 if (priv->video_preview != NULL)
720 gtk_widget_hide (priv->video_preview);
721 gtk_widget_show (priv->self_user_avatar_widget);
726 empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
728 EmpathyCallWindowPriv *priv = GET_PRIV (window);
730 empathy_call_window_status_message (window, _("Connecting..."));
731 priv->call_state = CONNECTING;
734 empathy_sound_start_playing (GTK_WIDGET (window),
735 EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
739 disable_camera (EmpathyCallWindow *self)
741 EmpathyCallWindowPriv *priv = GET_PRIV (self);
743 priv->camera_state = CAMERA_STATE_OFF;
744 display_video_preview (self, FALSE);
746 block_camera_control_signals (self);
747 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
748 priv->tool_button_camera_on), FALSE);
749 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
750 priv->tool_button_camera_preview), FALSE);
751 unblock_camera_control_signals (self);
755 tool_button_camera_off_toggled_cb (GtkToggleToolButton *toggle,
756 EmpathyCallWindow *self)
758 EmpathyCallWindowPriv *priv = GET_PRIV (self);
760 if (!gtk_toggle_tool_button_get_active (toggle))
762 if (priv->camera_state == CAMERA_STATE_OFF)
764 /* We can't change the state by disabling the button */
765 block_camera_control_signals (self);
766 gtk_toggle_tool_button_set_active (toggle, TRUE);
767 unblock_camera_control_signals (self);
773 DEBUG ("disable camera");
774 disable_camera (self);
778 enable_preview (EmpathyCallWindow *self)
780 EmpathyCallWindowPriv *priv = GET_PRIV (self);
782 priv->camera_state = CAMERA_STATE_PREVIEW;
783 display_video_preview (self, TRUE);
785 block_camera_control_signals (self);
786 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
787 priv->tool_button_camera_off), FALSE);
788 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
789 priv->tool_button_camera_on), FALSE);
790 unblock_camera_control_signals (self);
794 tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
795 EmpathyCallWindow *self)
797 EmpathyCallWindowPriv *priv = GET_PRIV (self);
799 if (!gtk_toggle_tool_button_get_active (toggle))
801 if (priv->camera_state == CAMERA_STATE_PREVIEW)
803 /* We can't change the state by disabling the button */
804 block_camera_control_signals (self);
805 gtk_toggle_tool_button_set_active (toggle, TRUE);
806 unblock_camera_control_signals (self);
812 DEBUG ("enable preview");
813 enable_preview (self);
817 enable_camera (EmpathyCallWindow *self)
819 EmpathyCallWindowPriv *priv = GET_PRIV (self);
821 priv->camera_state = CAMERA_STATE_ON;
822 empathy_call_window_set_send_video (self, TRUE);
824 block_camera_control_signals (self);
825 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
826 priv->tool_button_camera_off), FALSE);
827 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
828 priv->tool_button_camera_preview), FALSE);
829 unblock_camera_control_signals (self);
833 tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
834 EmpathyCallWindow *self)
836 EmpathyCallWindowPriv *priv = GET_PRIV (self);
838 if (!gtk_toggle_tool_button_get_active (toggle))
840 if (priv->camera_state == CAMERA_STATE_ON)
842 /* We can't change the state by disabling the button */
843 block_camera_control_signals (self);
844 gtk_toggle_tool_button_set_active (toggle, TRUE);
845 unblock_camera_control_signals (self);
851 DEBUG ("enable camera");
852 enable_camera (self);
856 empathy_call_window_init (EmpathyCallWindow *self)
858 EmpathyCallWindowPriv *priv = GET_PRIV (self);
867 GError *error = NULL;
869 filename = empathy_file_lookup ("empathy-call-window.ui", "src");
870 gui = empathy_builder_get_file (filename,
871 "call_window_vbox", &top_vbox,
872 "errors_vbox", &priv->errors_vbox,
874 "statusbar", &priv->statusbar,
875 "redial", &priv->redial_button,
876 "microphone", &priv->mic_button,
877 "toolbar", &priv->toolbar,
878 "send_video", &priv->send_video,
879 "menuredial", &priv->redial,
880 "ui_manager", &priv->ui_manager,
881 "menufullscreen", &priv->menu_fullscreen,
882 "camera_off", &priv->tool_button_camera_off,
883 "camera_preview", &priv->tool_button_camera_preview,
884 "camera_on", &priv->tool_button_camera_on,
888 empathy_builder_connect (gui, self,
889 "menuhangup", "activate", empathy_call_window_hangup_cb,
890 "hangup", "clicked", empathy_call_window_hangup_cb,
891 "menuredial", "activate", empathy_call_window_redial_cb,
892 "redial", "clicked", empathy_call_window_redial_cb,
893 "microphone", "toggled", empathy_call_window_mic_toggled_cb,
894 "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
895 "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
896 "camera_off", "toggled", tool_button_camera_off_toggled_cb,
897 "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
898 "camera_on", "toggled", tool_button_camera_on_toggled_cb,
901 priv->lock = g_mutex_new ();
903 gtk_container_add (GTK_CONTAINER (self), top_vbox);
905 priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
906 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
907 CONTENT_HBOX_BORDER_WIDTH);
908 gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
910 priv->pipeline = gst_pipeline_new (NULL);
911 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
912 priv->bus_message_source_id = gst_bus_add_watch (bus,
913 empathy_call_window_bus_message, self);
915 priv->fsnotifier = fs_element_added_notifier_new ();
916 fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
918 keyfile = g_key_file_new ();
919 filename = empathy_file_lookup ("element-properties", "data");
920 if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
922 fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
927 g_warning ("Could not load element-properties file: %s", error->message);
928 g_key_file_free (keyfile);
929 g_clear_error (&error);
934 priv->remote_user_output_frame = gtk_frame_new (NULL);
935 gtk_widget_set_size_request (priv->remote_user_output_frame,
936 EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
937 gtk_box_pack_start (GTK_BOX (priv->content_hbox),
938 priv->remote_user_output_frame, TRUE, TRUE,
939 CONTENT_HBOX_CHILDREN_PACKING_PADDING);
940 empathy_call_window_setup_remote_frame (bus, self);
942 priv->self_user_output_frame = gtk_frame_new (NULL);
943 gtk_widget_set_size_request (priv->self_user_output_frame,
944 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
946 priv->vbox = gtk_vbox_new (FALSE, 3);
947 gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
948 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
949 gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
951 empathy_call_window_setup_self_frame (bus, self);
953 empathy_call_window_setup_toolbar (self);
955 g_object_unref (bus);
957 priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
958 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
959 g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
960 G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
962 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
964 h = gtk_hbox_new (FALSE, 3);
965 gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
966 gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
968 priv->sidebar = empathy_sidebar_new ();
969 g_signal_connect (G_OBJECT (priv->sidebar),
970 "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
971 g_signal_connect (G_OBJECT (priv->sidebar),
972 "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
973 gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
975 priv->dtmf_panel = empathy_call_window_create_dtmf (self);
976 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
979 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
981 page = empathy_call_window_create_audio_input (self);
982 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
985 page = empathy_call_window_create_video_input (self);
986 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
989 gtk_widget_show_all (top_vbox);
991 gtk_widget_hide (priv->sidebar);
993 priv->fullscreen = empathy_call_window_fullscreen_new (self);
994 empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
996 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
997 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
999 g_signal_connect (G_OBJECT (self), "realize",
1000 G_CALLBACK (empathy_call_window_realized_cb), self);
1002 g_signal_connect (G_OBJECT (self), "delete-event",
1003 G_CALLBACK (empathy_call_window_delete_cb), self);
1005 g_signal_connect (G_OBJECT (self), "window-state-event",
1006 G_CALLBACK (empathy_call_window_state_event_cb), self);
1008 g_signal_connect (G_OBJECT (self), "key-press-event",
1009 G_CALLBACK (empathy_call_window_key_press_cb), self);
1011 priv->timer = g_timer_new ();
1013 g_object_ref (priv->ui_manager);
1014 g_object_unref (gui);
1017 /* Instead of specifying a width and a height, we specify only one size. That's
1018 because we want a square avatar icon. */
1020 init_contact_avatar_with_size (EmpathyContact *contact,
1021 GtkWidget *image_widget,
1024 GdkPixbuf *pixbuf_avatar = NULL;
1026 if (contact != NULL)
1028 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1032 if (pixbuf_avatar == NULL)
1034 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
1038 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1042 set_window_title (EmpathyCallWindow *self)
1044 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1047 /* translators: Call is a noun and %s is the contact name. This string
1048 * is used in the window title */
1049 tmp = g_strdup_printf (_("Call with %s"),
1050 empathy_contact_get_name (priv->contact));
1051 gtk_window_set_title (GTK_WINDOW (self), tmp);
1056 contact_name_changed_cb (EmpathyContact *contact,
1057 GParamSpec *pspec, EmpathyCallWindow *self)
1059 set_window_title (self);
1063 contact_avatar_changed_cb (EmpathyContact *contact,
1064 GParamSpec *pspec, GtkWidget *avatar_widget)
1068 size = avatar_widget->allocation.height;
1072 /* the widget is not allocated yet, set a default size */
1073 size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1074 REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1077 init_contact_avatar_with_size (contact, avatar_widget, size);
1081 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
1082 EmpathyContact *contact, const GError *error, gpointer user_data,
1083 GObject *weak_object)
1085 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1086 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1088 init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1089 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1091 g_signal_connect (contact, "notify::avatar",
1092 G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1096 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1097 EmpathyCallHandler *handler)
1099 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1101 g_object_get (handler, "contact", &(priv->contact), NULL);
1103 if (priv->contact != NULL)
1105 TpConnection *connection;
1106 EmpathyTpContactFactory *factory;
1108 set_window_title (self);
1110 g_signal_connect (priv->contact, "notify::name",
1111 G_CALLBACK (contact_name_changed_cb), self);
1112 g_signal_connect (priv->contact, "notify::avatar",
1113 G_CALLBACK (contact_avatar_changed_cb),
1114 priv->remote_user_avatar_widget);
1116 /* Retreiving the self avatar */
1117 connection = empathy_contact_get_connection (priv->contact);
1118 factory = empathy_tp_contact_factory_dup_singleton (connection);
1119 empathy_tp_contact_factory_get_from_handle (factory,
1120 tp_connection_get_self_handle (connection),
1121 empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1123 g_object_unref (factory);
1127 g_warning ("call handler doesn't have a contact");
1128 /* translators: Call is a noun. This string is used in the window
1130 gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1132 /* Since we can't access the remote contact, we can't get a connection
1133 to it and can't get the self contact (and its avatar). This means
1134 that we have to manually set the self avatar. */
1135 init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1136 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1139 init_contact_avatar_with_size (priv->contact,
1140 priv->remote_user_avatar_widget,
1141 MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1142 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1144 /* The remote avatar is shown by default and will be hidden when we receive
1145 video from the remote side. */
1146 gtk_widget_hide (priv->video_output);
1147 gtk_widget_show (priv->remote_user_avatar_widget);
1151 empathy_call_window_constructed (GObject *object)
1153 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1154 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1155 EmpathyTpCall *call;
1157 g_assert (priv->handler != NULL);
1159 g_object_get (priv->handler, "tp-call", &call, NULL);
1160 priv->outgoing = (call == NULL);
1162 g_object_unref (call);
1164 empathy_call_window_setup_avatars (self, priv->handler);
1165 empathy_call_window_set_state_connecting (self);
1167 if (empathy_call_handler_has_initial_video (priv->handler))
1169 /* Enable 'send video' buttons and display the preview */
1170 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), TRUE);
1171 gtk_toggle_tool_button_set_active (
1172 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
1174 display_video_preview (self, TRUE);
1178 static void empathy_call_window_dispose (GObject *object);
1179 static void empathy_call_window_finalize (GObject *object);
1182 empathy_call_window_set_property (GObject *object,
1183 guint property_id, const GValue *value, GParamSpec *pspec)
1185 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1187 switch (property_id)
1189 case PROP_CALL_HANDLER:
1190 priv->handler = g_value_dup_object (value);
1193 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1198 empathy_call_window_get_property (GObject *object,
1199 guint property_id, GValue *value, GParamSpec *pspec)
1201 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1203 switch (property_id)
1205 case PROP_CALL_HANDLER:
1206 g_value_set_object (value, priv->handler);
1209 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1214 empathy_call_window_class_init (
1215 EmpathyCallWindowClass *empathy_call_window_class)
1217 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1218 GParamSpec *param_spec;
1220 g_type_class_add_private (empathy_call_window_class,
1221 sizeof (EmpathyCallWindowPriv));
1223 object_class->constructed = empathy_call_window_constructed;
1224 object_class->set_property = empathy_call_window_set_property;
1225 object_class->get_property = empathy_call_window_get_property;
1227 object_class->dispose = empathy_call_window_dispose;
1228 object_class->finalize = empathy_call_window_finalize;
1230 param_spec = g_param_spec_object ("handler",
1231 "handler", "The call handler",
1232 EMPATHY_TYPE_CALL_HANDLER,
1233 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1234 g_object_class_install_property (object_class,
1235 PROP_CALL_HANDLER, param_spec);
1239 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1240 GParamSpec *property, EmpathyCallWindow *self)
1242 DEBUG ("video stream changed");
1243 empathy_call_window_update_avatars_visibility (call, self);
1247 empathy_call_window_dispose (GObject *object)
1249 EmpathyTpCall *call;
1250 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1251 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1253 if (priv->dispose_has_run)
1256 priv->dispose_has_run = TRUE;
1258 g_object_get (priv->handler, "tp-call", &call, NULL);
1262 g_signal_handlers_disconnect_by_func (call,
1263 empathy_call_window_video_stream_changed_cb, object);
1264 g_object_unref (call);
1267 if (priv->handler != NULL)
1268 g_object_unref (priv->handler);
1269 priv->handler = NULL;
1271 if (priv->pipeline != NULL)
1272 g_object_unref (priv->pipeline);
1273 priv->pipeline = NULL;
1275 if (priv->video_input != NULL)
1276 g_object_unref (priv->video_input);
1277 priv->video_input = NULL;
1279 if (priv->audio_input != NULL)
1280 g_object_unref (priv->audio_input);
1281 priv->audio_input = NULL;
1283 if (priv->audio_output != NULL)
1284 g_object_unref (priv->audio_output);
1285 priv->audio_output = NULL;
1287 if (priv->video_tee != NULL)
1288 g_object_unref (priv->video_tee);
1289 priv->video_tee = NULL;
1291 if (priv->fsnotifier != NULL)
1292 g_object_unref (priv->fsnotifier);
1293 priv->fsnotifier = NULL;
1295 if (priv->timer_id != 0)
1296 g_source_remove (priv->timer_id);
1299 if (priv->ui_manager != NULL)
1300 g_object_unref (priv->ui_manager);
1301 priv->ui_manager = NULL;
1303 if (priv->contact != NULL)
1305 g_signal_handlers_disconnect_by_func (priv->contact,
1306 contact_name_changed_cb, self);
1307 g_object_unref (priv->contact);
1308 priv->contact = NULL;
1311 /* release any references held by the object here */
1312 if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1313 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1317 empathy_call_window_finalize (GObject *object)
1319 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1320 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1322 if (priv->video_output_motion_handler_id != 0)
1324 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1325 priv->video_output_motion_handler_id);
1326 priv->video_output_motion_handler_id = 0;
1329 if (priv->bus_message_source_id != 0)
1331 g_source_remove (priv->bus_message_source_id);
1332 priv->bus_message_source_id = 0;
1335 /* free any data held directly by the object here */
1336 g_mutex_free (priv->lock);
1338 g_timer_destroy (priv->timer);
1340 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1345 empathy_call_window_new (EmpathyCallHandler *handler)
1347 return EMPATHY_CALL_WINDOW (
1348 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1352 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1353 GstElement *conference, gpointer user_data)
1355 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1356 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1358 gst_bin_add (GST_BIN (priv->pipeline), conference);
1360 gst_element_set_state (conference, GST_STATE_PLAYING);
1364 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1365 FsMediaType type, FsStreamDirection direction, gpointer user_data)
1367 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1368 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1370 if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1373 if (direction == FS_DIRECTION_RECV)
1376 /* video and direction is send */
1377 return priv->video_input != NULL;
1381 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1383 GstStateChangeReturn state_change_return;
1384 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1386 if (priv->pipeline == NULL)
1389 if (priv->bus_message_source_id != 0)
1391 g_source_remove (priv->bus_message_source_id);
1392 priv->bus_message_source_id = 0;
1395 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1397 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1398 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1400 if (priv->pipeline != NULL)
1401 g_object_unref (priv->pipeline);
1402 priv->pipeline = NULL;
1404 if (priv->video_input != NULL)
1405 g_object_unref (priv->video_input);
1406 priv->video_input = NULL;
1408 if (priv->audio_input != NULL)
1409 g_object_unref (priv->audio_input);
1410 priv->audio_input = NULL;
1412 g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1413 empathy_call_window_mic_volume_changed_cb, self);
1415 if (priv->audio_output != NULL)
1416 g_object_unref (priv->audio_output);
1417 priv->audio_output = NULL;
1419 if (priv->video_tee != NULL)
1420 g_object_unref (priv->video_tee);
1421 priv->video_tee = NULL;
1423 if (priv->video_preview != NULL)
1424 gtk_widget_destroy (priv->video_preview);
1425 priv->video_preview = NULL;
1427 priv->liveadder = NULL;
1428 priv->funnel = NULL;
1434 g_message ("Error: could not destroy pipeline. Closing call window");
1435 gtk_widget_destroy (GTK_WIDGET (self));
1442 empathy_call_window_disconnected (EmpathyCallWindow *self)
1444 gboolean could_disconnect = FALSE;
1445 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1446 gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1448 if (priv->call_state == CONNECTING)
1449 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1451 if (priv->call_state != REDIALING)
1452 priv->call_state = DISCONNECTED;
1454 if (could_reset_pipeline)
1456 gboolean initial_video = empathy_call_handler_has_initial_video (
1458 g_mutex_lock (priv->lock);
1460 g_timer_stop (priv->timer);
1462 if (priv->timer_id != 0)
1463 g_source_remove (priv->timer_id);
1466 g_mutex_unlock (priv->lock);
1468 empathy_call_window_status_message (self, _("Disconnected"));
1470 gtk_action_set_sensitive (priv->redial, TRUE);
1471 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1473 /* Reseting the send_video, camera_buton and mic_button to their
1475 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1476 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1477 gtk_action_set_sensitive (priv->send_video, FALSE);
1478 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1480 gtk_toggle_tool_button_set_active (
1481 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), initial_video);
1482 gtk_toggle_tool_button_set_active (
1483 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1485 gtk_progress_bar_set_fraction (
1486 GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1488 gtk_widget_hide (priv->video_output);
1489 gtk_widget_show (priv->remote_user_avatar_widget);
1491 priv->sending_video = FALSE;
1492 priv->call_started = FALSE;
1494 could_disconnect = TRUE;
1496 /* TODO: display the self avatar of the preview (depends if the "Always
1497 * Show Video Preview" is enabled or not) */
1500 return could_disconnect;
1505 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1508 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1509 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1511 if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1512 empathy_call_window_restart_call (self);
1517 empathy_call_window_channel_stream_closed_cb (EmpathyCallHandler *handler,
1518 TfStream *stream, gpointer user_data)
1520 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1521 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1524 g_object_get (stream, "media-type", &media_type, NULL);
1527 * This assumes that there is only one video stream per channel...
1530 if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1532 if (priv->funnel != NULL)
1536 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1537 (priv->video_output));
1539 gst_element_set_state (output, GST_STATE_NULL);
1540 gst_element_set_state (priv->funnel, GST_STATE_NULL);
1542 gst_bin_remove (GST_BIN (priv->pipeline), output);
1543 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1544 priv->funnel = NULL;
1547 else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1549 if (priv->liveadder != NULL)
1551 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1552 gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1554 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1555 gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1556 priv->liveadder = NULL;
1561 /* Called with global lock held */
1563 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1565 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1568 if (priv->funnel == NULL)
1572 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1573 (priv->video_output));
1575 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1577 gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1578 gst_bin_add (GST_BIN (priv->pipeline), output);
1580 gst_element_link (priv->funnel, output);
1582 gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1583 gst_element_set_state (output, GST_STATE_PLAYING);
1586 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1591 /* Called with global lock held */
1593 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1595 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1598 if (priv->liveadder == NULL)
1600 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1602 gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1603 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1605 gst_element_link (priv->liveadder, priv->audio_output);
1607 gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1608 gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1611 pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1617 empathy_call_window_update_timer (gpointer user_data)
1619 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1620 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1624 time_ = g_timer_elapsed (priv->timer, NULL);
1626 /* Translators: number of minutes:seconds the caller has been connected */
1627 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time_ / 60,
1629 empathy_call_window_status_message (self, str);
1636 display_error (EmpathyCallWindow *self,
1637 EmpathyTpCall *call,
1641 const gchar *details)
1643 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1644 GtkWidget *info_bar;
1645 GtkWidget *content_area;
1652 /* Create info bar */
1653 info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1656 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1658 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1660 /* hbox containing the image and the messages vbox */
1661 hbox = gtk_hbox_new (FALSE, 3);
1662 gtk_container_add (GTK_CONTAINER (content_area), hbox);
1665 image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1666 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1668 /* vbox containing the main message and the details expander */
1669 vbox = gtk_vbox_new (FALSE, 3);
1670 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1673 txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
1675 label = gtk_label_new (NULL);
1676 gtk_label_set_markup (GTK_LABEL (label), txt);
1677 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1678 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1681 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1684 if (details != NULL)
1686 GtkWidget *expander;
1688 expander = gtk_expander_new (_("Technical Details"));
1690 txt = g_strdup_printf ("<i>%s</i>", details);
1692 label = gtk_label_new (NULL);
1693 gtk_label_set_markup (GTK_LABEL (label), txt);
1694 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1695 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1698 gtk_container_add (GTK_CONTAINER (expander), label);
1699 gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1702 g_signal_connect (info_bar, "response",
1703 G_CALLBACK (gtk_widget_destroy), NULL);
1705 gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1706 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1707 gtk_widget_show_all (info_bar);
1711 media_stream_error_to_txt (EmpathyCallWindow *self,
1712 EmpathyTpCall *call,
1714 TpMediaStreamError error)
1716 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1723 case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1725 return g_strdup_printf (
1726 _("%s's software does not understand any of the audio formats "
1727 "supported by your computer"),
1728 empathy_contact_get_name (priv->contact));
1730 return g_strdup_printf (
1731 _("%s's software does not understand any of the video formats "
1732 "supported by your computer"),
1733 empathy_contact_get_name (priv->contact));
1735 case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1736 return g_strdup_printf (
1737 _("Can't establish a connection to %s. "
1738 "One of you might be on a network that does not allow "
1739 "direct connections."),
1740 empathy_contact_get_name (priv->contact));
1742 case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
1743 return g_strdup (_("There was a failure on the network"));
1745 case TP_MEDIA_STREAM_ERROR_NO_CODECS:
1747 return g_strdup (_("The audio formats necessary for this call "
1748 "are not installed on your computer"));
1750 return g_strdup (_("The video formats necessary for this call "
1751 "are not installed on your computer"));
1753 case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
1754 cm = empathy_tp_call_get_connection_manager (call);
1756 url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
1757 "product=Telepathy&component=%s", cm);
1759 result = g_strdup_printf (
1760 _("Something not expected happened in a Telepathy component. "
1761 "Please <a href=\"%s\">report this bug</a> and attach "
1762 "logs gathered from the 'Debug' window in the Help menu."), url);
1767 case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
1768 return g_strdup (_("There was a failure in the call engine"));
1776 empathy_call_window_stream_error (EmpathyCallWindow *self,
1777 EmpathyTpCall *call,
1786 desc = media_stream_error_to_txt (self, call, audio, code);
1789 /* No description, use the error message. That's not great as it's not
1790 * localized but it's better than nothing. */
1791 display_error (self, call, icon, title, msg, NULL);
1795 display_error (self, call, icon, title, desc, msg);
1801 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
1804 EmpathyCallWindow *self)
1806 empathy_call_window_stream_error (self, call, TRUE, code, msg,
1807 "gnome-stock-mic", _("Can't establish audio stream"));
1811 empathy_call_window_video_stream_error (EmpathyTpCall *call,
1814 EmpathyCallWindow *self)
1816 empathy_call_window_stream_error (self, call, FALSE, code, msg,
1817 "camera-web", _("Can't establish video stream"));
1821 empathy_call_window_connected (gpointer user_data)
1823 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1824 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1825 EmpathyTpCall *call;
1826 gboolean can_send_video;
1828 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1830 can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1831 empathy_contact_can_voip_video (priv->contact);
1833 g_object_get (priv->handler, "tp-call", &call, NULL);
1835 g_signal_connect (call, "notify::video-stream",
1836 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1838 if (empathy_tp_call_has_dtmf (call))
1839 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1841 if (priv->video_input == NULL)
1842 empathy_call_window_set_send_video (self, FALSE);
1844 priv->sending_video = can_send_video ?
1845 empathy_tp_call_is_sending_video (call) : FALSE;
1847 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1848 priv->sending_video && priv->video_input != NULL);
1849 gtk_toggle_tool_button_set_active (
1850 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
1851 priv->sending_video && priv->video_input != NULL);
1852 gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
1853 gtk_action_set_sensitive (priv->send_video, can_send_video);
1855 gtk_action_set_sensitive (priv->redial, FALSE);
1856 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1858 gtk_widget_set_sensitive (priv->mic_button, TRUE);
1860 empathy_call_window_update_avatars_visibility (call, self);
1862 g_object_unref (call);
1864 g_mutex_lock (priv->lock);
1866 priv->timer_id = g_timeout_add_seconds (1,
1867 empathy_call_window_update_timer, self);
1869 g_mutex_unlock (priv->lock);
1871 empathy_call_window_update_timer (self);
1877 /* Called from the streaming thread */
1879 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1880 GstPad *src, guint media_type, gpointer user_data)
1882 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1883 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1887 g_mutex_lock (priv->lock);
1889 if (priv->call_state != CONNECTED)
1891 g_timer_start (priv->timer);
1892 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
1893 priv->call_state = CONNECTED;
1898 case TP_MEDIA_STREAM_TYPE_AUDIO:
1899 pad = empathy_call_window_get_audio_sink_pad (self);
1901 case TP_MEDIA_STREAM_TYPE_VIDEO:
1902 gtk_widget_hide (priv->remote_user_avatar_widget);
1903 gtk_widget_show (priv->video_output);
1904 pad = empathy_call_window_get_video_sink_pad (self);
1907 g_assert_not_reached ();
1910 gst_pad_link (src, pad);
1911 gst_object_unref (pad);
1913 g_mutex_unlock (priv->lock);
1916 /* Called from the streaming thread */
1918 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1919 GstPad *sink, guint media_type, gpointer user_data)
1921 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1922 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1927 case TP_MEDIA_STREAM_TYPE_AUDIO:
1928 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1930 pad = gst_element_get_static_pad (priv->audio_input, "src");
1931 gst_pad_link (pad, sink);
1933 gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1935 case TP_MEDIA_STREAM_TYPE_VIDEO:
1936 if (priv->video_input != NULL)
1938 if (priv->video_tee != NULL)
1940 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1941 gst_pad_link (pad, sink);
1946 g_assert_not_reached ();
1952 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1954 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1955 GstElement *preview;
1957 DEBUG ("remove video input");
1958 preview = empathy_video_widget_get_element (
1959 EMPATHY_VIDEO_WIDGET (priv->video_preview));
1961 gst_element_set_state (priv->video_input, GST_STATE_NULL);
1962 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1963 gst_element_set_state (preview, GST_STATE_NULL);
1965 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1966 priv->video_tee, preview, NULL);
1968 g_object_unref (priv->video_input);
1969 priv->video_input = NULL;
1970 g_object_unref (priv->video_tee);
1971 priv->video_tee = NULL;
1972 gtk_widget_destroy (priv->video_preview);
1973 priv->video_preview = NULL;
1975 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1976 gtk_toggle_tool_button_set_active (
1977 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
1978 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1979 gtk_action_set_sensitive (priv->send_video, FALSE);
1981 gtk_widget_show (priv->self_user_avatar_widget);
1986 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1989 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1990 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1993 empathy_call_handler_bus_message (priv->handler, bus, message);
1995 switch (GST_MESSAGE_TYPE (message))
1997 case GST_MESSAGE_STATE_CHANGED:
1998 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2000 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2001 if (newstate == GST_STATE_PAUSED)
2002 empathy_call_window_setup_video_input (self);
2004 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2005 !priv->call_started)
2007 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2008 if (newstate == GST_STATE_PAUSED)
2010 priv->call_started = TRUE;
2011 empathy_call_handler_start_call (priv->handler);
2012 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2016 case GST_MESSAGE_ERROR:
2018 GError *error = NULL;
2019 GstElement *gst_error;
2022 gst_message_parse_error (message, &error, &debug);
2023 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2025 g_message ("Element error: %s -- %s\n", error->message, debug);
2027 if (g_str_has_prefix (gst_element_get_name (gst_error),
2028 VIDEO_INPUT_ERROR_PREFIX))
2030 /* Remove the video input and continue */
2031 if (priv->video_input != NULL)
2032 empathy_call_window_remove_video_input (self);
2033 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2037 empathy_call_window_disconnected (self);
2039 g_error_free (error);
2050 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
2051 EmpathyCallWindow *window)
2053 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2055 if (empathy_tp_call_is_receiving_video (call))
2057 gtk_widget_hide (priv->remote_user_avatar_widget);
2058 gtk_widget_show (priv->video_output);
2062 gtk_widget_hide (priv->video_output);
2063 gtk_widget_show (priv->remote_user_avatar_widget);
2068 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
2070 EmpathyCallWindow *self)
2072 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2073 EmpathyTpCall *call;
2075 g_object_get (priv->handler, "tp-call", &call, NULL);
2079 empathy_signal_connect_weak (call, "audio-stream-error",
2080 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
2081 empathy_signal_connect_weak (call, "video-stream-error",
2082 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
2084 g_object_unref (call);
2088 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2090 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2091 EmpathyTpCall *call;
2093 g_signal_connect (priv->handler, "conference-added",
2094 G_CALLBACK (empathy_call_window_conference_added_cb), window);
2095 g_signal_connect (priv->handler, "request-resource",
2096 G_CALLBACK (empathy_call_window_request_resource_cb), window);
2097 g_signal_connect (priv->handler, "closed",
2098 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2099 g_signal_connect (priv->handler, "src-pad-added",
2100 G_CALLBACK (empathy_call_window_src_added_cb), window);
2101 g_signal_connect (priv->handler, "sink-pad-added",
2102 G_CALLBACK (empathy_call_window_sink_added_cb), window);
2103 g_signal_connect (priv->handler, "stream-closed",
2104 G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
2106 g_object_get (priv->handler, "tp-call", &call, NULL);
2109 empathy_signal_connect_weak (call, "audio-stream-error",
2110 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
2111 empathy_signal_connect_weak (call, "video-stream-error",
2112 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
2114 g_object_unref (call);
2118 /* tp-call doesn't exist yet, we'll connect signals once it has been
2120 g_signal_connect (priv->handler, "notify::tp-call",
2121 G_CALLBACK (call_handler_notify_tp_call_cb), window);
2124 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2128 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2129 EmpathyCallWindow *window)
2131 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2133 if (priv->pipeline != NULL)
2135 if (priv->bus_message_source_id != 0)
2137 g_source_remove (priv->bus_message_source_id);
2138 priv->bus_message_source_id = 0;
2141 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2144 if (priv->call_state == CONNECTING)
2145 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2151 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2154 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2156 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2161 gtk_widget_hide (priv->sidebar);
2162 gtk_widget_hide (menu);
2163 gtk_widget_hide (priv->vbox);
2164 gtk_widget_hide (priv->statusbar);
2165 gtk_widget_hide (priv->toolbar);
2169 if (priv->sidebar_was_visible_before_fs)
2170 gtk_widget_show (priv->sidebar);
2172 gtk_widget_show (menu);
2173 gtk_widget_show (priv->vbox);
2174 gtk_widget_show (priv->statusbar);
2175 gtk_widget_show (priv->toolbar);
2177 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2178 priv->original_height_before_fs);
2183 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2185 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2187 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2188 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2189 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2190 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2191 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2192 priv->video_output, TRUE, TRUE,
2193 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2195 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2196 priv->vbox, TRUE, TRUE,
2197 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2202 empathy_call_window_state_event_cb (GtkWidget *widget,
2203 GdkEventWindowState *event, EmpathyCallWindow *window)
2205 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2207 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2208 gboolean set_fullscreen = event->new_window_state &
2209 GDK_WINDOW_STATE_FULLSCREEN;
2213 gboolean sidebar_was_visible;
2214 GtkAllocation allocation;
2215 gint original_width, original_height;
2217 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2218 original_width = allocation.width;
2219 original_height = allocation.height;
2221 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2223 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2224 priv->original_width_before_fs = original_width;
2225 priv->original_height_before_fs = original_height;
2227 if (priv->video_output_motion_handler_id == 0 &&
2228 priv->video_output != NULL)
2230 priv->video_output_motion_handler_id = g_signal_connect (
2231 G_OBJECT (priv->video_output), "motion-notify-event",
2232 G_CALLBACK (empathy_call_window_video_output_motion_notify),
2238 if (priv->video_output_motion_handler_id != 0)
2240 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2241 priv->video_output_motion_handler_id);
2242 priv->video_output_motion_handler_id = 0;
2246 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2248 show_controls (window, set_fullscreen);
2249 show_borders (window, set_fullscreen);
2250 gtk_action_set_stock_id (priv->menu_fullscreen,
2251 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2252 priv->is_fullscreen = set_fullscreen;
2259 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2260 EmpathyCallWindow *window)
2262 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2264 int w, h, handle_size;
2265 GtkAllocation allocation, sidebar_allocation;
2267 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2268 w = allocation.width;
2269 h = allocation.height;
2271 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2273 gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2274 if (gtk_toggle_button_get_active (toggle))
2276 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2277 gtk_widget_show (priv->sidebar);
2278 w += sidebar_allocation.width + handle_size;
2282 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2283 w -= sidebar_allocation.width + handle_size;
2284 gtk_widget_hide (priv->sidebar);
2287 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2290 gtk_window_resize (GTK_WINDOW (window), w, h);
2294 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2297 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2298 EmpathyTpCall *call;
2300 priv->sending_video = send;
2302 /* When we start sending video, we want to show the video preview by
2304 display_video_preview (window, send);
2306 g_object_get (priv->handler, "tp-call", &call, NULL);
2307 empathy_tp_call_request_video_stream_direction (call, send);
2308 g_object_unref (call);
2312 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
2313 EmpathyCallWindow *window)
2315 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2318 if (priv->call_state != CONNECTED)
2321 active = (gtk_toggle_action_get_active (toggle));
2323 if (priv->sending_video == active)
2326 empathy_call_window_set_send_video (window, active);
2327 gtk_toggle_tool_button_set_active (
2328 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), active);
2332 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2333 EmpathyCallWindow *window)
2335 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2338 if (priv->audio_input == NULL)
2341 active = (gtk_toggle_tool_button_get_active (toggle));
2345 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2347 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2351 /* TODO, Instead of setting the input volume to 0 we should probably
2352 * stop sending but this would cause the audio call to drop if both
2353 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2354 * in the future. GNOME #574574
2356 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2358 gtk_adjustment_set_value (priv->audio_input_adj, 0);
2363 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2364 EmpathyCallWindow *window)
2366 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2368 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2373 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2374 EmpathyCallWindow *window)
2376 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2378 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2383 empathy_call_window_hangup_cb (gpointer object,
2384 EmpathyCallWindow *window)
2386 if (empathy_call_window_disconnected (window))
2387 gtk_widget_destroy (GTK_WIDGET (window));
2391 empathy_call_window_restart_call (EmpathyCallWindow *window)
2394 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2396 gtk_widget_destroy (priv->remote_user_output_hbox);
2397 gtk_widget_destroy (priv->self_user_output_hbox);
2399 priv->pipeline = gst_pipeline_new (NULL);
2400 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2401 priv->bus_message_source_id = gst_bus_add_watch (bus,
2402 empathy_call_window_bus_message, window);
2404 empathy_call_window_setup_remote_frame (bus, window);
2405 empathy_call_window_setup_self_frame (bus, window);
2407 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2408 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2410 /* While the call was disconnected, the input volume might have changed.
2411 * However, since the audio_input source was destroyed, its volume has not
2412 * been updated during that time. That's why we manually update it here */
2413 empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2415 g_object_unref (bus);
2417 gtk_widget_show_all (priv->content_hbox);
2419 priv->outgoing = TRUE;
2420 empathy_call_window_set_state_connecting (window);
2422 priv->call_started = TRUE;
2423 empathy_call_handler_start_call (priv->handler);
2424 empathy_call_window_setup_avatars (window, priv->handler);
2425 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2427 gtk_action_set_sensitive (priv->redial, FALSE);
2428 gtk_widget_set_sensitive (priv->redial_button, FALSE);
2432 empathy_call_window_redial_cb (gpointer object,
2433 EmpathyCallWindow *window)
2435 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2437 if (priv->call_state == CONNECTED)
2438 priv->call_state = REDIALING;
2440 empathy_call_handler_stop_call (priv->handler);
2442 if (priv->call_state != CONNECTED)
2443 empathy_call_window_restart_call (window);
2447 empathy_call_window_fullscreen_cb (gpointer object,
2448 EmpathyCallWindow *window)
2450 empathy_call_window_fullscreen_toggle (window);
2454 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2456 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2458 if (priv->is_fullscreen)
2459 gtk_window_unfullscreen (GTK_WINDOW (window));
2461 gtk_window_fullscreen (GTK_WINDOW (window));
2465 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2466 GdkEventButton *event, EmpathyCallWindow *window)
2468 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2470 empathy_call_window_video_menu_popup (window, event->button);
2478 empathy_call_window_key_press_cb (GtkWidget *video_output,
2479 GdkEventKey *event, EmpathyCallWindow *window)
2481 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2483 if (priv->is_fullscreen && event->keyval == GDK_Escape)
2485 /* Since we are in fullscreen mode, toggling will bring us back to
2487 empathy_call_window_fullscreen_toggle (window);
2495 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2496 GdkEventMotion *event, EmpathyCallWindow *window)
2498 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2500 if (priv->is_fullscreen)
2502 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2509 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2513 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2515 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2517 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2518 button, gtk_get_current_event_time ());
2519 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2523 empathy_call_window_status_message (EmpathyCallWindow *window,
2526 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2528 if (priv->context_id == 0)
2530 priv->context_id = gtk_statusbar_get_context_id (
2531 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2535 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2538 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2543 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2544 gdouble value, EmpathyCallWindow *window)
2546 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2548 if (priv->audio_output == NULL)
2551 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2555 /* block all the signals related to camera control widgets. This is useful
2556 * when we are manually updating the UI and so don't want to fire the
2559 block_camera_control_signals (EmpathyCallWindow *self)
2561 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2563 g_signal_handlers_block_by_func (priv->tool_button_camera_off,
2564 tool_button_camera_off_toggled_cb, self);
2565 g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
2566 tool_button_camera_preview_toggled_cb, self);
2567 g_signal_handlers_block_by_func (priv->tool_button_camera_on,
2568 tool_button_camera_on_toggled_cb, self);
2572 unblock_camera_control_signals (EmpathyCallWindow *self)
2574 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2576 g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
2577 tool_button_camera_off_toggled_cb, self);
2578 g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
2579 tool_button_camera_preview_toggled_cb, self);
2580 g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
2581 tool_button_camera_on_toggled_cb, self);