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 display_video_preview (self, FALSE);
744 empathy_call_window_set_send_video (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);
753 priv->camera_state = CAMERA_STATE_OFF;
757 tool_button_camera_off_toggled_cb (GtkToggleToolButton *toggle,
758 EmpathyCallWindow *self)
760 EmpathyCallWindowPriv *priv = GET_PRIV (self);
762 if (!gtk_toggle_tool_button_get_active (toggle))
764 if (priv->camera_state == CAMERA_STATE_OFF)
766 /* We can't change the state by disabling the button */
767 block_camera_control_signals (self);
768 gtk_toggle_tool_button_set_active (toggle, TRUE);
769 unblock_camera_control_signals (self);
775 if (priv->camera_state == CAMERA_STATE_OFF)
778 DEBUG ("disable camera");
779 disable_camera (self);
783 enable_preview (EmpathyCallWindow *self)
785 EmpathyCallWindowPriv *priv = GET_PRIV (self);
787 if (priv->camera_state == CAMERA_STATE_ON)
788 /* preview is already displayed so we just have to stop sending */
789 empathy_call_window_set_send_video (self, FALSE);
791 display_video_preview (self, TRUE);
793 block_camera_control_signals (self);
794 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
795 priv->tool_button_camera_off), FALSE);
796 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
797 priv->tool_button_camera_on), FALSE);
798 unblock_camera_control_signals (self);
800 priv->camera_state = CAMERA_STATE_PREVIEW;
804 tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
805 EmpathyCallWindow *self)
807 EmpathyCallWindowPriv *priv = GET_PRIV (self);
809 if (!gtk_toggle_tool_button_get_active (toggle))
811 if (priv->camera_state == CAMERA_STATE_PREVIEW)
813 /* We can't change the state by disabling the button */
814 block_camera_control_signals (self);
815 gtk_toggle_tool_button_set_active (toggle, TRUE);
816 unblock_camera_control_signals (self);
822 if (priv->camera_state == CAMERA_STATE_PREVIEW)
825 DEBUG ("enable preview");
826 enable_preview (self);
830 enable_camera (EmpathyCallWindow *self)
832 EmpathyCallWindowPriv *priv = GET_PRIV (self);
834 empathy_call_window_set_send_video (self, TRUE);
836 block_camera_control_signals (self);
837 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
838 priv->tool_button_camera_off), FALSE);
839 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
840 priv->tool_button_camera_preview), FALSE);
841 unblock_camera_control_signals (self);
843 priv->camera_state = CAMERA_STATE_ON;
847 tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
848 EmpathyCallWindow *self)
850 EmpathyCallWindowPriv *priv = GET_PRIV (self);
852 if (!gtk_toggle_tool_button_get_active (toggle))
854 if (priv->camera_state == CAMERA_STATE_ON)
856 /* We can't change the state by disabling the button */
857 block_camera_control_signals (self);
858 gtk_toggle_tool_button_set_active (toggle, TRUE);
859 unblock_camera_control_signals (self);
865 if (priv->camera_state == CAMERA_STATE_ON)
868 DEBUG ("enable camera");
869 enable_camera (self);
873 empathy_call_window_init (EmpathyCallWindow *self)
875 EmpathyCallWindowPriv *priv = GET_PRIV (self);
884 GError *error = NULL;
886 filename = empathy_file_lookup ("empathy-call-window.ui", "src");
887 gui = empathy_builder_get_file (filename,
888 "call_window_vbox", &top_vbox,
889 "errors_vbox", &priv->errors_vbox,
891 "statusbar", &priv->statusbar,
892 "redial", &priv->redial_button,
893 "microphone", &priv->mic_button,
894 "toolbar", &priv->toolbar,
895 "send_video", &priv->send_video,
896 "menuredial", &priv->redial,
897 "ui_manager", &priv->ui_manager,
898 "menufullscreen", &priv->menu_fullscreen,
899 "camera_off", &priv->tool_button_camera_off,
900 "camera_preview", &priv->tool_button_camera_preview,
901 "camera_on", &priv->tool_button_camera_on,
905 empathy_builder_connect (gui, self,
906 "menuhangup", "activate", empathy_call_window_hangup_cb,
907 "hangup", "clicked", empathy_call_window_hangup_cb,
908 "menuredial", "activate", empathy_call_window_redial_cb,
909 "redial", "clicked", empathy_call_window_redial_cb,
910 "microphone", "toggled", empathy_call_window_mic_toggled_cb,
911 "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
912 "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
913 "camera_off", "toggled", tool_button_camera_off_toggled_cb,
914 "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
915 "camera_on", "toggled", tool_button_camera_on_toggled_cb,
918 priv->lock = g_mutex_new ();
920 gtk_container_add (GTK_CONTAINER (self), top_vbox);
922 priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
923 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
924 CONTENT_HBOX_BORDER_WIDTH);
925 gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
927 priv->pipeline = gst_pipeline_new (NULL);
928 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
929 priv->bus_message_source_id = gst_bus_add_watch (bus,
930 empathy_call_window_bus_message, self);
932 priv->fsnotifier = fs_element_added_notifier_new ();
933 fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
935 keyfile = g_key_file_new ();
936 filename = empathy_file_lookup ("element-properties", "data");
937 if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
939 fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
944 g_warning ("Could not load element-properties file: %s", error->message);
945 g_key_file_free (keyfile);
946 g_clear_error (&error);
951 priv->remote_user_output_frame = gtk_frame_new (NULL);
952 gtk_widget_set_size_request (priv->remote_user_output_frame,
953 EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
954 gtk_box_pack_start (GTK_BOX (priv->content_hbox),
955 priv->remote_user_output_frame, TRUE, TRUE,
956 CONTENT_HBOX_CHILDREN_PACKING_PADDING);
957 empathy_call_window_setup_remote_frame (bus, self);
959 priv->self_user_output_frame = gtk_frame_new (NULL);
960 gtk_widget_set_size_request (priv->self_user_output_frame,
961 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
963 priv->vbox = gtk_vbox_new (FALSE, 3);
964 gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
965 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
966 gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
968 empathy_call_window_setup_self_frame (bus, self);
970 empathy_call_window_setup_toolbar (self);
972 g_object_unref (bus);
974 priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
975 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
976 g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
977 G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
979 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
981 h = gtk_hbox_new (FALSE, 3);
982 gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
983 gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
985 priv->sidebar = empathy_sidebar_new ();
986 g_signal_connect (G_OBJECT (priv->sidebar),
987 "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
988 g_signal_connect (G_OBJECT (priv->sidebar),
989 "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
990 gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
992 priv->dtmf_panel = empathy_call_window_create_dtmf (self);
993 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
996 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
998 page = empathy_call_window_create_audio_input (self);
999 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
1002 page = empathy_call_window_create_video_input (self);
1003 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
1006 gtk_widget_show_all (top_vbox);
1008 gtk_widget_hide (priv->sidebar);
1010 priv->fullscreen = empathy_call_window_fullscreen_new (self);
1011 empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1012 priv->video_output);
1013 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1014 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1016 g_signal_connect (G_OBJECT (self), "realize",
1017 G_CALLBACK (empathy_call_window_realized_cb), self);
1019 g_signal_connect (G_OBJECT (self), "delete-event",
1020 G_CALLBACK (empathy_call_window_delete_cb), self);
1022 g_signal_connect (G_OBJECT (self), "window-state-event",
1023 G_CALLBACK (empathy_call_window_state_event_cb), self);
1025 g_signal_connect (G_OBJECT (self), "key-press-event",
1026 G_CALLBACK (empathy_call_window_key_press_cb), self);
1028 priv->timer = g_timer_new ();
1030 g_object_ref (priv->ui_manager);
1031 g_object_unref (gui);
1034 /* Instead of specifying a width and a height, we specify only one size. That's
1035 because we want a square avatar icon. */
1037 init_contact_avatar_with_size (EmpathyContact *contact,
1038 GtkWidget *image_widget,
1041 GdkPixbuf *pixbuf_avatar = NULL;
1043 if (contact != NULL)
1045 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1049 if (pixbuf_avatar == NULL)
1051 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
1055 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1059 set_window_title (EmpathyCallWindow *self)
1061 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1064 /* translators: Call is a noun and %s is the contact name. This string
1065 * is used in the window title */
1066 tmp = g_strdup_printf (_("Call with %s"),
1067 empathy_contact_get_name (priv->contact));
1068 gtk_window_set_title (GTK_WINDOW (self), tmp);
1073 contact_name_changed_cb (EmpathyContact *contact,
1074 GParamSpec *pspec, EmpathyCallWindow *self)
1076 set_window_title (self);
1080 contact_avatar_changed_cb (EmpathyContact *contact,
1081 GParamSpec *pspec, GtkWidget *avatar_widget)
1085 size = avatar_widget->allocation.height;
1089 /* the widget is not allocated yet, set a default size */
1090 size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1091 REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1094 init_contact_avatar_with_size (contact, avatar_widget, size);
1098 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
1099 EmpathyContact *contact, const GError *error, gpointer user_data,
1100 GObject *weak_object)
1102 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1103 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1105 init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1106 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1108 g_signal_connect (contact, "notify::avatar",
1109 G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1113 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1114 EmpathyCallHandler *handler)
1116 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1118 g_object_get (handler, "contact", &(priv->contact), NULL);
1120 if (priv->contact != NULL)
1122 TpConnection *connection;
1123 EmpathyTpContactFactory *factory;
1125 set_window_title (self);
1127 g_signal_connect (priv->contact, "notify::name",
1128 G_CALLBACK (contact_name_changed_cb), self);
1129 g_signal_connect (priv->contact, "notify::avatar",
1130 G_CALLBACK (contact_avatar_changed_cb),
1131 priv->remote_user_avatar_widget);
1133 /* Retreiving the self avatar */
1134 connection = empathy_contact_get_connection (priv->contact);
1135 factory = empathy_tp_contact_factory_dup_singleton (connection);
1136 empathy_tp_contact_factory_get_from_handle (factory,
1137 tp_connection_get_self_handle (connection),
1138 empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1140 g_object_unref (factory);
1144 g_warning ("call handler doesn't have a contact");
1145 /* translators: Call is a noun. This string is used in the window
1147 gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1149 /* Since we can't access the remote contact, we can't get a connection
1150 to it and can't get the self contact (and its avatar). This means
1151 that we have to manually set the self avatar. */
1152 init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1153 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1156 init_contact_avatar_with_size (priv->contact,
1157 priv->remote_user_avatar_widget,
1158 MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1159 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1161 /* The remote avatar is shown by default and will be hidden when we receive
1162 video from the remote side. */
1163 gtk_widget_hide (priv->video_output);
1164 gtk_widget_show (priv->remote_user_avatar_widget);
1168 empathy_call_window_constructed (GObject *object)
1170 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1171 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1172 EmpathyTpCall *call;
1174 g_assert (priv->handler != NULL);
1176 g_object_get (priv->handler, "tp-call", &call, NULL);
1177 priv->outgoing = (call == NULL);
1179 g_object_unref (call);
1181 empathy_call_window_setup_avatars (self, priv->handler);
1182 empathy_call_window_set_state_connecting (self);
1184 if (empathy_call_handler_has_initial_video (priv->handler))
1186 /* Enable 'send video' buttons and display the preview */
1187 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), TRUE);
1188 gtk_toggle_tool_button_set_active (
1189 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
1191 display_video_preview (self, TRUE);
1195 static void empathy_call_window_dispose (GObject *object);
1196 static void empathy_call_window_finalize (GObject *object);
1199 empathy_call_window_set_property (GObject *object,
1200 guint property_id, const GValue *value, GParamSpec *pspec)
1202 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1204 switch (property_id)
1206 case PROP_CALL_HANDLER:
1207 priv->handler = g_value_dup_object (value);
1210 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1215 empathy_call_window_get_property (GObject *object,
1216 guint property_id, GValue *value, GParamSpec *pspec)
1218 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1220 switch (property_id)
1222 case PROP_CALL_HANDLER:
1223 g_value_set_object (value, priv->handler);
1226 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1231 empathy_call_window_class_init (
1232 EmpathyCallWindowClass *empathy_call_window_class)
1234 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1235 GParamSpec *param_spec;
1237 g_type_class_add_private (empathy_call_window_class,
1238 sizeof (EmpathyCallWindowPriv));
1240 object_class->constructed = empathy_call_window_constructed;
1241 object_class->set_property = empathy_call_window_set_property;
1242 object_class->get_property = empathy_call_window_get_property;
1244 object_class->dispose = empathy_call_window_dispose;
1245 object_class->finalize = empathy_call_window_finalize;
1247 param_spec = g_param_spec_object ("handler",
1248 "handler", "The call handler",
1249 EMPATHY_TYPE_CALL_HANDLER,
1250 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1251 g_object_class_install_property (object_class,
1252 PROP_CALL_HANDLER, param_spec);
1256 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1257 GParamSpec *property, EmpathyCallWindow *self)
1259 DEBUG ("video stream changed");
1260 empathy_call_window_update_avatars_visibility (call, self);
1264 empathy_call_window_dispose (GObject *object)
1266 EmpathyTpCall *call;
1267 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1268 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1270 if (priv->dispose_has_run)
1273 priv->dispose_has_run = TRUE;
1275 g_object_get (priv->handler, "tp-call", &call, NULL);
1279 g_signal_handlers_disconnect_by_func (call,
1280 empathy_call_window_video_stream_changed_cb, object);
1281 g_object_unref (call);
1284 if (priv->handler != NULL)
1285 g_object_unref (priv->handler);
1286 priv->handler = NULL;
1288 if (priv->pipeline != NULL)
1289 g_object_unref (priv->pipeline);
1290 priv->pipeline = NULL;
1292 if (priv->video_input != NULL)
1293 g_object_unref (priv->video_input);
1294 priv->video_input = NULL;
1296 if (priv->audio_input != NULL)
1297 g_object_unref (priv->audio_input);
1298 priv->audio_input = NULL;
1300 if (priv->audio_output != NULL)
1301 g_object_unref (priv->audio_output);
1302 priv->audio_output = NULL;
1304 if (priv->video_tee != NULL)
1305 g_object_unref (priv->video_tee);
1306 priv->video_tee = NULL;
1308 if (priv->fsnotifier != NULL)
1309 g_object_unref (priv->fsnotifier);
1310 priv->fsnotifier = NULL;
1312 if (priv->timer_id != 0)
1313 g_source_remove (priv->timer_id);
1316 if (priv->ui_manager != NULL)
1317 g_object_unref (priv->ui_manager);
1318 priv->ui_manager = NULL;
1320 if (priv->contact != NULL)
1322 g_signal_handlers_disconnect_by_func (priv->contact,
1323 contact_name_changed_cb, self);
1324 g_object_unref (priv->contact);
1325 priv->contact = NULL;
1328 /* release any references held by the object here */
1329 if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1330 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1334 empathy_call_window_finalize (GObject *object)
1336 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1337 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1339 if (priv->video_output_motion_handler_id != 0)
1341 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1342 priv->video_output_motion_handler_id);
1343 priv->video_output_motion_handler_id = 0;
1346 if (priv->bus_message_source_id != 0)
1348 g_source_remove (priv->bus_message_source_id);
1349 priv->bus_message_source_id = 0;
1352 /* free any data held directly by the object here */
1353 g_mutex_free (priv->lock);
1355 g_timer_destroy (priv->timer);
1357 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1362 empathy_call_window_new (EmpathyCallHandler *handler)
1364 return EMPATHY_CALL_WINDOW (
1365 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1369 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1370 GstElement *conference, gpointer user_data)
1372 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1373 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1375 gst_bin_add (GST_BIN (priv->pipeline), conference);
1377 gst_element_set_state (conference, GST_STATE_PLAYING);
1381 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1382 FsMediaType type, FsStreamDirection direction, gpointer user_data)
1384 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1385 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1387 if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1390 if (direction == FS_DIRECTION_RECV)
1393 /* video and direction is send */
1394 return priv->video_input != NULL;
1398 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1400 GstStateChangeReturn state_change_return;
1401 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1403 if (priv->pipeline == NULL)
1406 if (priv->bus_message_source_id != 0)
1408 g_source_remove (priv->bus_message_source_id);
1409 priv->bus_message_source_id = 0;
1412 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1414 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1415 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1417 if (priv->pipeline != NULL)
1418 g_object_unref (priv->pipeline);
1419 priv->pipeline = NULL;
1421 if (priv->video_input != NULL)
1422 g_object_unref (priv->video_input);
1423 priv->video_input = NULL;
1425 if (priv->audio_input != NULL)
1426 g_object_unref (priv->audio_input);
1427 priv->audio_input = NULL;
1429 g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1430 empathy_call_window_mic_volume_changed_cb, self);
1432 if (priv->audio_output != NULL)
1433 g_object_unref (priv->audio_output);
1434 priv->audio_output = NULL;
1436 if (priv->video_tee != NULL)
1437 g_object_unref (priv->video_tee);
1438 priv->video_tee = NULL;
1440 if (priv->video_preview != NULL)
1441 gtk_widget_destroy (priv->video_preview);
1442 priv->video_preview = NULL;
1444 priv->liveadder = NULL;
1445 priv->funnel = NULL;
1451 g_message ("Error: could not destroy pipeline. Closing call window");
1452 gtk_widget_destroy (GTK_WIDGET (self));
1459 empathy_call_window_disconnected (EmpathyCallWindow *self)
1461 gboolean could_disconnect = FALSE;
1462 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1463 gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1465 if (priv->call_state == CONNECTING)
1466 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1468 if (priv->call_state != REDIALING)
1469 priv->call_state = DISCONNECTED;
1471 if (could_reset_pipeline)
1473 gboolean initial_video = empathy_call_handler_has_initial_video (
1475 g_mutex_lock (priv->lock);
1477 g_timer_stop (priv->timer);
1479 if (priv->timer_id != 0)
1480 g_source_remove (priv->timer_id);
1483 g_mutex_unlock (priv->lock);
1485 empathy_call_window_status_message (self, _("Disconnected"));
1487 gtk_action_set_sensitive (priv->redial, TRUE);
1488 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1490 /* Reseting the send_video, camera_buton and mic_button to their
1492 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1493 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1494 gtk_action_set_sensitive (priv->send_video, FALSE);
1495 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1497 gtk_toggle_tool_button_set_active (
1498 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), initial_video);
1499 gtk_toggle_tool_button_set_active (
1500 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1502 gtk_progress_bar_set_fraction (
1503 GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1505 gtk_widget_hide (priv->video_output);
1506 gtk_widget_show (priv->remote_user_avatar_widget);
1508 priv->sending_video = FALSE;
1509 priv->call_started = FALSE;
1511 could_disconnect = TRUE;
1513 /* TODO: display the self avatar of the preview (depends if the "Always
1514 * Show Video Preview" is enabled or not) */
1517 return could_disconnect;
1522 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1525 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1526 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1528 if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1529 empathy_call_window_restart_call (self);
1534 empathy_call_window_channel_stream_closed_cb (EmpathyCallHandler *handler,
1535 TfStream *stream, gpointer user_data)
1537 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1538 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1541 g_object_get (stream, "media-type", &media_type, NULL);
1544 * This assumes that there is only one video stream per channel...
1547 if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1549 if (priv->funnel != NULL)
1553 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1554 (priv->video_output));
1556 gst_element_set_state (output, GST_STATE_NULL);
1557 gst_element_set_state (priv->funnel, GST_STATE_NULL);
1559 gst_bin_remove (GST_BIN (priv->pipeline), output);
1560 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1561 priv->funnel = NULL;
1564 else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1566 if (priv->liveadder != NULL)
1568 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1569 gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1571 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1572 gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1573 priv->liveadder = NULL;
1578 /* Called with global lock held */
1580 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1582 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1585 if (priv->funnel == NULL)
1589 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1590 (priv->video_output));
1592 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1594 gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1595 gst_bin_add (GST_BIN (priv->pipeline), output);
1597 gst_element_link (priv->funnel, output);
1599 gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1600 gst_element_set_state (output, GST_STATE_PLAYING);
1603 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1608 /* Called with global lock held */
1610 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1612 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1615 if (priv->liveadder == NULL)
1617 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1619 gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1620 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1622 gst_element_link (priv->liveadder, priv->audio_output);
1624 gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1625 gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1628 pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1634 empathy_call_window_update_timer (gpointer user_data)
1636 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1637 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1641 time_ = g_timer_elapsed (priv->timer, NULL);
1643 /* Translators: number of minutes:seconds the caller has been connected */
1644 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time_ / 60,
1646 empathy_call_window_status_message (self, str);
1653 display_error (EmpathyCallWindow *self,
1654 EmpathyTpCall *call,
1658 const gchar *details)
1660 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1661 GtkWidget *info_bar;
1662 GtkWidget *content_area;
1669 /* Create info bar */
1670 info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1673 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1675 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1677 /* hbox containing the image and the messages vbox */
1678 hbox = gtk_hbox_new (FALSE, 3);
1679 gtk_container_add (GTK_CONTAINER (content_area), hbox);
1682 image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1683 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1685 /* vbox containing the main message and the details expander */
1686 vbox = gtk_vbox_new (FALSE, 3);
1687 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1690 txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
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_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1701 if (details != NULL)
1703 GtkWidget *expander;
1705 expander = gtk_expander_new (_("Technical Details"));
1707 txt = g_strdup_printf ("<i>%s</i>", details);
1709 label = gtk_label_new (NULL);
1710 gtk_label_set_markup (GTK_LABEL (label), txt);
1711 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1712 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1715 gtk_container_add (GTK_CONTAINER (expander), label);
1716 gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1719 g_signal_connect (info_bar, "response",
1720 G_CALLBACK (gtk_widget_destroy), NULL);
1722 gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1723 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1724 gtk_widget_show_all (info_bar);
1728 media_stream_error_to_txt (EmpathyCallWindow *self,
1729 EmpathyTpCall *call,
1731 TpMediaStreamError error)
1733 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1740 case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1742 return g_strdup_printf (
1743 _("%s's software does not understand any of the audio formats "
1744 "supported by your computer"),
1745 empathy_contact_get_name (priv->contact));
1747 return g_strdup_printf (
1748 _("%s's software does not understand any of the video formats "
1749 "supported by your computer"),
1750 empathy_contact_get_name (priv->contact));
1752 case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1753 return g_strdup_printf (
1754 _("Can't establish a connection to %s. "
1755 "One of you might be on a network that does not allow "
1756 "direct connections."),
1757 empathy_contact_get_name (priv->contact));
1759 case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
1760 return g_strdup (_("There was a failure on the network"));
1762 case TP_MEDIA_STREAM_ERROR_NO_CODECS:
1764 return g_strdup (_("The audio formats necessary for this call "
1765 "are not installed on your computer"));
1767 return g_strdup (_("The video formats necessary for this call "
1768 "are not installed on your computer"));
1770 case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
1771 cm = empathy_tp_call_get_connection_manager (call);
1773 url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
1774 "product=Telepathy&component=%s", cm);
1776 result = g_strdup_printf (
1777 _("Something not expected happened in a Telepathy component. "
1778 "Please <a href=\"%s\">report this bug</a> and attach "
1779 "logs gathered from the 'Debug' window in the Help menu."), url);
1784 case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
1785 return g_strdup (_("There was a failure in the call engine"));
1793 empathy_call_window_stream_error (EmpathyCallWindow *self,
1794 EmpathyTpCall *call,
1803 desc = media_stream_error_to_txt (self, call, audio, code);
1806 /* No description, use the error message. That's not great as it's not
1807 * localized but it's better than nothing. */
1808 display_error (self, call, icon, title, msg, NULL);
1812 display_error (self, call, icon, title, desc, msg);
1818 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
1821 EmpathyCallWindow *self)
1823 empathy_call_window_stream_error (self, call, TRUE, code, msg,
1824 "gnome-stock-mic", _("Can't establish audio stream"));
1828 empathy_call_window_video_stream_error (EmpathyTpCall *call,
1831 EmpathyCallWindow *self)
1833 empathy_call_window_stream_error (self, call, FALSE, code, msg,
1834 "camera-web", _("Can't establish video stream"));
1838 empathy_call_window_connected (gpointer user_data)
1840 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1841 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1842 EmpathyTpCall *call;
1843 gboolean can_send_video;
1845 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1847 can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1848 empathy_contact_can_voip_video (priv->contact);
1850 g_object_get (priv->handler, "tp-call", &call, NULL);
1852 g_signal_connect (call, "notify::video-stream",
1853 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1855 if (empathy_tp_call_has_dtmf (call))
1856 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1858 if (priv->video_input == NULL)
1859 empathy_call_window_set_send_video (self, FALSE);
1861 priv->sending_video = can_send_video ?
1862 empathy_tp_call_is_sending_video (call) : FALSE;
1864 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1865 priv->sending_video && priv->video_input != NULL);
1866 gtk_toggle_tool_button_set_active (
1867 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
1868 priv->sending_video && priv->video_input != NULL);
1869 gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
1870 gtk_action_set_sensitive (priv->send_video, can_send_video);
1872 gtk_action_set_sensitive (priv->redial, FALSE);
1873 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1875 gtk_widget_set_sensitive (priv->mic_button, TRUE);
1877 empathy_call_window_update_avatars_visibility (call, self);
1879 g_object_unref (call);
1881 g_mutex_lock (priv->lock);
1883 priv->timer_id = g_timeout_add_seconds (1,
1884 empathy_call_window_update_timer, self);
1886 g_mutex_unlock (priv->lock);
1888 empathy_call_window_update_timer (self);
1894 /* Called from the streaming thread */
1896 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1897 GstPad *src, guint media_type, gpointer user_data)
1899 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1900 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1904 g_mutex_lock (priv->lock);
1906 if (priv->call_state != CONNECTED)
1908 g_timer_start (priv->timer);
1909 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
1910 priv->call_state = CONNECTED;
1915 case TP_MEDIA_STREAM_TYPE_AUDIO:
1916 pad = empathy_call_window_get_audio_sink_pad (self);
1918 case TP_MEDIA_STREAM_TYPE_VIDEO:
1919 gtk_widget_hide (priv->remote_user_avatar_widget);
1920 gtk_widget_show (priv->video_output);
1921 pad = empathy_call_window_get_video_sink_pad (self);
1924 g_assert_not_reached ();
1927 gst_pad_link (src, pad);
1928 gst_object_unref (pad);
1930 g_mutex_unlock (priv->lock);
1933 /* Called from the streaming thread */
1935 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1936 GstPad *sink, guint media_type, gpointer user_data)
1938 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1939 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1944 case TP_MEDIA_STREAM_TYPE_AUDIO:
1945 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1947 pad = gst_element_get_static_pad (priv->audio_input, "src");
1948 gst_pad_link (pad, sink);
1950 gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1952 case TP_MEDIA_STREAM_TYPE_VIDEO:
1953 if (priv->video_input != NULL)
1955 if (priv->video_tee != NULL)
1957 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1958 gst_pad_link (pad, sink);
1963 g_assert_not_reached ();
1969 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1971 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1972 GstElement *preview;
1974 DEBUG ("remove video input");
1975 preview = empathy_video_widget_get_element (
1976 EMPATHY_VIDEO_WIDGET (priv->video_preview));
1978 gst_element_set_state (priv->video_input, GST_STATE_NULL);
1979 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1980 gst_element_set_state (preview, GST_STATE_NULL);
1982 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1983 priv->video_tee, preview, NULL);
1985 g_object_unref (priv->video_input);
1986 priv->video_input = NULL;
1987 g_object_unref (priv->video_tee);
1988 priv->video_tee = NULL;
1989 gtk_widget_destroy (priv->video_preview);
1990 priv->video_preview = NULL;
1992 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1993 gtk_toggle_tool_button_set_active (
1994 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
1995 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1996 gtk_action_set_sensitive (priv->send_video, FALSE);
1998 gtk_widget_show (priv->self_user_avatar_widget);
2003 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2006 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2007 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2010 empathy_call_handler_bus_message (priv->handler, bus, message);
2012 switch (GST_MESSAGE_TYPE (message))
2014 case GST_MESSAGE_STATE_CHANGED:
2015 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2017 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2018 if (newstate == GST_STATE_PAUSED)
2019 empathy_call_window_setup_video_input (self);
2021 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2022 !priv->call_started)
2024 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2025 if (newstate == GST_STATE_PAUSED)
2027 priv->call_started = TRUE;
2028 empathy_call_handler_start_call (priv->handler);
2029 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2033 case GST_MESSAGE_ERROR:
2035 GError *error = NULL;
2036 GstElement *gst_error;
2039 gst_message_parse_error (message, &error, &debug);
2040 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2042 g_message ("Element error: %s -- %s\n", error->message, debug);
2044 if (g_str_has_prefix (gst_element_get_name (gst_error),
2045 VIDEO_INPUT_ERROR_PREFIX))
2047 /* Remove the video input and continue */
2048 if (priv->video_input != NULL)
2049 empathy_call_window_remove_video_input (self);
2050 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2054 empathy_call_window_disconnected (self);
2056 g_error_free (error);
2067 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
2068 EmpathyCallWindow *window)
2070 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2072 if (empathy_tp_call_is_receiving_video (call))
2074 gtk_widget_hide (priv->remote_user_avatar_widget);
2075 gtk_widget_show (priv->video_output);
2079 gtk_widget_hide (priv->video_output);
2080 gtk_widget_show (priv->remote_user_avatar_widget);
2085 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
2087 EmpathyCallWindow *self)
2089 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2090 EmpathyTpCall *call;
2092 g_object_get (priv->handler, "tp-call", &call, NULL);
2096 empathy_signal_connect_weak (call, "audio-stream-error",
2097 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
2098 empathy_signal_connect_weak (call, "video-stream-error",
2099 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
2101 g_object_unref (call);
2105 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2107 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2108 EmpathyTpCall *call;
2110 g_signal_connect (priv->handler, "conference-added",
2111 G_CALLBACK (empathy_call_window_conference_added_cb), window);
2112 g_signal_connect (priv->handler, "request-resource",
2113 G_CALLBACK (empathy_call_window_request_resource_cb), window);
2114 g_signal_connect (priv->handler, "closed",
2115 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2116 g_signal_connect (priv->handler, "src-pad-added",
2117 G_CALLBACK (empathy_call_window_src_added_cb), window);
2118 g_signal_connect (priv->handler, "sink-pad-added",
2119 G_CALLBACK (empathy_call_window_sink_added_cb), window);
2120 g_signal_connect (priv->handler, "stream-closed",
2121 G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
2123 g_object_get (priv->handler, "tp-call", &call, NULL);
2126 empathy_signal_connect_weak (call, "audio-stream-error",
2127 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
2128 empathy_signal_connect_weak (call, "video-stream-error",
2129 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
2131 g_object_unref (call);
2135 /* tp-call doesn't exist yet, we'll connect signals once it has been
2137 g_signal_connect (priv->handler, "notify::tp-call",
2138 G_CALLBACK (call_handler_notify_tp_call_cb), window);
2141 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2145 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2146 EmpathyCallWindow *window)
2148 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2150 if (priv->pipeline != NULL)
2152 if (priv->bus_message_source_id != 0)
2154 g_source_remove (priv->bus_message_source_id);
2155 priv->bus_message_source_id = 0;
2158 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2161 if (priv->call_state == CONNECTING)
2162 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2168 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2171 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2173 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2178 gtk_widget_hide (priv->sidebar);
2179 gtk_widget_hide (menu);
2180 gtk_widget_hide (priv->vbox);
2181 gtk_widget_hide (priv->statusbar);
2182 gtk_widget_hide (priv->toolbar);
2186 if (priv->sidebar_was_visible_before_fs)
2187 gtk_widget_show (priv->sidebar);
2189 gtk_widget_show (menu);
2190 gtk_widget_show (priv->vbox);
2191 gtk_widget_show (priv->statusbar);
2192 gtk_widget_show (priv->toolbar);
2194 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2195 priv->original_height_before_fs);
2200 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2202 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2204 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2205 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2206 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2207 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2208 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2209 priv->video_output, TRUE, TRUE,
2210 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2212 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2213 priv->vbox, TRUE, TRUE,
2214 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2219 empathy_call_window_state_event_cb (GtkWidget *widget,
2220 GdkEventWindowState *event, EmpathyCallWindow *window)
2222 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2224 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2225 gboolean set_fullscreen = event->new_window_state &
2226 GDK_WINDOW_STATE_FULLSCREEN;
2230 gboolean sidebar_was_visible;
2231 GtkAllocation allocation;
2232 gint original_width, original_height;
2234 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2235 original_width = allocation.width;
2236 original_height = allocation.height;
2238 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2240 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2241 priv->original_width_before_fs = original_width;
2242 priv->original_height_before_fs = original_height;
2244 if (priv->video_output_motion_handler_id == 0 &&
2245 priv->video_output != NULL)
2247 priv->video_output_motion_handler_id = g_signal_connect (
2248 G_OBJECT (priv->video_output), "motion-notify-event",
2249 G_CALLBACK (empathy_call_window_video_output_motion_notify),
2255 if (priv->video_output_motion_handler_id != 0)
2257 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2258 priv->video_output_motion_handler_id);
2259 priv->video_output_motion_handler_id = 0;
2263 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2265 show_controls (window, set_fullscreen);
2266 show_borders (window, set_fullscreen);
2267 gtk_action_set_stock_id (priv->menu_fullscreen,
2268 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2269 priv->is_fullscreen = set_fullscreen;
2276 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2277 EmpathyCallWindow *window)
2279 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2281 int w, h, handle_size;
2282 GtkAllocation allocation, sidebar_allocation;
2284 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2285 w = allocation.width;
2286 h = allocation.height;
2288 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2290 gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2291 if (gtk_toggle_button_get_active (toggle))
2293 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2294 gtk_widget_show (priv->sidebar);
2295 w += sidebar_allocation.width + handle_size;
2299 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2300 w -= sidebar_allocation.width + handle_size;
2301 gtk_widget_hide (priv->sidebar);
2304 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2307 gtk_window_resize (GTK_WINDOW (window), w, h);
2311 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2314 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2315 EmpathyTpCall *call;
2317 priv->sending_video = send;
2319 /* When we start sending video, we want to show the video preview by
2321 display_video_preview (window, send);
2323 g_object_get (priv->handler, "tp-call", &call, NULL);
2324 DEBUG ("%s sending video", send ? "start": "stop");
2325 empathy_tp_call_request_video_stream_direction (call, send);
2326 g_object_unref (call);
2330 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
2331 EmpathyCallWindow *window)
2333 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2336 if (priv->call_state != CONNECTED)
2339 active = (gtk_toggle_action_get_active (toggle));
2341 if (priv->sending_video == active)
2344 empathy_call_window_set_send_video (window, active);
2345 gtk_toggle_tool_button_set_active (
2346 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), active);
2350 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2351 EmpathyCallWindow *window)
2353 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2356 if (priv->audio_input == NULL)
2359 active = (gtk_toggle_tool_button_get_active (toggle));
2363 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2365 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2369 /* TODO, Instead of setting the input volume to 0 we should probably
2370 * stop sending but this would cause the audio call to drop if both
2371 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2372 * in the future. GNOME #574574
2374 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2376 gtk_adjustment_set_value (priv->audio_input_adj, 0);
2381 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2382 EmpathyCallWindow *window)
2384 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2386 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2391 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2392 EmpathyCallWindow *window)
2394 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2396 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2401 empathy_call_window_hangup_cb (gpointer object,
2402 EmpathyCallWindow *window)
2404 if (empathy_call_window_disconnected (window))
2405 gtk_widget_destroy (GTK_WIDGET (window));
2409 empathy_call_window_restart_call (EmpathyCallWindow *window)
2412 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2414 gtk_widget_destroy (priv->remote_user_output_hbox);
2415 gtk_widget_destroy (priv->self_user_output_hbox);
2417 priv->pipeline = gst_pipeline_new (NULL);
2418 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2419 priv->bus_message_source_id = gst_bus_add_watch (bus,
2420 empathy_call_window_bus_message, window);
2422 empathy_call_window_setup_remote_frame (bus, window);
2423 empathy_call_window_setup_self_frame (bus, window);
2425 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2426 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2428 /* While the call was disconnected, the input volume might have changed.
2429 * However, since the audio_input source was destroyed, its volume has not
2430 * been updated during that time. That's why we manually update it here */
2431 empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2433 g_object_unref (bus);
2435 gtk_widget_show_all (priv->content_hbox);
2437 priv->outgoing = TRUE;
2438 empathy_call_window_set_state_connecting (window);
2440 priv->call_started = TRUE;
2441 empathy_call_handler_start_call (priv->handler);
2442 empathy_call_window_setup_avatars (window, priv->handler);
2443 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2445 gtk_action_set_sensitive (priv->redial, FALSE);
2446 gtk_widget_set_sensitive (priv->redial_button, FALSE);
2450 empathy_call_window_redial_cb (gpointer object,
2451 EmpathyCallWindow *window)
2453 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2455 if (priv->call_state == CONNECTED)
2456 priv->call_state = REDIALING;
2458 empathy_call_handler_stop_call (priv->handler);
2460 if (priv->call_state != CONNECTED)
2461 empathy_call_window_restart_call (window);
2465 empathy_call_window_fullscreen_cb (gpointer object,
2466 EmpathyCallWindow *window)
2468 empathy_call_window_fullscreen_toggle (window);
2472 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2474 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2476 if (priv->is_fullscreen)
2477 gtk_window_unfullscreen (GTK_WINDOW (window));
2479 gtk_window_fullscreen (GTK_WINDOW (window));
2483 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2484 GdkEventButton *event, EmpathyCallWindow *window)
2486 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2488 empathy_call_window_video_menu_popup (window, event->button);
2496 empathy_call_window_key_press_cb (GtkWidget *video_output,
2497 GdkEventKey *event, EmpathyCallWindow *window)
2499 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2501 if (priv->is_fullscreen && event->keyval == GDK_Escape)
2503 /* Since we are in fullscreen mode, toggling will bring us back to
2505 empathy_call_window_fullscreen_toggle (window);
2513 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2514 GdkEventMotion *event, EmpathyCallWindow *window)
2516 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2518 if (priv->is_fullscreen)
2520 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2527 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2531 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2533 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2535 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2536 button, gtk_get_current_event_time ());
2537 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2541 empathy_call_window_status_message (EmpathyCallWindow *window,
2544 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2546 if (priv->context_id == 0)
2548 priv->context_id = gtk_statusbar_get_context_id (
2549 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2553 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2556 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2561 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2562 gdouble value, EmpathyCallWindow *window)
2564 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2566 if (priv->audio_output == NULL)
2569 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2573 /* block all the signals related to camera control widgets. This is useful
2574 * when we are manually updating the UI and so don't want to fire the
2577 block_camera_control_signals (EmpathyCallWindow *self)
2579 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2581 g_signal_handlers_block_by_func (priv->tool_button_camera_off,
2582 tool_button_camera_off_toggled_cb, self);
2583 g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
2584 tool_button_camera_preview_toggled_cb, self);
2585 g_signal_handlers_block_by_func (priv->tool_button_camera_on,
2586 tool_button_camera_on_toggled_cb, self);
2590 unblock_camera_control_signals (EmpathyCallWindow *self)
2592 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2594 g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
2595 tool_button_camera_off_toggled_cb, self);
2596 g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
2597 tool_button_camera_preview_toggled_cb, self);
2598 g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
2599 tool_button_camera_on_toggled_cb, self);