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 if (priv->camera_state == CAMERA_STATE_OFF)
776 DEBUG ("disable camera");
777 disable_camera (self);
781 enable_preview (EmpathyCallWindow *self)
783 EmpathyCallWindowPriv *priv = GET_PRIV (self);
785 priv->camera_state = CAMERA_STATE_PREVIEW;
786 display_video_preview (self, TRUE);
788 block_camera_control_signals (self);
789 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
790 priv->tool_button_camera_off), FALSE);
791 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
792 priv->tool_button_camera_on), FALSE);
793 unblock_camera_control_signals (self);
797 tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
798 EmpathyCallWindow *self)
800 EmpathyCallWindowPriv *priv = GET_PRIV (self);
802 if (!gtk_toggle_tool_button_get_active (toggle))
804 if (priv->camera_state == CAMERA_STATE_PREVIEW)
806 /* We can't change the state by disabling the button */
807 block_camera_control_signals (self);
808 gtk_toggle_tool_button_set_active (toggle, TRUE);
809 unblock_camera_control_signals (self);
815 if (priv->camera_state == CAMERA_STATE_PREVIEW)
818 DEBUG ("enable preview");
819 enable_preview (self);
823 enable_camera (EmpathyCallWindow *self)
825 EmpathyCallWindowPriv *priv = GET_PRIV (self);
827 priv->camera_state = CAMERA_STATE_ON;
828 empathy_call_window_set_send_video (self, TRUE);
830 block_camera_control_signals (self);
831 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
832 priv->tool_button_camera_off), FALSE);
833 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
834 priv->tool_button_camera_preview), FALSE);
835 unblock_camera_control_signals (self);
839 tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
840 EmpathyCallWindow *self)
842 EmpathyCallWindowPriv *priv = GET_PRIV (self);
844 if (!gtk_toggle_tool_button_get_active (toggle))
846 if (priv->camera_state == CAMERA_STATE_ON)
848 /* We can't change the state by disabling the button */
849 block_camera_control_signals (self);
850 gtk_toggle_tool_button_set_active (toggle, TRUE);
851 unblock_camera_control_signals (self);
857 if (priv->camera_state == CAMERA_STATE_ON)
860 DEBUG ("enable camera");
861 enable_camera (self);
865 empathy_call_window_init (EmpathyCallWindow *self)
867 EmpathyCallWindowPriv *priv = GET_PRIV (self);
876 GError *error = NULL;
878 filename = empathy_file_lookup ("empathy-call-window.ui", "src");
879 gui = empathy_builder_get_file (filename,
880 "call_window_vbox", &top_vbox,
881 "errors_vbox", &priv->errors_vbox,
883 "statusbar", &priv->statusbar,
884 "redial", &priv->redial_button,
885 "microphone", &priv->mic_button,
886 "toolbar", &priv->toolbar,
887 "send_video", &priv->send_video,
888 "menuredial", &priv->redial,
889 "ui_manager", &priv->ui_manager,
890 "menufullscreen", &priv->menu_fullscreen,
891 "camera_off", &priv->tool_button_camera_off,
892 "camera_preview", &priv->tool_button_camera_preview,
893 "camera_on", &priv->tool_button_camera_on,
897 empathy_builder_connect (gui, self,
898 "menuhangup", "activate", empathy_call_window_hangup_cb,
899 "hangup", "clicked", empathy_call_window_hangup_cb,
900 "menuredial", "activate", empathy_call_window_redial_cb,
901 "redial", "clicked", empathy_call_window_redial_cb,
902 "microphone", "toggled", empathy_call_window_mic_toggled_cb,
903 "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
904 "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
905 "camera_off", "toggled", tool_button_camera_off_toggled_cb,
906 "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
907 "camera_on", "toggled", tool_button_camera_on_toggled_cb,
910 priv->lock = g_mutex_new ();
912 gtk_container_add (GTK_CONTAINER (self), top_vbox);
914 priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
915 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
916 CONTENT_HBOX_BORDER_WIDTH);
917 gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
919 priv->pipeline = gst_pipeline_new (NULL);
920 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
921 priv->bus_message_source_id = gst_bus_add_watch (bus,
922 empathy_call_window_bus_message, self);
924 priv->fsnotifier = fs_element_added_notifier_new ();
925 fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
927 keyfile = g_key_file_new ();
928 filename = empathy_file_lookup ("element-properties", "data");
929 if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
931 fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
936 g_warning ("Could not load element-properties file: %s", error->message);
937 g_key_file_free (keyfile);
938 g_clear_error (&error);
943 priv->remote_user_output_frame = gtk_frame_new (NULL);
944 gtk_widget_set_size_request (priv->remote_user_output_frame,
945 EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
946 gtk_box_pack_start (GTK_BOX (priv->content_hbox),
947 priv->remote_user_output_frame, TRUE, TRUE,
948 CONTENT_HBOX_CHILDREN_PACKING_PADDING);
949 empathy_call_window_setup_remote_frame (bus, self);
951 priv->self_user_output_frame = gtk_frame_new (NULL);
952 gtk_widget_set_size_request (priv->self_user_output_frame,
953 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
955 priv->vbox = gtk_vbox_new (FALSE, 3);
956 gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
957 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
958 gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
960 empathy_call_window_setup_self_frame (bus, self);
962 empathy_call_window_setup_toolbar (self);
964 g_object_unref (bus);
966 priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
967 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
968 g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
969 G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
971 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
973 h = gtk_hbox_new (FALSE, 3);
974 gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
975 gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
977 priv->sidebar = empathy_sidebar_new ();
978 g_signal_connect (G_OBJECT (priv->sidebar),
979 "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
980 g_signal_connect (G_OBJECT (priv->sidebar),
981 "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
982 gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
984 priv->dtmf_panel = empathy_call_window_create_dtmf (self);
985 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
988 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
990 page = empathy_call_window_create_audio_input (self);
991 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
994 page = empathy_call_window_create_video_input (self);
995 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
998 gtk_widget_show_all (top_vbox);
1000 gtk_widget_hide (priv->sidebar);
1002 priv->fullscreen = empathy_call_window_fullscreen_new (self);
1003 empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1004 priv->video_output);
1005 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1006 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1008 g_signal_connect (G_OBJECT (self), "realize",
1009 G_CALLBACK (empathy_call_window_realized_cb), self);
1011 g_signal_connect (G_OBJECT (self), "delete-event",
1012 G_CALLBACK (empathy_call_window_delete_cb), self);
1014 g_signal_connect (G_OBJECT (self), "window-state-event",
1015 G_CALLBACK (empathy_call_window_state_event_cb), self);
1017 g_signal_connect (G_OBJECT (self), "key-press-event",
1018 G_CALLBACK (empathy_call_window_key_press_cb), self);
1020 priv->timer = g_timer_new ();
1022 g_object_ref (priv->ui_manager);
1023 g_object_unref (gui);
1026 /* Instead of specifying a width and a height, we specify only one size. That's
1027 because we want a square avatar icon. */
1029 init_contact_avatar_with_size (EmpathyContact *contact,
1030 GtkWidget *image_widget,
1033 GdkPixbuf *pixbuf_avatar = NULL;
1035 if (contact != NULL)
1037 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1041 if (pixbuf_avatar == NULL)
1043 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
1047 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1051 set_window_title (EmpathyCallWindow *self)
1053 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1056 /* translators: Call is a noun and %s is the contact name. This string
1057 * is used in the window title */
1058 tmp = g_strdup_printf (_("Call with %s"),
1059 empathy_contact_get_name (priv->contact));
1060 gtk_window_set_title (GTK_WINDOW (self), tmp);
1065 contact_name_changed_cb (EmpathyContact *contact,
1066 GParamSpec *pspec, EmpathyCallWindow *self)
1068 set_window_title (self);
1072 contact_avatar_changed_cb (EmpathyContact *contact,
1073 GParamSpec *pspec, GtkWidget *avatar_widget)
1077 size = avatar_widget->allocation.height;
1081 /* the widget is not allocated yet, set a default size */
1082 size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1083 REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1086 init_contact_avatar_with_size (contact, avatar_widget, size);
1090 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
1091 EmpathyContact *contact, const GError *error, gpointer user_data,
1092 GObject *weak_object)
1094 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1095 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1097 init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1098 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1100 g_signal_connect (contact, "notify::avatar",
1101 G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1105 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1106 EmpathyCallHandler *handler)
1108 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1110 g_object_get (handler, "contact", &(priv->contact), NULL);
1112 if (priv->contact != NULL)
1114 TpConnection *connection;
1115 EmpathyTpContactFactory *factory;
1117 set_window_title (self);
1119 g_signal_connect (priv->contact, "notify::name",
1120 G_CALLBACK (contact_name_changed_cb), self);
1121 g_signal_connect (priv->contact, "notify::avatar",
1122 G_CALLBACK (contact_avatar_changed_cb),
1123 priv->remote_user_avatar_widget);
1125 /* Retreiving the self avatar */
1126 connection = empathy_contact_get_connection (priv->contact);
1127 factory = empathy_tp_contact_factory_dup_singleton (connection);
1128 empathy_tp_contact_factory_get_from_handle (factory,
1129 tp_connection_get_self_handle (connection),
1130 empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1132 g_object_unref (factory);
1136 g_warning ("call handler doesn't have a contact");
1137 /* translators: Call is a noun. This string is used in the window
1139 gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1141 /* Since we can't access the remote contact, we can't get a connection
1142 to it and can't get the self contact (and its avatar). This means
1143 that we have to manually set the self avatar. */
1144 init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1145 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1148 init_contact_avatar_with_size (priv->contact,
1149 priv->remote_user_avatar_widget,
1150 MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1151 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1153 /* The remote avatar is shown by default and will be hidden when we receive
1154 video from the remote side. */
1155 gtk_widget_hide (priv->video_output);
1156 gtk_widget_show (priv->remote_user_avatar_widget);
1160 empathy_call_window_constructed (GObject *object)
1162 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1163 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1164 EmpathyTpCall *call;
1166 g_assert (priv->handler != NULL);
1168 g_object_get (priv->handler, "tp-call", &call, NULL);
1169 priv->outgoing = (call == NULL);
1171 g_object_unref (call);
1173 empathy_call_window_setup_avatars (self, priv->handler);
1174 empathy_call_window_set_state_connecting (self);
1176 if (empathy_call_handler_has_initial_video (priv->handler))
1178 /* Enable 'send video' buttons and display the preview */
1179 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), TRUE);
1180 gtk_toggle_tool_button_set_active (
1181 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
1183 display_video_preview (self, TRUE);
1187 static void empathy_call_window_dispose (GObject *object);
1188 static void empathy_call_window_finalize (GObject *object);
1191 empathy_call_window_set_property (GObject *object,
1192 guint property_id, const GValue *value, GParamSpec *pspec)
1194 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1196 switch (property_id)
1198 case PROP_CALL_HANDLER:
1199 priv->handler = g_value_dup_object (value);
1202 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1207 empathy_call_window_get_property (GObject *object,
1208 guint property_id, GValue *value, GParamSpec *pspec)
1210 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1212 switch (property_id)
1214 case PROP_CALL_HANDLER:
1215 g_value_set_object (value, priv->handler);
1218 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1223 empathy_call_window_class_init (
1224 EmpathyCallWindowClass *empathy_call_window_class)
1226 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1227 GParamSpec *param_spec;
1229 g_type_class_add_private (empathy_call_window_class,
1230 sizeof (EmpathyCallWindowPriv));
1232 object_class->constructed = empathy_call_window_constructed;
1233 object_class->set_property = empathy_call_window_set_property;
1234 object_class->get_property = empathy_call_window_get_property;
1236 object_class->dispose = empathy_call_window_dispose;
1237 object_class->finalize = empathy_call_window_finalize;
1239 param_spec = g_param_spec_object ("handler",
1240 "handler", "The call handler",
1241 EMPATHY_TYPE_CALL_HANDLER,
1242 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1243 g_object_class_install_property (object_class,
1244 PROP_CALL_HANDLER, param_spec);
1248 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1249 GParamSpec *property, EmpathyCallWindow *self)
1251 DEBUG ("video stream changed");
1252 empathy_call_window_update_avatars_visibility (call, self);
1256 empathy_call_window_dispose (GObject *object)
1258 EmpathyTpCall *call;
1259 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1260 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1262 if (priv->dispose_has_run)
1265 priv->dispose_has_run = TRUE;
1267 g_object_get (priv->handler, "tp-call", &call, NULL);
1271 g_signal_handlers_disconnect_by_func (call,
1272 empathy_call_window_video_stream_changed_cb, object);
1273 g_object_unref (call);
1276 if (priv->handler != NULL)
1277 g_object_unref (priv->handler);
1278 priv->handler = NULL;
1280 if (priv->pipeline != NULL)
1281 g_object_unref (priv->pipeline);
1282 priv->pipeline = NULL;
1284 if (priv->video_input != NULL)
1285 g_object_unref (priv->video_input);
1286 priv->video_input = NULL;
1288 if (priv->audio_input != NULL)
1289 g_object_unref (priv->audio_input);
1290 priv->audio_input = NULL;
1292 if (priv->audio_output != NULL)
1293 g_object_unref (priv->audio_output);
1294 priv->audio_output = NULL;
1296 if (priv->video_tee != NULL)
1297 g_object_unref (priv->video_tee);
1298 priv->video_tee = NULL;
1300 if (priv->fsnotifier != NULL)
1301 g_object_unref (priv->fsnotifier);
1302 priv->fsnotifier = NULL;
1304 if (priv->timer_id != 0)
1305 g_source_remove (priv->timer_id);
1308 if (priv->ui_manager != NULL)
1309 g_object_unref (priv->ui_manager);
1310 priv->ui_manager = NULL;
1312 if (priv->contact != NULL)
1314 g_signal_handlers_disconnect_by_func (priv->contact,
1315 contact_name_changed_cb, self);
1316 g_object_unref (priv->contact);
1317 priv->contact = NULL;
1320 /* release any references held by the object here */
1321 if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1322 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1326 empathy_call_window_finalize (GObject *object)
1328 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1329 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1331 if (priv->video_output_motion_handler_id != 0)
1333 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1334 priv->video_output_motion_handler_id);
1335 priv->video_output_motion_handler_id = 0;
1338 if (priv->bus_message_source_id != 0)
1340 g_source_remove (priv->bus_message_source_id);
1341 priv->bus_message_source_id = 0;
1344 /* free any data held directly by the object here */
1345 g_mutex_free (priv->lock);
1347 g_timer_destroy (priv->timer);
1349 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1354 empathy_call_window_new (EmpathyCallHandler *handler)
1356 return EMPATHY_CALL_WINDOW (
1357 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1361 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1362 GstElement *conference, gpointer user_data)
1364 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1365 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1367 gst_bin_add (GST_BIN (priv->pipeline), conference);
1369 gst_element_set_state (conference, GST_STATE_PLAYING);
1373 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1374 FsMediaType type, FsStreamDirection direction, gpointer user_data)
1376 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1377 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1379 if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1382 if (direction == FS_DIRECTION_RECV)
1385 /* video and direction is send */
1386 return priv->video_input != NULL;
1390 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1392 GstStateChangeReturn state_change_return;
1393 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1395 if (priv->pipeline == NULL)
1398 if (priv->bus_message_source_id != 0)
1400 g_source_remove (priv->bus_message_source_id);
1401 priv->bus_message_source_id = 0;
1404 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1406 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1407 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1409 if (priv->pipeline != NULL)
1410 g_object_unref (priv->pipeline);
1411 priv->pipeline = NULL;
1413 if (priv->video_input != NULL)
1414 g_object_unref (priv->video_input);
1415 priv->video_input = NULL;
1417 if (priv->audio_input != NULL)
1418 g_object_unref (priv->audio_input);
1419 priv->audio_input = NULL;
1421 g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1422 empathy_call_window_mic_volume_changed_cb, self);
1424 if (priv->audio_output != NULL)
1425 g_object_unref (priv->audio_output);
1426 priv->audio_output = NULL;
1428 if (priv->video_tee != NULL)
1429 g_object_unref (priv->video_tee);
1430 priv->video_tee = NULL;
1432 if (priv->video_preview != NULL)
1433 gtk_widget_destroy (priv->video_preview);
1434 priv->video_preview = NULL;
1436 priv->liveadder = NULL;
1437 priv->funnel = NULL;
1443 g_message ("Error: could not destroy pipeline. Closing call window");
1444 gtk_widget_destroy (GTK_WIDGET (self));
1451 empathy_call_window_disconnected (EmpathyCallWindow *self)
1453 gboolean could_disconnect = FALSE;
1454 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1455 gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1457 if (priv->call_state == CONNECTING)
1458 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1460 if (priv->call_state != REDIALING)
1461 priv->call_state = DISCONNECTED;
1463 if (could_reset_pipeline)
1465 gboolean initial_video = empathy_call_handler_has_initial_video (
1467 g_mutex_lock (priv->lock);
1469 g_timer_stop (priv->timer);
1471 if (priv->timer_id != 0)
1472 g_source_remove (priv->timer_id);
1475 g_mutex_unlock (priv->lock);
1477 empathy_call_window_status_message (self, _("Disconnected"));
1479 gtk_action_set_sensitive (priv->redial, TRUE);
1480 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1482 /* Reseting the send_video, camera_buton and mic_button to their
1484 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1485 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1486 gtk_action_set_sensitive (priv->send_video, FALSE);
1487 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1489 gtk_toggle_tool_button_set_active (
1490 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), initial_video);
1491 gtk_toggle_tool_button_set_active (
1492 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1494 gtk_progress_bar_set_fraction (
1495 GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1497 gtk_widget_hide (priv->video_output);
1498 gtk_widget_show (priv->remote_user_avatar_widget);
1500 priv->sending_video = FALSE;
1501 priv->call_started = FALSE;
1503 could_disconnect = TRUE;
1505 /* TODO: display the self avatar of the preview (depends if the "Always
1506 * Show Video Preview" is enabled or not) */
1509 return could_disconnect;
1514 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1517 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1518 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1520 if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1521 empathy_call_window_restart_call (self);
1526 empathy_call_window_channel_stream_closed_cb (EmpathyCallHandler *handler,
1527 TfStream *stream, gpointer user_data)
1529 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1530 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1533 g_object_get (stream, "media-type", &media_type, NULL);
1536 * This assumes that there is only one video stream per channel...
1539 if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1541 if (priv->funnel != NULL)
1545 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1546 (priv->video_output));
1548 gst_element_set_state (output, GST_STATE_NULL);
1549 gst_element_set_state (priv->funnel, GST_STATE_NULL);
1551 gst_bin_remove (GST_BIN (priv->pipeline), output);
1552 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1553 priv->funnel = NULL;
1556 else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1558 if (priv->liveadder != NULL)
1560 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1561 gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1563 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1564 gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1565 priv->liveadder = NULL;
1570 /* Called with global lock held */
1572 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1574 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1577 if (priv->funnel == NULL)
1581 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1582 (priv->video_output));
1584 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1586 gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1587 gst_bin_add (GST_BIN (priv->pipeline), output);
1589 gst_element_link (priv->funnel, output);
1591 gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1592 gst_element_set_state (output, GST_STATE_PLAYING);
1595 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1600 /* Called with global lock held */
1602 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1604 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1607 if (priv->liveadder == NULL)
1609 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1611 gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1612 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1614 gst_element_link (priv->liveadder, priv->audio_output);
1616 gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1617 gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1620 pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1626 empathy_call_window_update_timer (gpointer user_data)
1628 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1629 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1633 time_ = g_timer_elapsed (priv->timer, NULL);
1635 /* Translators: number of minutes:seconds the caller has been connected */
1636 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time_ / 60,
1638 empathy_call_window_status_message (self, str);
1645 display_error (EmpathyCallWindow *self,
1646 EmpathyTpCall *call,
1650 const gchar *details)
1652 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1653 GtkWidget *info_bar;
1654 GtkWidget *content_area;
1661 /* Create info bar */
1662 info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1665 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1667 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1669 /* hbox containing the image and the messages vbox */
1670 hbox = gtk_hbox_new (FALSE, 3);
1671 gtk_container_add (GTK_CONTAINER (content_area), hbox);
1674 image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1675 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1677 /* vbox containing the main message and the details expander */
1678 vbox = gtk_vbox_new (FALSE, 3);
1679 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1682 txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
1684 label = gtk_label_new (NULL);
1685 gtk_label_set_markup (GTK_LABEL (label), txt);
1686 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1687 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1690 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1693 if (details != NULL)
1695 GtkWidget *expander;
1697 expander = gtk_expander_new (_("Technical Details"));
1699 txt = g_strdup_printf ("<i>%s</i>", details);
1701 label = gtk_label_new (NULL);
1702 gtk_label_set_markup (GTK_LABEL (label), txt);
1703 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1704 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1707 gtk_container_add (GTK_CONTAINER (expander), label);
1708 gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1711 g_signal_connect (info_bar, "response",
1712 G_CALLBACK (gtk_widget_destroy), NULL);
1714 gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1715 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1716 gtk_widget_show_all (info_bar);
1720 media_stream_error_to_txt (EmpathyCallWindow *self,
1721 EmpathyTpCall *call,
1723 TpMediaStreamError error)
1725 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1732 case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1734 return g_strdup_printf (
1735 _("%s's software does not understand any of the audio formats "
1736 "supported by your computer"),
1737 empathy_contact_get_name (priv->contact));
1739 return g_strdup_printf (
1740 _("%s's software does not understand any of the video formats "
1741 "supported by your computer"),
1742 empathy_contact_get_name (priv->contact));
1744 case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1745 return g_strdup_printf (
1746 _("Can't establish a connection to %s. "
1747 "One of you might be on a network that does not allow "
1748 "direct connections."),
1749 empathy_contact_get_name (priv->contact));
1751 case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
1752 return g_strdup (_("There was a failure on the network"));
1754 case TP_MEDIA_STREAM_ERROR_NO_CODECS:
1756 return g_strdup (_("The audio formats necessary for this call "
1757 "are not installed on your computer"));
1759 return g_strdup (_("The video formats necessary for this call "
1760 "are not installed on your computer"));
1762 case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
1763 cm = empathy_tp_call_get_connection_manager (call);
1765 url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
1766 "product=Telepathy&component=%s", cm);
1768 result = g_strdup_printf (
1769 _("Something not expected happened in a Telepathy component. "
1770 "Please <a href=\"%s\">report this bug</a> and attach "
1771 "logs gathered from the 'Debug' window in the Help menu."), url);
1776 case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
1777 return g_strdup (_("There was a failure in the call engine"));
1785 empathy_call_window_stream_error (EmpathyCallWindow *self,
1786 EmpathyTpCall *call,
1795 desc = media_stream_error_to_txt (self, call, audio, code);
1798 /* No description, use the error message. That's not great as it's not
1799 * localized but it's better than nothing. */
1800 display_error (self, call, icon, title, msg, NULL);
1804 display_error (self, call, icon, title, desc, msg);
1810 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
1813 EmpathyCallWindow *self)
1815 empathy_call_window_stream_error (self, call, TRUE, code, msg,
1816 "gnome-stock-mic", _("Can't establish audio stream"));
1820 empathy_call_window_video_stream_error (EmpathyTpCall *call,
1823 EmpathyCallWindow *self)
1825 empathy_call_window_stream_error (self, call, FALSE, code, msg,
1826 "camera-web", _("Can't establish video stream"));
1830 empathy_call_window_connected (gpointer user_data)
1832 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1833 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1834 EmpathyTpCall *call;
1835 gboolean can_send_video;
1837 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1839 can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1840 empathy_contact_can_voip_video (priv->contact);
1842 g_object_get (priv->handler, "tp-call", &call, NULL);
1844 g_signal_connect (call, "notify::video-stream",
1845 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1847 if (empathy_tp_call_has_dtmf (call))
1848 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1850 if (priv->video_input == NULL)
1851 empathy_call_window_set_send_video (self, FALSE);
1853 priv->sending_video = can_send_video ?
1854 empathy_tp_call_is_sending_video (call) : FALSE;
1856 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1857 priv->sending_video && priv->video_input != NULL);
1858 gtk_toggle_tool_button_set_active (
1859 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
1860 priv->sending_video && priv->video_input != NULL);
1861 gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
1862 gtk_action_set_sensitive (priv->send_video, can_send_video);
1864 gtk_action_set_sensitive (priv->redial, FALSE);
1865 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1867 gtk_widget_set_sensitive (priv->mic_button, TRUE);
1869 empathy_call_window_update_avatars_visibility (call, self);
1871 g_object_unref (call);
1873 g_mutex_lock (priv->lock);
1875 priv->timer_id = g_timeout_add_seconds (1,
1876 empathy_call_window_update_timer, self);
1878 g_mutex_unlock (priv->lock);
1880 empathy_call_window_update_timer (self);
1886 /* Called from the streaming thread */
1888 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1889 GstPad *src, guint media_type, gpointer user_data)
1891 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1892 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1896 g_mutex_lock (priv->lock);
1898 if (priv->call_state != CONNECTED)
1900 g_timer_start (priv->timer);
1901 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
1902 priv->call_state = CONNECTED;
1907 case TP_MEDIA_STREAM_TYPE_AUDIO:
1908 pad = empathy_call_window_get_audio_sink_pad (self);
1910 case TP_MEDIA_STREAM_TYPE_VIDEO:
1911 gtk_widget_hide (priv->remote_user_avatar_widget);
1912 gtk_widget_show (priv->video_output);
1913 pad = empathy_call_window_get_video_sink_pad (self);
1916 g_assert_not_reached ();
1919 gst_pad_link (src, pad);
1920 gst_object_unref (pad);
1922 g_mutex_unlock (priv->lock);
1925 /* Called from the streaming thread */
1927 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1928 GstPad *sink, guint media_type, gpointer user_data)
1930 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1931 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1936 case TP_MEDIA_STREAM_TYPE_AUDIO:
1937 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1939 pad = gst_element_get_static_pad (priv->audio_input, "src");
1940 gst_pad_link (pad, sink);
1942 gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1944 case TP_MEDIA_STREAM_TYPE_VIDEO:
1945 if (priv->video_input != NULL)
1947 if (priv->video_tee != NULL)
1949 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1950 gst_pad_link (pad, sink);
1955 g_assert_not_reached ();
1961 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1963 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1964 GstElement *preview;
1966 DEBUG ("remove video input");
1967 preview = empathy_video_widget_get_element (
1968 EMPATHY_VIDEO_WIDGET (priv->video_preview));
1970 gst_element_set_state (priv->video_input, GST_STATE_NULL);
1971 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1972 gst_element_set_state (preview, GST_STATE_NULL);
1974 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1975 priv->video_tee, preview, NULL);
1977 g_object_unref (priv->video_input);
1978 priv->video_input = NULL;
1979 g_object_unref (priv->video_tee);
1980 priv->video_tee = NULL;
1981 gtk_widget_destroy (priv->video_preview);
1982 priv->video_preview = NULL;
1984 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1985 gtk_toggle_tool_button_set_active (
1986 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
1987 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1988 gtk_action_set_sensitive (priv->send_video, FALSE);
1990 gtk_widget_show (priv->self_user_avatar_widget);
1995 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1998 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1999 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2002 empathy_call_handler_bus_message (priv->handler, bus, message);
2004 switch (GST_MESSAGE_TYPE (message))
2006 case GST_MESSAGE_STATE_CHANGED:
2007 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2009 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2010 if (newstate == GST_STATE_PAUSED)
2011 empathy_call_window_setup_video_input (self);
2013 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2014 !priv->call_started)
2016 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2017 if (newstate == GST_STATE_PAUSED)
2019 priv->call_started = TRUE;
2020 empathy_call_handler_start_call (priv->handler);
2021 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2025 case GST_MESSAGE_ERROR:
2027 GError *error = NULL;
2028 GstElement *gst_error;
2031 gst_message_parse_error (message, &error, &debug);
2032 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2034 g_message ("Element error: %s -- %s\n", error->message, debug);
2036 if (g_str_has_prefix (gst_element_get_name (gst_error),
2037 VIDEO_INPUT_ERROR_PREFIX))
2039 /* Remove the video input and continue */
2040 if (priv->video_input != NULL)
2041 empathy_call_window_remove_video_input (self);
2042 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2046 empathy_call_window_disconnected (self);
2048 g_error_free (error);
2059 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
2060 EmpathyCallWindow *window)
2062 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2064 if (empathy_tp_call_is_receiving_video (call))
2066 gtk_widget_hide (priv->remote_user_avatar_widget);
2067 gtk_widget_show (priv->video_output);
2071 gtk_widget_hide (priv->video_output);
2072 gtk_widget_show (priv->remote_user_avatar_widget);
2077 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
2079 EmpathyCallWindow *self)
2081 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2082 EmpathyTpCall *call;
2084 g_object_get (priv->handler, "tp-call", &call, NULL);
2088 empathy_signal_connect_weak (call, "audio-stream-error",
2089 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
2090 empathy_signal_connect_weak (call, "video-stream-error",
2091 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
2093 g_object_unref (call);
2097 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2099 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2100 EmpathyTpCall *call;
2102 g_signal_connect (priv->handler, "conference-added",
2103 G_CALLBACK (empathy_call_window_conference_added_cb), window);
2104 g_signal_connect (priv->handler, "request-resource",
2105 G_CALLBACK (empathy_call_window_request_resource_cb), window);
2106 g_signal_connect (priv->handler, "closed",
2107 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2108 g_signal_connect (priv->handler, "src-pad-added",
2109 G_CALLBACK (empathy_call_window_src_added_cb), window);
2110 g_signal_connect (priv->handler, "sink-pad-added",
2111 G_CALLBACK (empathy_call_window_sink_added_cb), window);
2112 g_signal_connect (priv->handler, "stream-closed",
2113 G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
2115 g_object_get (priv->handler, "tp-call", &call, NULL);
2118 empathy_signal_connect_weak (call, "audio-stream-error",
2119 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
2120 empathy_signal_connect_weak (call, "video-stream-error",
2121 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
2123 g_object_unref (call);
2127 /* tp-call doesn't exist yet, we'll connect signals once it has been
2129 g_signal_connect (priv->handler, "notify::tp-call",
2130 G_CALLBACK (call_handler_notify_tp_call_cb), window);
2133 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2137 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2138 EmpathyCallWindow *window)
2140 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2142 if (priv->pipeline != NULL)
2144 if (priv->bus_message_source_id != 0)
2146 g_source_remove (priv->bus_message_source_id);
2147 priv->bus_message_source_id = 0;
2150 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2153 if (priv->call_state == CONNECTING)
2154 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2160 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2163 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2165 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2170 gtk_widget_hide (priv->sidebar);
2171 gtk_widget_hide (menu);
2172 gtk_widget_hide (priv->vbox);
2173 gtk_widget_hide (priv->statusbar);
2174 gtk_widget_hide (priv->toolbar);
2178 if (priv->sidebar_was_visible_before_fs)
2179 gtk_widget_show (priv->sidebar);
2181 gtk_widget_show (menu);
2182 gtk_widget_show (priv->vbox);
2183 gtk_widget_show (priv->statusbar);
2184 gtk_widget_show (priv->toolbar);
2186 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2187 priv->original_height_before_fs);
2192 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2194 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2196 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2197 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2198 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2199 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2200 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2201 priv->video_output, TRUE, TRUE,
2202 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2204 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2205 priv->vbox, TRUE, TRUE,
2206 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2211 empathy_call_window_state_event_cb (GtkWidget *widget,
2212 GdkEventWindowState *event, EmpathyCallWindow *window)
2214 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2216 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2217 gboolean set_fullscreen = event->new_window_state &
2218 GDK_WINDOW_STATE_FULLSCREEN;
2222 gboolean sidebar_was_visible;
2223 GtkAllocation allocation;
2224 gint original_width, original_height;
2226 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2227 original_width = allocation.width;
2228 original_height = allocation.height;
2230 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2232 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2233 priv->original_width_before_fs = original_width;
2234 priv->original_height_before_fs = original_height;
2236 if (priv->video_output_motion_handler_id == 0 &&
2237 priv->video_output != NULL)
2239 priv->video_output_motion_handler_id = g_signal_connect (
2240 G_OBJECT (priv->video_output), "motion-notify-event",
2241 G_CALLBACK (empathy_call_window_video_output_motion_notify),
2247 if (priv->video_output_motion_handler_id != 0)
2249 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2250 priv->video_output_motion_handler_id);
2251 priv->video_output_motion_handler_id = 0;
2255 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2257 show_controls (window, set_fullscreen);
2258 show_borders (window, set_fullscreen);
2259 gtk_action_set_stock_id (priv->menu_fullscreen,
2260 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2261 priv->is_fullscreen = set_fullscreen;
2268 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2269 EmpathyCallWindow *window)
2271 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2273 int w, h, handle_size;
2274 GtkAllocation allocation, sidebar_allocation;
2276 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2277 w = allocation.width;
2278 h = allocation.height;
2280 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2282 gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2283 if (gtk_toggle_button_get_active (toggle))
2285 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2286 gtk_widget_show (priv->sidebar);
2287 w += sidebar_allocation.width + handle_size;
2291 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2292 w -= sidebar_allocation.width + handle_size;
2293 gtk_widget_hide (priv->sidebar);
2296 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2299 gtk_window_resize (GTK_WINDOW (window), w, h);
2303 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2306 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2307 EmpathyTpCall *call;
2309 priv->sending_video = send;
2311 /* When we start sending video, we want to show the video preview by
2313 display_video_preview (window, send);
2315 g_object_get (priv->handler, "tp-call", &call, NULL);
2316 empathy_tp_call_request_video_stream_direction (call, send);
2317 g_object_unref (call);
2321 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
2322 EmpathyCallWindow *window)
2324 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2327 if (priv->call_state != CONNECTED)
2330 active = (gtk_toggle_action_get_active (toggle));
2332 if (priv->sending_video == active)
2335 empathy_call_window_set_send_video (window, active);
2336 gtk_toggle_tool_button_set_active (
2337 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), active);
2341 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2342 EmpathyCallWindow *window)
2344 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2347 if (priv->audio_input == NULL)
2350 active = (gtk_toggle_tool_button_get_active (toggle));
2354 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2356 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2360 /* TODO, Instead of setting the input volume to 0 we should probably
2361 * stop sending but this would cause the audio call to drop if both
2362 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2363 * in the future. GNOME #574574
2365 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2367 gtk_adjustment_set_value (priv->audio_input_adj, 0);
2372 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2373 EmpathyCallWindow *window)
2375 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2377 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2382 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2383 EmpathyCallWindow *window)
2385 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2387 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2392 empathy_call_window_hangup_cb (gpointer object,
2393 EmpathyCallWindow *window)
2395 if (empathy_call_window_disconnected (window))
2396 gtk_widget_destroy (GTK_WIDGET (window));
2400 empathy_call_window_restart_call (EmpathyCallWindow *window)
2403 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2405 gtk_widget_destroy (priv->remote_user_output_hbox);
2406 gtk_widget_destroy (priv->self_user_output_hbox);
2408 priv->pipeline = gst_pipeline_new (NULL);
2409 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2410 priv->bus_message_source_id = gst_bus_add_watch (bus,
2411 empathy_call_window_bus_message, window);
2413 empathy_call_window_setup_remote_frame (bus, window);
2414 empathy_call_window_setup_self_frame (bus, window);
2416 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2417 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2419 /* While the call was disconnected, the input volume might have changed.
2420 * However, since the audio_input source was destroyed, its volume has not
2421 * been updated during that time. That's why we manually update it here */
2422 empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2424 g_object_unref (bus);
2426 gtk_widget_show_all (priv->content_hbox);
2428 priv->outgoing = TRUE;
2429 empathy_call_window_set_state_connecting (window);
2431 priv->call_started = TRUE;
2432 empathy_call_handler_start_call (priv->handler);
2433 empathy_call_window_setup_avatars (window, priv->handler);
2434 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2436 gtk_action_set_sensitive (priv->redial, FALSE);
2437 gtk_widget_set_sensitive (priv->redial_button, FALSE);
2441 empathy_call_window_redial_cb (gpointer object,
2442 EmpathyCallWindow *window)
2444 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2446 if (priv->call_state == CONNECTED)
2447 priv->call_state = REDIALING;
2449 empathy_call_handler_stop_call (priv->handler);
2451 if (priv->call_state != CONNECTED)
2452 empathy_call_window_restart_call (window);
2456 empathy_call_window_fullscreen_cb (gpointer object,
2457 EmpathyCallWindow *window)
2459 empathy_call_window_fullscreen_toggle (window);
2463 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2465 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2467 if (priv->is_fullscreen)
2468 gtk_window_unfullscreen (GTK_WINDOW (window));
2470 gtk_window_fullscreen (GTK_WINDOW (window));
2474 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2475 GdkEventButton *event, EmpathyCallWindow *window)
2477 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2479 empathy_call_window_video_menu_popup (window, event->button);
2487 empathy_call_window_key_press_cb (GtkWidget *video_output,
2488 GdkEventKey *event, EmpathyCallWindow *window)
2490 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2492 if (priv->is_fullscreen && event->keyval == GDK_Escape)
2494 /* Since we are in fullscreen mode, toggling will bring us back to
2496 empathy_call_window_fullscreen_toggle (window);
2504 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2505 GdkEventMotion *event, EmpathyCallWindow *window)
2507 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2509 if (priv->is_fullscreen)
2511 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2518 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2522 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2524 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2526 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2527 button, gtk_get_current_event_time ());
2528 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2532 empathy_call_window_status_message (EmpathyCallWindow *window,
2535 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2537 if (priv->context_id == 0)
2539 priv->context_id = gtk_statusbar_get_context_id (
2540 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2544 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2547 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2552 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2553 gdouble value, EmpathyCallWindow *window)
2555 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2557 if (priv->audio_output == NULL)
2560 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2564 /* block all the signals related to camera control widgets. This is useful
2565 * when we are manually updating the UI and so don't want to fire the
2568 block_camera_control_signals (EmpathyCallWindow *self)
2570 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2572 g_signal_handlers_block_by_func (priv->tool_button_camera_off,
2573 tool_button_camera_off_toggled_cb, self);
2574 g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
2575 tool_button_camera_preview_toggled_cb, self);
2576 g_signal_handlers_block_by_func (priv->tool_button_camera_on,
2577 tool_button_camera_on_toggled_cb, self);
2581 unblock_camera_control_signals (EmpathyCallWindow *self)
2583 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2585 g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
2586 tool_button_camera_off_toggled_cb, self);
2587 g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
2588 tool_button_camera_preview_toggled_cb, self);
2589 g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
2590 tool_button_camera_on_toggled_cb, self);