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;
131 GtkAction *menu_fullscreen;
132 GtkAction *action_camera;
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_mic_toggled_cb (
218 GtkToggleToolButton *toggle, EmpathyCallWindow *window);
220 static void empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
221 EmpathyCallWindow *window);
223 static void empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
224 EmpathyCallWindow *window);
226 static void empathy_call_window_hangup_cb (gpointer object,
227 EmpathyCallWindow *window);
229 static void empathy_call_window_fullscreen_cb (gpointer object,
230 EmpathyCallWindow *window);
232 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
234 static gboolean empathy_call_window_video_button_press_cb (
235 GtkWidget *video_output, GdkEventButton *event, EmpathyCallWindow *window);
237 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
238 GdkEventKey *event, EmpathyCallWindow *window);
240 static gboolean empathy_call_window_video_output_motion_notify (
241 GtkWidget *widget, GdkEventMotion *event, EmpathyCallWindow *window);
243 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
246 static void empathy_call_window_redial_cb (gpointer object,
247 EmpathyCallWindow *window);
249 static void empathy_call_window_restart_call (EmpathyCallWindow *window);
251 static void empathy_call_window_status_message (EmpathyCallWindow *window,
254 static void empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
255 EmpathyCallWindow *window);
257 static gboolean empathy_call_window_bus_message (GstBus *bus,
258 GstMessage *message, gpointer user_data);
261 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
262 gdouble value, EmpathyCallWindow *window);
264 static void block_camera_control_signals (EmpathyCallWindow *self);
265 static void unblock_camera_control_signals (EmpathyCallWindow *self);
268 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
270 EmpathyCallWindowPriv *priv = GET_PRIV (self);
271 GtkToolItem *tool_item;
272 GtkWidget *camera_off_icon;
273 GdkPixbuf *pixbuf, *modded_pixbuf;
275 /* set the icon of the 'camera off' button by greying off the webcam icon */
276 pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
277 GTK_ICON_SIZE_SMALL_TOOLBAR);
279 modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
280 gdk_pixbuf_get_width (pixbuf),
281 gdk_pixbuf_get_height (pixbuf));
283 gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
284 g_object_unref (pixbuf);
286 camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
287 g_object_unref (modded_pixbuf);
288 gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
289 priv->tool_button_camera_off), camera_off_icon);
291 /* Add an empty expanded GtkToolItem so the volume button is at the end of
293 tool_item = gtk_tool_item_new ();
294 gtk_tool_item_set_expand (tool_item, TRUE);
295 gtk_widget_show (GTK_WIDGET (tool_item));
296 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
298 priv->volume_button = gtk_volume_button_new ();
299 /* FIXME listen to the audiosinks signals and update the button according to
300 * that, for now starting out at 1.0 and assuming only the app changes the
302 gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
303 g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
304 G_CALLBACK (empathy_call_window_volume_changed_cb), self);
306 tool_item = gtk_tool_item_new ();
307 gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
308 gtk_widget_show_all (GTK_WIDGET (tool_item));
309 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
313 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
315 EmpathyCallWindowPriv *priv = GET_PRIV (window);
320 g_object_get (priv->handler, "tp-call", &call, NULL);
322 button_quark = g_quark_from_static_string (BUTTON_ID);
323 event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
326 empathy_tp_call_start_tone (call, event);
328 g_object_unref (call);
332 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
334 EmpathyCallWindowPriv *priv = GET_PRIV (window);
337 g_object_get (priv->handler, "tp-call", &call, NULL);
339 empathy_tp_call_stop_tone (call);
341 g_object_unref (call);
345 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
353 } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
354 { "2", TP_DTMF_EVENT_DIGIT_2 },
355 { "3", TP_DTMF_EVENT_DIGIT_3 },
356 { "4", TP_DTMF_EVENT_DIGIT_4 },
357 { "5", TP_DTMF_EVENT_DIGIT_5 },
358 { "6", TP_DTMF_EVENT_DIGIT_6 },
359 { "7", TP_DTMF_EVENT_DIGIT_7 },
360 { "8", TP_DTMF_EVENT_DIGIT_8 },
361 { "9", TP_DTMF_EVENT_DIGIT_9 },
362 { "#", TP_DTMF_EVENT_HASH },
363 { "0", TP_DTMF_EVENT_DIGIT_0 },
364 { "*", TP_DTMF_EVENT_ASTERISK },
367 button_quark = g_quark_from_static_string (BUTTON_ID);
369 table = gtk_table_new (4, 3, TRUE);
371 for (i = 0; dtmfbuttons[i].label != NULL; i++)
373 GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
374 gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
375 i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
377 g_object_set_qdata (G_OBJECT (button), button_quark,
378 GUINT_TO_POINTER (dtmfbuttons[i].event));
380 g_signal_connect (G_OBJECT (button), "pressed",
381 G_CALLBACK (dtmf_button_pressed_cb), self);
382 g_signal_connect (G_OBJECT (button), "released",
383 G_CALLBACK (dtmf_button_released_cb), self);
390 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
391 gchar *label_text, GtkWidget *bin)
393 GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
394 GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
395 GtkWidget *label = gtk_label_new (label_text);
397 gtk_widget_set_sensitive (scale, FALSE);
399 gtk_container_add (GTK_CONTAINER (bin), vbox);
401 gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
402 gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
403 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
409 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
410 EmpathyCallWindow *self)
413 EmpathyCallWindowPriv *priv = GET_PRIV (self);
415 empathy_video_src_set_channel (priv->video_input,
416 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
420 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
421 EmpathyCallWindow *self)
424 EmpathyCallWindowPriv *priv = GET_PRIV (self);
426 empathy_video_src_set_channel (priv->video_input,
427 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
431 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
432 EmpathyCallWindow *self)
435 EmpathyCallWindowPriv *priv = GET_PRIV (self);
437 empathy_video_src_set_channel (priv->video_input,
438 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
443 empathy_call_window_create_video_input (EmpathyCallWindow *self)
445 EmpathyCallWindowPriv *priv = GET_PRIV (self);
448 hbox = gtk_hbox_new (TRUE, 3);
450 priv->video_contrast = empathy_call_window_create_video_input_add_slider (
451 self, _("Contrast"), hbox);
453 priv->video_brightness = empathy_call_window_create_video_input_add_slider (
454 self, _("Brightness"), hbox);
456 priv->video_gamma = empathy_call_window_create_video_input_add_slider (
457 self, _("Gamma"), hbox);
463 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
465 EmpathyCallWindowPriv *priv = GET_PRIV (self);
469 supported = empathy_video_src_get_supported_channels (priv->video_input);
471 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
473 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
475 gtk_adjustment_set_value (adj,
476 empathy_video_src_get_channel (priv->video_input,
477 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
479 g_signal_connect (G_OBJECT (adj), "value-changed",
480 G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
482 gtk_widget_set_sensitive (priv->video_contrast, TRUE);
485 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
487 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
489 gtk_adjustment_set_value (adj,
490 empathy_video_src_get_channel (priv->video_input,
491 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
493 g_signal_connect (G_OBJECT (adj), "value-changed",
494 G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
495 gtk_widget_set_sensitive (priv->video_brightness, TRUE);
498 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
500 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
502 gtk_adjustment_set_value (adj,
503 empathy_video_src_get_channel (priv->video_input,
504 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
506 g_signal_connect (G_OBJECT (adj), "value-changed",
507 G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
508 gtk_widget_set_sensitive (priv->video_gamma, TRUE);
513 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
514 EmpathyCallWindow *self)
516 EmpathyCallWindowPriv *priv = GET_PRIV (self);
519 if (priv->audio_input == NULL)
522 volume = gtk_adjustment_get_value (adj)/100.0;
524 /* Don't store the volume because of muting */
525 if (volume > 0 || gtk_toggle_tool_button_get_active (
526 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
527 priv->volume = volume;
529 /* Ensure that the toggle button is active if the volume is > 0 and inactive
530 * if it's smaller than 0 */
531 if ((volume > 0) != gtk_toggle_tool_button_get_active (
532 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
533 gtk_toggle_tool_button_set_active (
534 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
536 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
541 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
542 gdouble level, EmpathyCallWindow *window)
545 EmpathyCallWindowPriv *priv = GET_PRIV (window);
547 value = CLAMP (pow (10, level / 20), 0.0, 1.0);
548 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
553 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
555 EmpathyCallWindowPriv *priv = GET_PRIV (self);
556 GtkWidget *hbox, *vbox, *label;
558 hbox = gtk_hbox_new (TRUE, 3);
560 vbox = gtk_vbox_new (FALSE, 3);
561 gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
563 priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
564 gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
565 label = gtk_label_new (_("Volume"));
567 priv->audio_input_adj = gtk_range_get_adjustment (
568 GTK_RANGE (priv->volume_scale));
569 priv->volume = empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
570 (priv->audio_input));
571 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
573 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
574 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
576 gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
577 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
579 priv->volume_progress_bar = gtk_progress_bar_new ();
580 gtk_progress_bar_set_orientation (
581 GTK_PROGRESS_BAR (priv->volume_progress_bar),
582 GTK_PROGRESS_BOTTOM_TO_TOP);
583 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
586 gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
593 empathy_call_window_setup_remote_frame (GstBus *bus, EmpathyCallWindow *self)
595 EmpathyCallWindowPriv *priv = GET_PRIV (self);
597 /* Initializing all the content (UI and output gst elements) related to the
599 priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
601 priv->remote_user_avatar_widget = gtk_image_new ();
602 gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
603 priv->remote_user_avatar_widget, TRUE, TRUE, 0);
605 priv->video_output = empathy_video_widget_new (bus);
606 gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
607 priv->video_output, TRUE, TRUE, 0);
609 gtk_widget_add_events (priv->video_output,
610 GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
611 g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
612 G_CALLBACK (empathy_call_window_video_button_press_cb), self);
614 gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
615 priv->remote_user_output_hbox);
617 priv->audio_output = empathy_audio_sink_new ();
618 gst_object_ref (priv->audio_output);
619 gst_object_sink (priv->audio_output);
623 empathy_call_window_setup_self_frame (GstBus *bus, EmpathyCallWindow *self)
625 EmpathyCallWindowPriv *priv = GET_PRIV (self);
627 /* Initializing all the content (UI and input gst elements) related to the
628 self contact, except for the video preview widget. This widget is only
629 initialized when the "show video preview" option is activated */
630 priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
632 priv->self_user_avatar_widget = gtk_image_new ();
633 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
634 priv->self_user_avatar_widget, TRUE, TRUE, 0);
636 gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
637 priv->self_user_output_hbox);
639 priv->video_input = empathy_video_src_new ();
640 gst_object_ref (priv->video_input);
641 gst_object_sink (priv->video_input);
643 priv->audio_input = empathy_audio_src_new ();
644 gst_object_ref (priv->audio_input);
645 gst_object_sink (priv->audio_input);
647 empathy_signal_connect_weak (priv->audio_input, "peak-level-changed",
648 G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
653 empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
655 EmpathyCallWindowPriv *priv = GET_PRIV (window);
657 GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
659 if (priv->video_preview != NULL)
661 /* Since the video preview and the video tee are initialized and freed
662 at the same time, if one is initialized, then the other one should
664 g_assert (priv->video_tee != NULL);
668 DEBUG ("Create video preview");
669 g_assert (priv->video_tee == NULL);
671 priv->video_tee = gst_element_factory_make ("tee", NULL);
672 gst_object_ref (priv->video_tee);
673 gst_object_sink (priv->video_tee);
675 priv->video_preview = empathy_video_widget_new_with_size (bus,
676 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
677 g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
678 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
679 priv->video_preview, TRUE, TRUE, 0);
681 preview = empathy_video_widget_get_element (
682 EMPATHY_VIDEO_WIDGET (priv->video_preview));
683 gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
684 priv->video_tee, preview, NULL);
685 gst_element_link_many (priv->video_input, priv->video_tee,
688 g_object_unref (bus);
690 gst_element_set_state (preview, GST_STATE_PLAYING);
691 gst_element_set_state (priv->video_input, GST_STATE_PLAYING);
692 gst_element_set_state (priv->video_tee, GST_STATE_PLAYING);
696 display_video_preview (EmpathyCallWindow *self,
699 EmpathyCallWindowPriv *priv = GET_PRIV (self);
703 /* Display the preview and hide the self avatar */
704 DEBUG ("Show video preview");
706 if (priv->video_preview == NULL)
707 empathy_call_window_setup_video_preview (self);
708 gtk_widget_show (priv->video_preview);
709 gtk_widget_hide (priv->self_user_avatar_widget);
713 /* Display the self avatar and hide the preview */
714 DEBUG ("Show self avatar");
716 if (priv->video_preview != NULL)
717 gtk_widget_hide (priv->video_preview);
718 gtk_widget_show (priv->self_user_avatar_widget);
723 empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
725 EmpathyCallWindowPriv *priv = GET_PRIV (window);
727 empathy_call_window_status_message (window, _("Connecting..."));
728 priv->call_state = CONNECTING;
731 empathy_sound_start_playing (GTK_WIDGET (window),
732 EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
736 disable_camera (EmpathyCallWindow *self)
738 EmpathyCallWindowPriv *priv = GET_PRIV (self);
740 if (priv->camera_state == CAMERA_STATE_OFF)
743 DEBUG ("disable camera");
745 display_video_preview (self, FALSE);
747 if (priv->camera_state == CAMERA_STATE_ON)
748 empathy_call_window_set_send_video (self, FALSE);
750 block_camera_control_signals (self);
751 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
752 priv->tool_button_camera_on), FALSE);
753 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
754 priv->tool_button_camera_preview), FALSE);
756 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
757 priv->tool_button_camera_off), TRUE);
758 gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
760 unblock_camera_control_signals (self);
762 priv->camera_state = CAMERA_STATE_OFF;
766 tool_button_camera_off_toggled_cb (GtkToggleToolButton *toggle,
767 EmpathyCallWindow *self)
769 EmpathyCallWindowPriv *priv = GET_PRIV (self);
771 if (!gtk_toggle_tool_button_get_active (toggle))
773 if (priv->camera_state == CAMERA_STATE_OFF)
775 /* We can't change the state by disabling the button */
776 block_camera_control_signals (self);
777 gtk_toggle_tool_button_set_active (toggle, TRUE);
778 unblock_camera_control_signals (self);
784 disable_camera (self);
788 enable_preview (EmpathyCallWindow *self)
790 EmpathyCallWindowPriv *priv = GET_PRIV (self);
792 if (priv->camera_state == CAMERA_STATE_PREVIEW)
795 DEBUG ("enable preview");
797 if (priv->camera_state == CAMERA_STATE_ON)
798 /* preview is already displayed so we just have to stop sending */
799 empathy_call_window_set_send_video (self, FALSE);
801 display_video_preview (self, TRUE);
803 block_camera_control_signals (self);
804 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
805 priv->tool_button_camera_off), FALSE);
806 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
807 priv->tool_button_camera_on), FALSE);
809 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
810 priv->tool_button_camera_preview), TRUE);
811 gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
812 CAMERA_STATE_PREVIEW);
813 unblock_camera_control_signals (self);
815 priv->camera_state = CAMERA_STATE_PREVIEW;
819 tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
820 EmpathyCallWindow *self)
822 EmpathyCallWindowPriv *priv = GET_PRIV (self);
824 if (!gtk_toggle_tool_button_get_active (toggle))
826 if (priv->camera_state == CAMERA_STATE_PREVIEW)
828 /* We can't change the state by disabling the button */
829 block_camera_control_signals (self);
830 gtk_toggle_tool_button_set_active (toggle, TRUE);
831 unblock_camera_control_signals (self);
837 enable_preview (self);
841 enable_camera (EmpathyCallWindow *self)
843 EmpathyCallWindowPriv *priv = GET_PRIV (self);
845 if (priv->camera_state == CAMERA_STATE_ON)
848 DEBUG ("enable camera");
850 empathy_call_window_set_send_video (self, TRUE);
852 block_camera_control_signals (self);
853 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
854 priv->tool_button_camera_off), FALSE);
855 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
856 priv->tool_button_camera_preview), FALSE);
858 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
859 priv->tool_button_camera_on), TRUE);
860 gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
862 unblock_camera_control_signals (self);
864 priv->camera_state = CAMERA_STATE_ON;
868 tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
869 EmpathyCallWindow *self)
871 EmpathyCallWindowPriv *priv = GET_PRIV (self);
873 if (!gtk_toggle_tool_button_get_active (toggle))
875 if (priv->camera_state == CAMERA_STATE_ON)
877 /* We can't change the state by disabling the button */
878 block_camera_control_signals (self);
879 gtk_toggle_tool_button_set_active (toggle, TRUE);
880 unblock_camera_control_signals (self);
886 enable_camera (self);
890 action_camera_change_cb (GtkRadioAction *action,
891 GtkRadioAction *current,
892 EmpathyCallWindow *self)
896 state = gtk_radio_action_get_current_value (current);
900 case CAMERA_STATE_OFF:
901 disable_camera (self);
904 case CAMERA_STATE_PREVIEW:
905 enable_preview (self);
908 case CAMERA_STATE_ON:
909 enable_camera (self);
913 g_assert_not_reached ();
918 empathy_call_window_init (EmpathyCallWindow *self)
920 EmpathyCallWindowPriv *priv = GET_PRIV (self);
929 GError *error = NULL;
931 filename = empathy_file_lookup ("empathy-call-window.ui", "src");
932 gui = empathy_builder_get_file (filename,
933 "call_window_vbox", &top_vbox,
934 "errors_vbox", &priv->errors_vbox,
936 "statusbar", &priv->statusbar,
937 "redial", &priv->redial_button,
938 "microphone", &priv->mic_button,
939 "toolbar", &priv->toolbar,
940 "menuredial", &priv->redial,
941 "ui_manager", &priv->ui_manager,
942 "menufullscreen", &priv->menu_fullscreen,
943 "camera_off", &priv->tool_button_camera_off,
944 "camera_preview", &priv->tool_button_camera_preview,
945 "camera_on", &priv->tool_button_camera_on,
946 "action_camera_off", &priv->action_camera,
950 empathy_builder_connect (gui, self,
951 "menuhangup", "activate", empathy_call_window_hangup_cb,
952 "hangup", "clicked", empathy_call_window_hangup_cb,
953 "menuredial", "activate", empathy_call_window_redial_cb,
954 "redial", "clicked", empathy_call_window_redial_cb,
955 "microphone", "toggled", empathy_call_window_mic_toggled_cb,
956 "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
957 "camera_off", "toggled", tool_button_camera_off_toggled_cb,
958 "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
959 "camera_on", "toggled", tool_button_camera_on_toggled_cb,
960 "action_camera_off", "changed", action_camera_change_cb,
963 priv->lock = g_mutex_new ();
965 gtk_container_add (GTK_CONTAINER (self), top_vbox);
967 priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
968 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
969 CONTENT_HBOX_BORDER_WIDTH);
970 gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
972 priv->pipeline = gst_pipeline_new (NULL);
973 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
974 priv->bus_message_source_id = gst_bus_add_watch (bus,
975 empathy_call_window_bus_message, self);
977 priv->fsnotifier = fs_element_added_notifier_new ();
978 fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
980 keyfile = g_key_file_new ();
981 filename = empathy_file_lookup ("element-properties", "data");
982 if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
984 fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
989 g_warning ("Could not load element-properties file: %s", error->message);
990 g_key_file_free (keyfile);
991 g_clear_error (&error);
996 priv->remote_user_output_frame = gtk_frame_new (NULL);
997 gtk_widget_set_size_request (priv->remote_user_output_frame,
998 EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
999 gtk_box_pack_start (GTK_BOX (priv->content_hbox),
1000 priv->remote_user_output_frame, TRUE, TRUE,
1001 CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1002 empathy_call_window_setup_remote_frame (bus, self);
1004 priv->self_user_output_frame = gtk_frame_new (NULL);
1005 gtk_widget_set_size_request (priv->self_user_output_frame,
1006 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
1008 priv->vbox = gtk_vbox_new (FALSE, 3);
1009 gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
1010 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1011 gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
1013 empathy_call_window_setup_self_frame (bus, self);
1015 empathy_call_window_setup_toolbar (self);
1017 g_object_unref (bus);
1019 priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
1020 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1021 g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
1022 G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
1024 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1026 h = gtk_hbox_new (FALSE, 3);
1027 gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
1028 gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
1030 priv->sidebar = empathy_sidebar_new ();
1031 g_signal_connect (G_OBJECT (priv->sidebar),
1032 "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
1033 g_signal_connect (G_OBJECT (priv->sidebar),
1034 "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
1035 gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
1037 priv->dtmf_panel = empathy_call_window_create_dtmf (self);
1038 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
1041 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1043 page = empathy_call_window_create_audio_input (self);
1044 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
1047 page = empathy_call_window_create_video_input (self);
1048 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
1051 gtk_widget_show_all (top_vbox);
1053 gtk_widget_hide (priv->sidebar);
1055 priv->fullscreen = empathy_call_window_fullscreen_new (self);
1056 empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1057 priv->video_output);
1058 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1059 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1061 g_signal_connect (G_OBJECT (self), "realize",
1062 G_CALLBACK (empathy_call_window_realized_cb), self);
1064 g_signal_connect (G_OBJECT (self), "delete-event",
1065 G_CALLBACK (empathy_call_window_delete_cb), self);
1067 g_signal_connect (G_OBJECT (self), "window-state-event",
1068 G_CALLBACK (empathy_call_window_state_event_cb), self);
1070 g_signal_connect (G_OBJECT (self), "key-press-event",
1071 G_CALLBACK (empathy_call_window_key_press_cb), self);
1073 priv->timer = g_timer_new ();
1075 g_object_ref (priv->ui_manager);
1076 g_object_unref (gui);
1079 /* Instead of specifying a width and a height, we specify only one size. That's
1080 because we want a square avatar icon. */
1082 init_contact_avatar_with_size (EmpathyContact *contact,
1083 GtkWidget *image_widget,
1086 GdkPixbuf *pixbuf_avatar = NULL;
1088 if (contact != NULL)
1090 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1094 if (pixbuf_avatar == NULL)
1096 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
1100 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1104 set_window_title (EmpathyCallWindow *self)
1106 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1109 /* translators: Call is a noun and %s is the contact name. This string
1110 * is used in the window title */
1111 tmp = g_strdup_printf (_("Call with %s"),
1112 empathy_contact_get_name (priv->contact));
1113 gtk_window_set_title (GTK_WINDOW (self), tmp);
1118 contact_name_changed_cb (EmpathyContact *contact,
1119 GParamSpec *pspec, EmpathyCallWindow *self)
1121 set_window_title (self);
1125 contact_avatar_changed_cb (EmpathyContact *contact,
1126 GParamSpec *pspec, GtkWidget *avatar_widget)
1130 size = avatar_widget->allocation.height;
1134 /* the widget is not allocated yet, set a default size */
1135 size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1136 REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1139 init_contact_avatar_with_size (contact, avatar_widget, size);
1143 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
1144 EmpathyContact *contact, const GError *error, gpointer user_data,
1145 GObject *weak_object)
1147 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1148 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1150 init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1151 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1153 g_signal_connect (contact, "notify::avatar",
1154 G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1158 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1159 EmpathyCallHandler *handler)
1161 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1163 g_object_get (handler, "contact", &(priv->contact), NULL);
1165 if (priv->contact != NULL)
1167 TpConnection *connection;
1168 EmpathyTpContactFactory *factory;
1170 set_window_title (self);
1172 g_signal_connect (priv->contact, "notify::name",
1173 G_CALLBACK (contact_name_changed_cb), self);
1174 g_signal_connect (priv->contact, "notify::avatar",
1175 G_CALLBACK (contact_avatar_changed_cb),
1176 priv->remote_user_avatar_widget);
1178 /* Retreiving the self avatar */
1179 connection = empathy_contact_get_connection (priv->contact);
1180 factory = empathy_tp_contact_factory_dup_singleton (connection);
1181 empathy_tp_contact_factory_get_from_handle (factory,
1182 tp_connection_get_self_handle (connection),
1183 empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1185 g_object_unref (factory);
1189 g_warning ("call handler doesn't have a contact");
1190 /* translators: Call is a noun. This string is used in the window
1192 gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1194 /* Since we can't access the remote contact, we can't get a connection
1195 to it and can't get the self contact (and its avatar). This means
1196 that we have to manually set the self avatar. */
1197 init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1198 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1201 init_contact_avatar_with_size (priv->contact,
1202 priv->remote_user_avatar_widget,
1203 MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1204 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1206 /* The remote avatar is shown by default and will be hidden when we receive
1207 video from the remote side. */
1208 gtk_widget_hide (priv->video_output);
1209 gtk_widget_show (priv->remote_user_avatar_widget);
1213 empathy_call_window_constructed (GObject *object)
1215 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1216 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1217 EmpathyTpCall *call;
1219 g_assert (priv->handler != NULL);
1221 g_object_get (priv->handler, "tp-call", &call, NULL);
1222 priv->outgoing = (call == NULL);
1224 g_object_unref (call);
1226 empathy_call_window_setup_avatars (self, priv->handler);
1227 empathy_call_window_set_state_connecting (self);
1229 if (empathy_call_handler_has_initial_video (priv->handler))
1231 /* Enable 'send video' buttons and display the preview */
1232 gtk_toggle_tool_button_set_active (
1233 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
1235 display_video_preview (self, TRUE);
1239 static void empathy_call_window_dispose (GObject *object);
1240 static void empathy_call_window_finalize (GObject *object);
1243 empathy_call_window_set_property (GObject *object,
1244 guint property_id, const GValue *value, GParamSpec *pspec)
1246 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1248 switch (property_id)
1250 case PROP_CALL_HANDLER:
1251 priv->handler = g_value_dup_object (value);
1254 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1259 empathy_call_window_get_property (GObject *object,
1260 guint property_id, GValue *value, GParamSpec *pspec)
1262 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1264 switch (property_id)
1266 case PROP_CALL_HANDLER:
1267 g_value_set_object (value, priv->handler);
1270 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1275 empathy_call_window_class_init (
1276 EmpathyCallWindowClass *empathy_call_window_class)
1278 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1279 GParamSpec *param_spec;
1281 g_type_class_add_private (empathy_call_window_class,
1282 sizeof (EmpathyCallWindowPriv));
1284 object_class->constructed = empathy_call_window_constructed;
1285 object_class->set_property = empathy_call_window_set_property;
1286 object_class->get_property = empathy_call_window_get_property;
1288 object_class->dispose = empathy_call_window_dispose;
1289 object_class->finalize = empathy_call_window_finalize;
1291 param_spec = g_param_spec_object ("handler",
1292 "handler", "The call handler",
1293 EMPATHY_TYPE_CALL_HANDLER,
1294 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1295 g_object_class_install_property (object_class,
1296 PROP_CALL_HANDLER, param_spec);
1300 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1301 GParamSpec *property, EmpathyCallWindow *self)
1303 DEBUG ("video stream changed");
1304 empathy_call_window_update_avatars_visibility (call, self);
1308 empathy_call_window_dispose (GObject *object)
1310 EmpathyTpCall *call;
1311 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1312 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1314 if (priv->dispose_has_run)
1317 priv->dispose_has_run = TRUE;
1319 g_object_get (priv->handler, "tp-call", &call, NULL);
1323 g_signal_handlers_disconnect_by_func (call,
1324 empathy_call_window_video_stream_changed_cb, object);
1325 g_object_unref (call);
1328 if (priv->handler != NULL)
1329 g_object_unref (priv->handler);
1330 priv->handler = NULL;
1332 if (priv->pipeline != NULL)
1333 g_object_unref (priv->pipeline);
1334 priv->pipeline = NULL;
1336 if (priv->video_input != NULL)
1337 g_object_unref (priv->video_input);
1338 priv->video_input = NULL;
1340 if (priv->audio_input != NULL)
1341 g_object_unref (priv->audio_input);
1342 priv->audio_input = NULL;
1344 if (priv->audio_output != NULL)
1345 g_object_unref (priv->audio_output);
1346 priv->audio_output = NULL;
1348 if (priv->video_tee != NULL)
1349 g_object_unref (priv->video_tee);
1350 priv->video_tee = NULL;
1352 if (priv->fsnotifier != NULL)
1353 g_object_unref (priv->fsnotifier);
1354 priv->fsnotifier = NULL;
1356 if (priv->timer_id != 0)
1357 g_source_remove (priv->timer_id);
1360 if (priv->ui_manager != NULL)
1361 g_object_unref (priv->ui_manager);
1362 priv->ui_manager = NULL;
1364 if (priv->contact != NULL)
1366 g_signal_handlers_disconnect_by_func (priv->contact,
1367 contact_name_changed_cb, self);
1368 g_object_unref (priv->contact);
1369 priv->contact = NULL;
1372 /* release any references held by the object here */
1373 if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1374 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1378 empathy_call_window_finalize (GObject *object)
1380 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1381 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1383 if (priv->video_output_motion_handler_id != 0)
1385 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1386 priv->video_output_motion_handler_id);
1387 priv->video_output_motion_handler_id = 0;
1390 if (priv->bus_message_source_id != 0)
1392 g_source_remove (priv->bus_message_source_id);
1393 priv->bus_message_source_id = 0;
1396 /* free any data held directly by the object here */
1397 g_mutex_free (priv->lock);
1399 g_timer_destroy (priv->timer);
1401 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1406 empathy_call_window_new (EmpathyCallHandler *handler)
1408 return EMPATHY_CALL_WINDOW (
1409 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1413 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1414 GstElement *conference, gpointer user_data)
1416 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1417 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1419 gst_bin_add (GST_BIN (priv->pipeline), conference);
1421 gst_element_set_state (conference, GST_STATE_PLAYING);
1425 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1426 FsMediaType type, FsStreamDirection direction, gpointer user_data)
1428 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1429 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1431 if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1434 if (direction == FS_DIRECTION_RECV)
1437 /* video and direction is send */
1438 return priv->video_input != NULL;
1442 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1444 GstStateChangeReturn state_change_return;
1445 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1447 if (priv->pipeline == NULL)
1450 if (priv->bus_message_source_id != 0)
1452 g_source_remove (priv->bus_message_source_id);
1453 priv->bus_message_source_id = 0;
1456 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1458 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1459 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1461 if (priv->pipeline != NULL)
1462 g_object_unref (priv->pipeline);
1463 priv->pipeline = NULL;
1465 if (priv->video_input != NULL)
1466 g_object_unref (priv->video_input);
1467 priv->video_input = NULL;
1469 if (priv->audio_input != NULL)
1470 g_object_unref (priv->audio_input);
1471 priv->audio_input = NULL;
1473 g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1474 empathy_call_window_mic_volume_changed_cb, self);
1476 if (priv->audio_output != NULL)
1477 g_object_unref (priv->audio_output);
1478 priv->audio_output = NULL;
1480 if (priv->video_tee != NULL)
1481 g_object_unref (priv->video_tee);
1482 priv->video_tee = NULL;
1484 if (priv->video_preview != NULL)
1485 gtk_widget_destroy (priv->video_preview);
1486 priv->video_preview = NULL;
1488 priv->liveadder = NULL;
1489 priv->funnel = NULL;
1495 g_message ("Error: could not destroy pipeline. Closing call window");
1496 gtk_widget_destroy (GTK_WIDGET (self));
1503 empathy_call_window_disconnected (EmpathyCallWindow *self)
1505 gboolean could_disconnect = FALSE;
1506 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1507 gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1509 if (priv->call_state == CONNECTING)
1510 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1512 if (priv->call_state != REDIALING)
1513 priv->call_state = DISCONNECTED;
1515 if (could_reset_pipeline)
1517 g_mutex_lock (priv->lock);
1519 g_timer_stop (priv->timer);
1521 if (priv->timer_id != 0)
1522 g_source_remove (priv->timer_id);
1525 g_mutex_unlock (priv->lock);
1527 empathy_call_window_status_message (self, _("Disconnected"));
1529 gtk_action_set_sensitive (priv->redial, TRUE);
1530 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1532 /* Reseting the send_video, camera_buton and mic_button to their
1534 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1535 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1536 gtk_toggle_tool_button_set_active (
1537 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1538 gtk_toggle_tool_button_set_active (
1539 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1541 gtk_progress_bar_set_fraction (
1542 GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1544 gtk_widget_hide (priv->video_output);
1545 gtk_widget_show (priv->remote_user_avatar_widget);
1547 priv->sending_video = FALSE;
1548 priv->call_started = FALSE;
1550 could_disconnect = TRUE;
1552 /* TODO: display the self avatar of the preview (depends if the "Always
1553 * Show Video Preview" is enabled or not) */
1556 return could_disconnect;
1561 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1564 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1565 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1567 if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1568 empathy_call_window_restart_call (self);
1573 empathy_call_window_channel_stream_closed_cb (EmpathyCallHandler *handler,
1574 TfStream *stream, gpointer user_data)
1576 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1577 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1580 g_object_get (stream, "media-type", &media_type, NULL);
1583 * This assumes that there is only one video stream per channel...
1586 if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1588 if (priv->funnel != NULL)
1592 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1593 (priv->video_output));
1595 gst_element_set_state (output, GST_STATE_NULL);
1596 gst_element_set_state (priv->funnel, GST_STATE_NULL);
1598 gst_bin_remove (GST_BIN (priv->pipeline), output);
1599 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1600 priv->funnel = NULL;
1603 else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1605 if (priv->liveadder != NULL)
1607 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1608 gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1610 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1611 gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1612 priv->liveadder = NULL;
1617 /* Called with global lock held */
1619 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1621 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1624 if (priv->funnel == NULL)
1628 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1629 (priv->video_output));
1631 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1633 gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1634 gst_bin_add (GST_BIN (priv->pipeline), output);
1636 gst_element_link (priv->funnel, output);
1638 gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1639 gst_element_set_state (output, GST_STATE_PLAYING);
1642 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1647 /* Called with global lock held */
1649 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1651 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1654 if (priv->liveadder == NULL)
1656 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1658 gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1659 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1661 gst_element_link (priv->liveadder, priv->audio_output);
1663 gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1664 gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1667 pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1673 empathy_call_window_update_timer (gpointer user_data)
1675 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1676 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1680 time_ = g_timer_elapsed (priv->timer, NULL);
1682 /* Translators: number of minutes:seconds the caller has been connected */
1683 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time_ / 60,
1685 empathy_call_window_status_message (self, str);
1692 display_error (EmpathyCallWindow *self,
1693 EmpathyTpCall *call,
1697 const gchar *details)
1699 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1700 GtkWidget *info_bar;
1701 GtkWidget *content_area;
1708 /* Create info bar */
1709 info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1712 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1714 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1716 /* hbox containing the image and the messages vbox */
1717 hbox = gtk_hbox_new (FALSE, 3);
1718 gtk_container_add (GTK_CONTAINER (content_area), hbox);
1721 image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1722 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1724 /* vbox containing the main message and the details expander */
1725 vbox = gtk_vbox_new (FALSE, 3);
1726 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1729 txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
1731 label = gtk_label_new (NULL);
1732 gtk_label_set_markup (GTK_LABEL (label), txt);
1733 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1734 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1737 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1740 if (details != NULL)
1742 GtkWidget *expander;
1744 expander = gtk_expander_new (_("Technical Details"));
1746 txt = g_strdup_printf ("<i>%s</i>", details);
1748 label = gtk_label_new (NULL);
1749 gtk_label_set_markup (GTK_LABEL (label), txt);
1750 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1751 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1754 gtk_container_add (GTK_CONTAINER (expander), label);
1755 gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1758 g_signal_connect (info_bar, "response",
1759 G_CALLBACK (gtk_widget_destroy), NULL);
1761 gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1762 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1763 gtk_widget_show_all (info_bar);
1767 media_stream_error_to_txt (EmpathyCallWindow *self,
1768 EmpathyTpCall *call,
1770 TpMediaStreamError error)
1772 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1779 case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1781 return g_strdup_printf (
1782 _("%s's software does not understand any of the audio formats "
1783 "supported by your computer"),
1784 empathy_contact_get_name (priv->contact));
1786 return g_strdup_printf (
1787 _("%s's software does not understand any of the video formats "
1788 "supported by your computer"),
1789 empathy_contact_get_name (priv->contact));
1791 case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1792 return g_strdup_printf (
1793 _("Can't establish a connection to %s. "
1794 "One of you might be on a network that does not allow "
1795 "direct connections."),
1796 empathy_contact_get_name (priv->contact));
1798 case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
1799 return g_strdup (_("There was a failure on the network"));
1801 case TP_MEDIA_STREAM_ERROR_NO_CODECS:
1803 return g_strdup (_("The audio formats necessary for this call "
1804 "are not installed on your computer"));
1806 return g_strdup (_("The video formats necessary for this call "
1807 "are not installed on your computer"));
1809 case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
1810 cm = empathy_tp_call_get_connection_manager (call);
1812 url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
1813 "product=Telepathy&component=%s", cm);
1815 result = g_strdup_printf (
1816 _("Something not expected happened in a Telepathy component. "
1817 "Please <a href=\"%s\">report this bug</a> and attach "
1818 "logs gathered from the 'Debug' window in the Help menu."), url);
1823 case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
1824 return g_strdup (_("There was a failure in the call engine"));
1832 empathy_call_window_stream_error (EmpathyCallWindow *self,
1833 EmpathyTpCall *call,
1842 desc = media_stream_error_to_txt (self, call, audio, code);
1845 /* No description, use the error message. That's not great as it's not
1846 * localized but it's better than nothing. */
1847 display_error (self, call, icon, title, msg, NULL);
1851 display_error (self, call, icon, title, desc, msg);
1857 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
1860 EmpathyCallWindow *self)
1862 empathy_call_window_stream_error (self, call, TRUE, code, msg,
1863 "gnome-stock-mic", _("Can't establish audio stream"));
1867 empathy_call_window_video_stream_error (EmpathyTpCall *call,
1870 EmpathyCallWindow *self)
1872 empathy_call_window_stream_error (self, call, FALSE, code, msg,
1873 "camera-web", _("Can't establish video stream"));
1877 empathy_call_window_connected (gpointer user_data)
1879 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1880 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1881 EmpathyTpCall *call;
1882 gboolean can_send_video;
1884 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1886 can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1887 empathy_contact_can_voip_video (priv->contact);
1889 g_object_get (priv->handler, "tp-call", &call, NULL);
1891 g_signal_connect (call, "notify::video-stream",
1892 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1894 if (empathy_tp_call_has_dtmf (call))
1895 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1897 if (priv->video_input == NULL)
1898 empathy_call_window_set_send_video (self, FALSE);
1900 priv->sending_video = can_send_video ?
1901 empathy_tp_call_is_sending_video (call) : FALSE;
1903 gtk_toggle_tool_button_set_active (
1904 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
1905 priv->sending_video && priv->video_input != NULL);
1906 gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
1908 gtk_action_set_sensitive (priv->redial, FALSE);
1909 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1911 gtk_widget_set_sensitive (priv->mic_button, TRUE);
1913 empathy_call_window_update_avatars_visibility (call, self);
1915 g_object_unref (call);
1917 g_mutex_lock (priv->lock);
1919 priv->timer_id = g_timeout_add_seconds (1,
1920 empathy_call_window_update_timer, self);
1922 g_mutex_unlock (priv->lock);
1924 empathy_call_window_update_timer (self);
1930 /* Called from the streaming thread */
1932 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1933 GstPad *src, guint media_type, gpointer user_data)
1935 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1936 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1940 g_mutex_lock (priv->lock);
1942 if (priv->call_state != CONNECTED)
1944 g_timer_start (priv->timer);
1945 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
1946 priv->call_state = CONNECTED;
1951 case TP_MEDIA_STREAM_TYPE_AUDIO:
1952 pad = empathy_call_window_get_audio_sink_pad (self);
1954 case TP_MEDIA_STREAM_TYPE_VIDEO:
1955 gtk_widget_hide (priv->remote_user_avatar_widget);
1956 gtk_widget_show (priv->video_output);
1957 pad = empathy_call_window_get_video_sink_pad (self);
1960 g_assert_not_reached ();
1963 gst_pad_link (src, pad);
1964 gst_object_unref (pad);
1966 g_mutex_unlock (priv->lock);
1969 /* Called from the streaming thread */
1971 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1972 GstPad *sink, guint media_type, gpointer user_data)
1974 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1975 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1980 case TP_MEDIA_STREAM_TYPE_AUDIO:
1981 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1983 pad = gst_element_get_static_pad (priv->audio_input, "src");
1984 gst_pad_link (pad, sink);
1986 gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1988 case TP_MEDIA_STREAM_TYPE_VIDEO:
1989 if (priv->video_input != NULL)
1991 if (priv->video_tee != NULL)
1993 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1994 gst_pad_link (pad, sink);
1999 g_assert_not_reached ();
2005 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2007 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2008 GstElement *preview;
2010 DEBUG ("remove video input");
2011 preview = empathy_video_widget_get_element (
2012 EMPATHY_VIDEO_WIDGET (priv->video_preview));
2014 gst_element_set_state (priv->video_input, GST_STATE_NULL);
2015 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2016 gst_element_set_state (preview, GST_STATE_NULL);
2018 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2019 priv->video_tee, preview, NULL);
2021 g_object_unref (priv->video_input);
2022 priv->video_input = NULL;
2023 g_object_unref (priv->video_tee);
2024 priv->video_tee = NULL;
2025 gtk_widget_destroy (priv->video_preview);
2026 priv->video_preview = NULL;
2028 gtk_toggle_tool_button_set_active (
2029 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
2030 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
2032 gtk_widget_show (priv->self_user_avatar_widget);
2037 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2040 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2041 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2044 empathy_call_handler_bus_message (priv->handler, bus, message);
2046 switch (GST_MESSAGE_TYPE (message))
2048 case GST_MESSAGE_STATE_CHANGED:
2049 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2051 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2052 if (newstate == GST_STATE_PAUSED)
2053 empathy_call_window_setup_video_input (self);
2055 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2056 !priv->call_started)
2058 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2059 if (newstate == GST_STATE_PAUSED)
2061 priv->call_started = TRUE;
2062 empathy_call_handler_start_call (priv->handler);
2063 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2067 case GST_MESSAGE_ERROR:
2069 GError *error = NULL;
2070 GstElement *gst_error;
2073 gst_message_parse_error (message, &error, &debug);
2074 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2076 g_message ("Element error: %s -- %s\n", error->message, debug);
2078 if (g_str_has_prefix (gst_element_get_name (gst_error),
2079 VIDEO_INPUT_ERROR_PREFIX))
2081 /* Remove the video input and continue */
2082 if (priv->video_input != NULL)
2083 empathy_call_window_remove_video_input (self);
2084 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2088 empathy_call_window_disconnected (self);
2090 g_error_free (error);
2101 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
2102 EmpathyCallWindow *window)
2104 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2106 if (empathy_tp_call_is_receiving_video (call))
2108 gtk_widget_hide (priv->remote_user_avatar_widget);
2109 gtk_widget_show (priv->video_output);
2113 gtk_widget_hide (priv->video_output);
2114 gtk_widget_show (priv->remote_user_avatar_widget);
2119 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
2121 EmpathyCallWindow *self)
2123 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2124 EmpathyTpCall *call;
2126 g_object_get (priv->handler, "tp-call", &call, NULL);
2130 empathy_signal_connect_weak (call, "audio-stream-error",
2131 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
2132 empathy_signal_connect_weak (call, "video-stream-error",
2133 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
2135 g_object_unref (call);
2139 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2141 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2142 EmpathyTpCall *call;
2144 g_signal_connect (priv->handler, "conference-added",
2145 G_CALLBACK (empathy_call_window_conference_added_cb), window);
2146 g_signal_connect (priv->handler, "request-resource",
2147 G_CALLBACK (empathy_call_window_request_resource_cb), window);
2148 g_signal_connect (priv->handler, "closed",
2149 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2150 g_signal_connect (priv->handler, "src-pad-added",
2151 G_CALLBACK (empathy_call_window_src_added_cb), window);
2152 g_signal_connect (priv->handler, "sink-pad-added",
2153 G_CALLBACK (empathy_call_window_sink_added_cb), window);
2154 g_signal_connect (priv->handler, "stream-closed",
2155 G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
2157 g_object_get (priv->handler, "tp-call", &call, NULL);
2160 empathy_signal_connect_weak (call, "audio-stream-error",
2161 G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
2162 empathy_signal_connect_weak (call, "video-stream-error",
2163 G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
2165 g_object_unref (call);
2169 /* tp-call doesn't exist yet, we'll connect signals once it has been
2171 g_signal_connect (priv->handler, "notify::tp-call",
2172 G_CALLBACK (call_handler_notify_tp_call_cb), window);
2175 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2179 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2180 EmpathyCallWindow *window)
2182 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2184 if (priv->pipeline != NULL)
2186 if (priv->bus_message_source_id != 0)
2188 g_source_remove (priv->bus_message_source_id);
2189 priv->bus_message_source_id = 0;
2192 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2195 if (priv->call_state == CONNECTING)
2196 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2202 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2205 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2207 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2212 gtk_widget_hide (priv->sidebar);
2213 gtk_widget_hide (menu);
2214 gtk_widget_hide (priv->vbox);
2215 gtk_widget_hide (priv->statusbar);
2216 gtk_widget_hide (priv->toolbar);
2220 if (priv->sidebar_was_visible_before_fs)
2221 gtk_widget_show (priv->sidebar);
2223 gtk_widget_show (menu);
2224 gtk_widget_show (priv->vbox);
2225 gtk_widget_show (priv->statusbar);
2226 gtk_widget_show (priv->toolbar);
2228 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2229 priv->original_height_before_fs);
2234 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2236 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2238 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2239 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2240 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2241 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2242 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2243 priv->video_output, TRUE, TRUE,
2244 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2246 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2247 priv->vbox, TRUE, TRUE,
2248 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2253 empathy_call_window_state_event_cb (GtkWidget *widget,
2254 GdkEventWindowState *event, EmpathyCallWindow *window)
2256 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2258 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2259 gboolean set_fullscreen = event->new_window_state &
2260 GDK_WINDOW_STATE_FULLSCREEN;
2264 gboolean sidebar_was_visible;
2265 GtkAllocation allocation;
2266 gint original_width, original_height;
2268 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2269 original_width = allocation.width;
2270 original_height = allocation.height;
2272 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2274 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2275 priv->original_width_before_fs = original_width;
2276 priv->original_height_before_fs = original_height;
2278 if (priv->video_output_motion_handler_id == 0 &&
2279 priv->video_output != NULL)
2281 priv->video_output_motion_handler_id = g_signal_connect (
2282 G_OBJECT (priv->video_output), "motion-notify-event",
2283 G_CALLBACK (empathy_call_window_video_output_motion_notify),
2289 if (priv->video_output_motion_handler_id != 0)
2291 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2292 priv->video_output_motion_handler_id);
2293 priv->video_output_motion_handler_id = 0;
2297 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2299 show_controls (window, set_fullscreen);
2300 show_borders (window, set_fullscreen);
2301 gtk_action_set_stock_id (priv->menu_fullscreen,
2302 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2303 priv->is_fullscreen = set_fullscreen;
2310 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2311 EmpathyCallWindow *window)
2313 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2315 int w, h, handle_size;
2316 GtkAllocation allocation, sidebar_allocation;
2318 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2319 w = allocation.width;
2320 h = allocation.height;
2322 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2324 gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2325 if (gtk_toggle_button_get_active (toggle))
2327 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2328 gtk_widget_show (priv->sidebar);
2329 w += sidebar_allocation.width + handle_size;
2333 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2334 w -= sidebar_allocation.width + handle_size;
2335 gtk_widget_hide (priv->sidebar);
2338 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2341 gtk_window_resize (GTK_WINDOW (window), w, h);
2345 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2348 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2349 EmpathyTpCall *call;
2351 priv->sending_video = send;
2353 /* When we start sending video, we want to show the video preview by
2355 display_video_preview (window, send);
2357 if (priv->call_state != CONNECTED)
2360 g_object_get (priv->handler, "tp-call", &call, NULL);
2361 DEBUG ("%s sending video", send ? "start": "stop");
2362 empathy_tp_call_request_video_stream_direction (call, send);
2363 g_object_unref (call);
2367 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2368 EmpathyCallWindow *window)
2370 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2373 if (priv->audio_input == NULL)
2376 active = (gtk_toggle_tool_button_get_active (toggle));
2380 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2382 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2386 /* TODO, Instead of setting the input volume to 0 we should probably
2387 * stop sending but this would cause the audio call to drop if both
2388 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2389 * in the future. GNOME #574574
2391 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2393 gtk_adjustment_set_value (priv->audio_input_adj, 0);
2398 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2399 EmpathyCallWindow *window)
2401 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2403 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2408 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2409 EmpathyCallWindow *window)
2411 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2413 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2418 empathy_call_window_hangup_cb (gpointer object,
2419 EmpathyCallWindow *window)
2421 if (empathy_call_window_disconnected (window))
2422 gtk_widget_destroy (GTK_WIDGET (window));
2426 empathy_call_window_restart_call (EmpathyCallWindow *window)
2429 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2431 gtk_widget_destroy (priv->remote_user_output_hbox);
2432 gtk_widget_destroy (priv->self_user_output_hbox);
2434 priv->pipeline = gst_pipeline_new (NULL);
2435 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2436 priv->bus_message_source_id = gst_bus_add_watch (bus,
2437 empathy_call_window_bus_message, window);
2439 empathy_call_window_setup_remote_frame (bus, window);
2440 empathy_call_window_setup_self_frame (bus, window);
2442 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2443 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2445 /* While the call was disconnected, the input volume might have changed.
2446 * However, since the audio_input source was destroyed, its volume has not
2447 * been updated during that time. That's why we manually update it here */
2448 empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2450 g_object_unref (bus);
2452 gtk_widget_show_all (priv->content_hbox);
2454 priv->outgoing = TRUE;
2455 empathy_call_window_set_state_connecting (window);
2457 priv->call_started = TRUE;
2458 empathy_call_handler_start_call (priv->handler);
2459 empathy_call_window_setup_avatars (window, priv->handler);
2460 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2462 gtk_action_set_sensitive (priv->redial, FALSE);
2463 gtk_widget_set_sensitive (priv->redial_button, FALSE);
2467 empathy_call_window_redial_cb (gpointer object,
2468 EmpathyCallWindow *window)
2470 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2472 if (priv->call_state == CONNECTED)
2473 priv->call_state = REDIALING;
2475 empathy_call_handler_stop_call (priv->handler);
2477 if (priv->call_state != CONNECTED)
2478 empathy_call_window_restart_call (window);
2482 empathy_call_window_fullscreen_cb (gpointer object,
2483 EmpathyCallWindow *window)
2485 empathy_call_window_fullscreen_toggle (window);
2489 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2491 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2493 if (priv->is_fullscreen)
2494 gtk_window_unfullscreen (GTK_WINDOW (window));
2496 gtk_window_fullscreen (GTK_WINDOW (window));
2500 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2501 GdkEventButton *event, EmpathyCallWindow *window)
2503 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2505 empathy_call_window_video_menu_popup (window, event->button);
2513 empathy_call_window_key_press_cb (GtkWidget *video_output,
2514 GdkEventKey *event, EmpathyCallWindow *window)
2516 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2518 if (priv->is_fullscreen && event->keyval == GDK_Escape)
2520 /* Since we are in fullscreen mode, toggling will bring us back to
2522 empathy_call_window_fullscreen_toggle (window);
2530 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2531 GdkEventMotion *event, EmpathyCallWindow *window)
2533 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2535 if (priv->is_fullscreen)
2537 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2544 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2548 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2550 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2552 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2553 button, gtk_get_current_event_time ());
2554 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2558 empathy_call_window_status_message (EmpathyCallWindow *window,
2561 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2563 if (priv->context_id == 0)
2565 priv->context_id = gtk_statusbar_get_context_id (
2566 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2570 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2573 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2578 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2579 gdouble value, EmpathyCallWindow *window)
2581 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2583 if (priv->audio_output == NULL)
2586 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2590 /* block all the signals related to camera control widgets. This is useful
2591 * when we are manually updating the UI and so don't want to fire the
2594 block_camera_control_signals (EmpathyCallWindow *self)
2596 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2598 g_signal_handlers_block_by_func (priv->tool_button_camera_off,
2599 tool_button_camera_off_toggled_cb, self);
2600 g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
2601 tool_button_camera_preview_toggled_cb, self);
2602 g_signal_handlers_block_by_func (priv->tool_button_camera_on,
2603 tool_button_camera_on_toggled_cb, self);
2604 g_signal_handlers_block_by_func (priv->action_camera,
2605 tool_button_camera_on_toggled_cb, self);
2609 unblock_camera_control_signals (EmpathyCallWindow *self)
2611 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2613 g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
2614 tool_button_camera_off_toggled_cb, self);
2615 g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
2616 tool_button_camera_preview_toggled_cb, self);
2617 g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
2618 tool_button_camera_on_toggled_cb, self);
2619 g_signal_handlers_unblock_by_func (priv->action_camera,
2620 tool_button_camera_on_toggled_cb, self);