2 * empathy-call-window.c - Source for EmpathyCallWindow
3 * Copyright (C) 2008-2011 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 <clutter/clutter.h>
33 #include <clutter-gtk/clutter-gtk.h>
34 #include <clutter-gst/clutter-gst.h>
36 #include <telepathy-glib/util.h>
37 #include <telepathy-farstream/telepathy-farstream.h>
38 #include <telepathy-glib/util.h>
40 #include <gst/farsight/fs-element-added-notifier.h>
41 #include <gst/farsight/fs-utils.h>
43 #include <libempathy/empathy-tp-contact-factory.h>
44 #include <libempathy/empathy-utils.h>
45 #include <libempathy-gtk/empathy-avatar-image.h>
46 #include <libempathy-gtk/empathy-ui-utils.h>
47 #include <libempathy-gtk/empathy-sound-manager.h>
48 #include <libempathy-gtk/empathy-geometry.h>
49 #include <libempathy-gtk/empathy-images.h>
51 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
52 #include <libempathy/empathy-debug.h>
54 #include "empathy-call-window.h"
55 #include "empathy-call-window-fullscreen.h"
56 #include "empathy-call-factory.h"
57 #include "empathy-video-widget.h"
58 #include "empathy-audio-src.h"
59 #include "empathy-audio-sink.h"
60 #include "empathy-video-src.h"
61 #include "ev-sidebar.h"
63 #define BUTTON_ID "empathy-call-dtmf-button-id"
65 #define CONTENT_HBOX_BORDER_WIDTH 6
66 #define CONTENT_HBOX_SPACING 3
67 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
69 #define SELF_VIDEO_SECTION_WIDTH 120
70 #define SELF_VIDEO_SECTION_HEIGTH 90
72 /* The avatar's default width and height are set to the same value because we
73 want a square icon. */
74 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
75 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
76 EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
78 /* If an video input error occurs, the error message will start with "v4l" */
79 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
81 /* The time interval in milliseconds between 2 outgoing rings */
82 #define MS_BETWEEN_RING 500
84 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
87 PROP_CALL_HANDLER = 1,
103 struct _EmpathyCallWindowPriv
105 gboolean dispose_has_run;
106 EmpathyCallHandler *handler;
108 EmpathyContact *contact;
113 GtkUIManager *ui_manager;
114 GtkWidget *errors_vbox;
115 /* widget displays the video received from the remote user. This widget is
116 * alive only during call. */
117 ClutterActor *video_output;
118 ClutterActor *video_preview;
119 ClutterActor *preview_hidden_button;
120 GtkWidget *video_container;
121 GtkWidget *remote_user_avatar_widget;
123 GtkWidget *statusbar;
124 GtkWidget *volume_button;
125 GtkWidget *redial_button;
126 GtkWidget *mic_button;
127 GtkWidget *camera_button;
128 GtkWidget *dialpad_button;
132 GtkAction *menu_sidebar;
133 GtkAction *menu_fullscreen;
135 /* The box that contains self and remote avatar and video
136 input/output. When we redial, we destroy and re-create the box */
137 ClutterActor *video_box;
138 ClutterLayoutManager *video_layout;
140 /* We keep a reference on the hbox which contains the main content so we can
141 easilly repack everything when toggling fullscreen */
142 GtkWidget *content_hbox;
144 /* This vbox is contained in the content_hbox and it contains the
145 sidebar button. When toggling fullscreen,
146 it needs to be repacked. We keep a reference on it for easier access. */
149 gulong video_output_motion_handler_id;
150 guint bus_message_source_id;
153 GtkWidget *volume_scale;
154 GtkWidget *volume_progress_bar;
155 GtkAdjustment *audio_input_adj;
157 GtkWidget *dtmf_panel;
160 GtkWidget *details_vbox;
161 GtkWidget *vcodec_encoding_label;
162 GtkWidget *acodec_encoding_label;
163 GtkWidget *vcodec_decoding_label;
164 GtkWidget *acodec_decoding_label;
166 GtkWidget *audio_remote_candidate_label;
167 GtkWidget *audio_local_candidate_label;
168 GtkWidget *video_remote_candidate_label;
169 GtkWidget *video_local_candidate_label;
170 GtkWidget *video_remote_candidate_info_img;
171 GtkWidget *video_local_candidate_info_img;
172 GtkWidget *audio_remote_candidate_info_img;
173 GtkWidget *audio_local_candidate_info_img;
175 GstElement *video_input;
176 GstElement *video_preview_sink;
177 GstElement *video_output_sink;
178 GstElement *audio_input;
179 GstElement *audio_output;
180 GstElement *pipeline;
181 GstElement *video_tee;
192 GtkWidget *video_contrast;
193 GtkWidget *video_brightness;
194 GtkWidget *video_gamma;
197 gboolean call_started;
198 gboolean sending_video;
199 CameraState camera_state;
201 EmpathyCallWindowFullscreen *fullscreen;
202 gboolean is_fullscreen;
204 /* Those fields represent the state of the window before it actually was in
206 gboolean sidebar_was_visible_before_fs;
207 gint original_width_before_fs;
208 gint original_height_before_fs;
210 /* TRUE if the call should be started when the pipeline is playing */
211 gboolean start_call_when_playing;
212 /* TRUE if we requested to set the pipeline in the playing state */
213 gboolean pipeline_playing;
215 EmpathySoundManager *sound_mgr;
218 #define GET_PRIV(o) (EMPATHY_CALL_WINDOW (o)->priv)
220 static void empathy_call_window_realized_cb (GtkWidget *widget,
221 EmpathyCallWindow *window);
223 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
224 GdkEvent *event, EmpathyCallWindow *window);
226 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
227 GdkEventWindowState *event, EmpathyCallWindow *window);
229 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
232 static void empathy_call_window_mic_toggled_cb (
233 GtkToggleToolButton *toggle, EmpathyCallWindow *window);
235 static void empathy_call_window_sidebar_cb (GtkToggleAction *menu,
236 EmpathyCallWindow *self);
238 static void empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
239 EmpathyCallWindow *window);
241 static void empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
242 EmpathyCallWindow *window);
244 static void empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar,
246 EmpathyCallWindow *window);
248 static void empathy_call_window_hangup_cb (gpointer object,
249 EmpathyCallWindow *window);
251 static void empathy_call_window_fullscreen_cb (gpointer object,
252 EmpathyCallWindow *window);
254 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
256 static gboolean empathy_call_window_video_button_press_cb (
257 GtkWidget *video_output, GdkEventButton *event, EmpathyCallWindow *window);
259 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
260 GdkEventKey *event, EmpathyCallWindow *window);
262 static gboolean empathy_call_window_video_output_motion_notify (
263 GtkWidget *widget, GdkEventMotion *event, EmpathyCallWindow *window);
265 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
268 static void empathy_call_window_redial_cb (gpointer object,
269 EmpathyCallWindow *window);
271 static void empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
272 EmpathyCallWindow *window);
274 static void empathy_call_window_restart_call (EmpathyCallWindow *window);
276 static void empathy_call_window_status_message (EmpathyCallWindow *window,
279 static gboolean empathy_call_window_bus_message (GstBus *bus,
280 GstMessage *message, gpointer user_data);
283 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
284 gdouble value, EmpathyCallWindow *window);
287 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
289 EmpathyCallWindowPriv *priv = GET_PRIV (self);
290 GtkToolItem *tool_item;
292 /* Add an empty expanded GtkToolItem so the volume button is at the end of
294 tool_item = gtk_tool_item_new ();
295 gtk_tool_item_set_expand (tool_item, TRUE);
296 gtk_widget_show (GTK_WIDGET (tool_item));
297 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
299 priv->volume_button = gtk_volume_button_new ();
300 /* FIXME listen to the audiosinks signals and update the button according to
301 * that, for now starting out at 1.0 and assuming only the app changes the
303 gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
304 g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
305 G_CALLBACK (empathy_call_window_volume_changed_cb), self);
307 tool_item = gtk_tool_item_new ();
308 gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
309 gtk_widget_show_all (GTK_WIDGET (tool_item));
310 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
314 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
316 EmpathyCallWindowPriv *priv = GET_PRIV (window);
317 TpyCallChannel *call;
321 g_object_get (priv->handler, "call-channel", &call, NULL);
323 button_quark = g_quark_from_static_string (BUTTON_ID);
324 event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
327 tpy_call_channel_dtmf_start_tone (call, event);
329 g_object_unref (call);
333 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
335 EmpathyCallWindowPriv *priv = GET_PRIV (window);
336 TpyCallChannel *call;
338 g_object_get (priv->handler, "call-channel", &call, NULL);
340 tpy_call_channel_dtmf_stop_tone (call);
342 g_object_unref (call);
346 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
354 } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
355 { "2", TP_DTMF_EVENT_DIGIT_2 },
356 { "3", TP_DTMF_EVENT_DIGIT_3 },
357 { "4", TP_DTMF_EVENT_DIGIT_4 },
358 { "5", TP_DTMF_EVENT_DIGIT_5 },
359 { "6", TP_DTMF_EVENT_DIGIT_6 },
360 { "7", TP_DTMF_EVENT_DIGIT_7 },
361 { "8", TP_DTMF_EVENT_DIGIT_8 },
362 { "9", TP_DTMF_EVENT_DIGIT_9 },
363 { "#", TP_DTMF_EVENT_HASH },
364 { "0", TP_DTMF_EVENT_DIGIT_0 },
365 { "*", TP_DTMF_EVENT_ASTERISK },
368 button_quark = g_quark_from_static_string (BUTTON_ID);
370 table = gtk_table_new (4, 3, TRUE);
372 for (i = 0; dtmfbuttons[i].label != NULL; i++)
374 GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
375 gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
376 i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
378 g_object_set_qdata (G_OBJECT (button), button_quark,
379 GUINT_TO_POINTER (dtmfbuttons[i].event));
381 g_signal_connect (G_OBJECT (button), "pressed",
382 G_CALLBACK (dtmf_button_pressed_cb), self);
383 g_signal_connect (G_OBJECT (button), "released",
384 G_CALLBACK (dtmf_button_released_cb), self);
391 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
392 gchar *label_text, GtkWidget *bin)
394 GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
395 GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
396 GtkWidget *label = gtk_label_new (label_text);
398 gtk_widget_set_sensitive (scale, FALSE);
400 gtk_container_add (GTK_CONTAINER (bin), vbox);
402 gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
403 gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
404 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
410 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
411 EmpathyCallWindow *self)
414 EmpathyCallWindowPriv *priv = GET_PRIV (self);
416 empathy_video_src_set_channel (priv->video_input,
417 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
421 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
422 EmpathyCallWindow *self)
425 EmpathyCallWindowPriv *priv = GET_PRIV (self);
427 empathy_video_src_set_channel (priv->video_input,
428 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
432 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
433 EmpathyCallWindow *self)
436 EmpathyCallWindowPriv *priv = GET_PRIV (self);
438 empathy_video_src_set_channel (priv->video_input,
439 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
444 empathy_call_window_create_video_input (EmpathyCallWindow *self)
446 EmpathyCallWindowPriv *priv = GET_PRIV (self);
449 hbox = gtk_hbox_new (TRUE, 3);
451 priv->video_contrast = empathy_call_window_create_video_input_add_slider (
452 self, _("Contrast"), hbox);
454 priv->video_brightness = empathy_call_window_create_video_input_add_slider (
455 self, _("Brightness"), hbox);
457 priv->video_gamma = empathy_call_window_create_video_input_add_slider (
458 self, _("Gamma"), hbox);
464 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
466 EmpathyCallWindowPriv *priv = GET_PRIV (self);
470 supported = empathy_video_src_get_supported_channels (priv->video_input);
472 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
474 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
476 gtk_adjustment_set_value (adj,
477 empathy_video_src_get_channel (priv->video_input,
478 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
480 g_signal_connect (G_OBJECT (adj), "value-changed",
481 G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
483 gtk_widget_set_sensitive (priv->video_contrast, TRUE);
486 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
488 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
490 gtk_adjustment_set_value (adj,
491 empathy_video_src_get_channel (priv->video_input,
492 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
494 g_signal_connect (G_OBJECT (adj), "value-changed",
495 G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
496 gtk_widget_set_sensitive (priv->video_brightness, TRUE);
499 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
501 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
503 gtk_adjustment_set_value (adj,
504 empathy_video_src_get_channel (priv->video_input,
505 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
507 g_signal_connect (G_OBJECT (adj), "value-changed",
508 G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
509 gtk_widget_set_sensitive (priv->video_gamma, TRUE);
514 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
515 EmpathyCallWindow *self)
517 EmpathyCallWindowPriv *priv = GET_PRIV (self);
520 volume = gtk_adjustment_get_value (adj)/100.0;
522 /* Don't store the volume because of muting */
523 if (volume > 0 || gtk_toggle_tool_button_get_active (
524 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
525 priv->volume = volume;
527 /* Ensure that the toggle button is active if the volume is > 0 and inactive
528 * if it's smaller than 0 */
529 if ((volume > 0) != gtk_toggle_tool_button_get_active (
530 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
531 gtk_toggle_tool_button_set_active (
532 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
534 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
539 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
540 gdouble level, EmpathyCallWindow *window)
543 EmpathyCallWindowPriv *priv = GET_PRIV (window);
545 value = CLAMP (pow (10, level / 20), 0.0, 1.0);
546 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
551 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
553 EmpathyCallWindowPriv *priv = GET_PRIV (self);
554 GtkWidget *hbox, *vbox, *label;
556 hbox = gtk_hbox_new (TRUE, 3);
558 vbox = gtk_vbox_new (FALSE, 3);
559 gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
561 priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
562 gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
563 label = gtk_label_new (_("Volume"));
565 priv->audio_input_adj = gtk_range_get_adjustment (
566 GTK_RANGE (priv->volume_scale));
567 priv->volume = empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
568 (priv->audio_input));
569 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
571 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
572 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
574 gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
575 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
577 priv->volume_progress_bar = gtk_progress_bar_new ();
579 gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->volume_progress_bar),
580 GTK_ORIENTATION_VERTICAL);
582 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
585 gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
592 create_video_output_widget (EmpathyCallWindow *self)
594 EmpathyCallWindowPriv *priv = GET_PRIV (self);
596 g_assert (priv->video_output == NULL);
597 g_assert (priv->pipeline != NULL);
599 priv->video_output = clutter_texture_new ();
601 clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (priv->video_output),
604 priv->video_output_sink = clutter_gst_video_sink_new (
605 CLUTTER_TEXTURE (priv->video_output));
607 clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_box),
610 gtk_widget_add_events (priv->video_container,
611 GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
612 g_signal_connect (G_OBJECT (priv->video_container), "button-press-event",
613 G_CALLBACK (empathy_call_window_video_button_press_cb), self);
617 create_video_input (EmpathyCallWindow *self)
619 EmpathyCallWindowPriv *priv = GET_PRIV (self);
621 g_assert (priv->video_input == NULL);
622 priv->video_input = empathy_video_src_new ();
623 gst_object_ref (priv->video_input);
624 gst_object_sink (priv->video_input);
628 create_audio_input (EmpathyCallWindow *self)
630 EmpathyCallWindowPriv *priv = GET_PRIV (self);
632 g_assert (priv->audio_input == NULL);
633 priv->audio_input = empathy_audio_src_new ();
634 gst_object_ref (priv->audio_input);
635 gst_object_sink (priv->audio_input);
637 tp_g_signal_connect_object (priv->audio_input, "peak-level-changed",
638 G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
643 add_video_preview_to_pipeline (EmpathyCallWindow *self)
645 EmpathyCallWindowPriv *priv = GET_PRIV (self);
648 g_assert (priv->video_preview != NULL);
649 g_assert (priv->pipeline != NULL);
650 g_assert (priv->video_input != NULL);
651 g_assert (priv->video_tee != NULL);
653 preview = priv->video_preview_sink;
655 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_input))
657 g_warning ("Could not add video input to pipeline");
661 if (!gst_bin_add (GST_BIN (priv->pipeline), preview))
663 g_warning ("Could not add video preview to pipeline");
667 if (!gst_element_link (priv->video_input, priv->video_tee))
669 g_warning ("Could not link video input to video tee");
673 if (!gst_element_link (priv->video_tee, preview))
675 g_warning ("Could not link video tee to video preview");
681 empathy_call_window_disable_camera_cb (GtkAction *action,
682 EmpathyCallWindow *self)
684 clutter_actor_destroy (self->priv->preview_hidden_button);
686 gtk_toggle_tool_button_set_active (
687 GTK_TOGGLE_TOOL_BUTTON (self->priv->camera_button), FALSE);
691 empathy_call_window_minimise_camera_cb (GtkAction *action,
692 EmpathyCallWindow *self)
694 clutter_actor_hide (self->priv->video_preview);
695 clutter_actor_show (self->priv->preview_hidden_button);
699 empathy_call_window_maximise_camera_cb (GtkAction *action,
700 EmpathyCallWindow *self)
702 clutter_actor_show (self->priv->video_preview);
703 clutter_actor_hide (self->priv->preview_hidden_button);
707 empathy_call_window_preview_button_clicked_cb (GtkButton *button,
708 EmpathyCallWindow *self)
712 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
714 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
715 0, gtk_get_current_event_time ());
716 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
720 empathy_call_window_preview_hidden_button_clicked_cb (GtkButton *button,
721 EmpathyCallWindow *self)
725 menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
726 "/preview-hidden-menu");
727 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
728 0, gtk_get_current_event_time ());
729 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
733 create_video_preview (EmpathyCallWindow *self)
735 EmpathyCallWindowPriv *priv = GET_PRIV (self);
736 ClutterLayoutManager *layout, *layout_center;
737 ClutterActor *preview;
742 g_assert (priv->video_preview == NULL);
744 preview = clutter_texture_new ();
745 clutter_actor_set_size (preview,
746 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
747 priv->video_preview_sink = clutter_gst_video_sink_new (
748 CLUTTER_TEXTURE (preview));
750 /* Flip the video preview */
751 clutter_actor_set_rotation (preview,
754 SELF_VIDEO_SECTION_WIDTH * 0.5,
758 /* Add a little offset to the video preview */
759 layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_END,
760 CLUTTER_BIN_ALIGNMENT_START);
761 priv->video_preview = clutter_box_new (layout);
762 clutter_actor_set_size (priv->video_preview,
763 SELF_VIDEO_SECTION_WIDTH + 10, SELF_VIDEO_SECTION_HEIGTH + 10);
765 layout_center = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
766 CLUTTER_BIN_ALIGNMENT_CENTER);
767 box = clutter_box_new (layout_center);
768 clutter_actor_set_size (box,
769 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
771 clutter_container_add_actor (CLUTTER_CONTAINER (box), preview);
772 clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_preview), box);
774 g_object_set (priv->video_preview_sink,
779 /* Translators: this is an "Info" label. It should be as short
781 button = gtk_button_new_with_label (_("i"));
782 b = gtk_clutter_actor_new_with_contents (button);
784 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout_center),
786 CLUTTER_BIN_ALIGNMENT_END,
787 CLUTTER_BIN_ALIGNMENT_END);
789 g_signal_connect (button, "clicked",
790 G_CALLBACK (empathy_call_window_preview_button_clicked_cb),
793 /* Translators: this is an "Info" label. It should be as short
795 button = gtk_button_new_with_label (_("i"));
796 priv->preview_hidden_button =
797 gtk_clutter_actor_new_with_contents (button);
799 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->video_layout),
800 priv->preview_hidden_button,
801 CLUTTER_BIN_ALIGNMENT_START,
802 CLUTTER_BIN_ALIGNMENT_END);
804 clutter_actor_hide (priv->preview_hidden_button);
806 g_signal_connect (button, "clicked",
807 G_CALLBACK (empathy_call_window_preview_hidden_button_clicked_cb),
810 clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->video_layout),
812 CLUTTER_BIN_ALIGNMENT_START,
813 CLUTTER_BIN_ALIGNMENT_END);
817 play_camera (EmpathyCallWindow *window,
820 EmpathyCallWindowPriv *priv = GET_PRIV (window);
824 if (priv->video_preview == NULL)
826 create_video_preview (window);
827 add_video_preview_to_pipeline (window);
831 state = GST_STATE_PLAYING;
833 state = GST_STATE_NULL;
835 preview = priv->video_preview_sink;
837 gst_element_set_state (preview, state);
838 gst_element_set_state (priv->video_input, state);
839 gst_element_set_state (priv->video_tee, state);
843 display_video_preview (EmpathyCallWindow *self,
846 EmpathyCallWindowPriv *priv = GET_PRIV (self);
850 /* Display the preview and hide the self avatar */
851 DEBUG ("Show video preview");
853 play_camera (self, TRUE);
854 clutter_actor_show (priv->video_preview);
858 /* Display the self avatar and hide the preview */
859 DEBUG ("Show self avatar");
861 if (priv->video_preview != NULL)
863 clutter_actor_hide (priv->video_preview);
864 play_camera (self, FALSE);
870 empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
872 EmpathyCallWindowPriv *priv = GET_PRIV (window);
874 empathy_call_window_status_message (window, _("Connecting…"));
875 priv->call_state = CONNECTING;
878 empathy_sound_manager_start_playing (priv->sound_mgr, GTK_WIDGET (window),
879 EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
883 disable_camera (EmpathyCallWindow *self)
885 EmpathyCallWindowPriv *priv = GET_PRIV (self);
887 if (priv->camera_state == CAMERA_STATE_OFF)
890 DEBUG ("Disable camera");
892 display_video_preview (self, FALSE);
894 if (priv->camera_state == CAMERA_STATE_ON)
895 empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
897 priv->camera_state = CAMERA_STATE_OFF;
901 enable_camera (EmpathyCallWindow *self)
903 EmpathyCallWindowPriv *priv = GET_PRIV (self);
905 if (priv->camera_state == CAMERA_STATE_ON)
908 if (priv->video_input == NULL)
910 DEBUG ("Can't enable camera, no input");
914 DEBUG ("Enable camera");
916 empathy_call_window_set_send_video (self, CAMERA_STATE_ON);
918 priv->camera_state = CAMERA_STATE_ON;
922 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
923 EmpathyCallWindow *self)
925 if (gtk_toggle_tool_button_get_active (toggle))
926 enable_camera (self);
928 disable_camera (self);
932 create_pipeline (EmpathyCallWindow *self)
934 EmpathyCallWindowPriv *priv = GET_PRIV (self);
937 g_assert (priv->pipeline == NULL);
939 priv->pipeline = gst_pipeline_new (NULL);
940 priv->pipeline_playing = FALSE;
942 priv->video_tee = gst_element_factory_make ("tee", NULL);
943 gst_object_ref (priv->video_tee);
944 gst_object_sink (priv->video_tee);
946 gst_bin_add (GST_BIN (priv->pipeline), priv->video_tee);
948 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
949 priv->bus_message_source_id = gst_bus_add_watch (bus,
950 empathy_call_window_bus_message, self);
952 g_object_unref (bus);
956 empathy_call_window_init (EmpathyCallWindow *self)
958 EmpathyCallWindowPriv *priv;
965 ClutterConstraint *size_constraint;
966 ClutterActor *remote_avatar;
967 GtkStyleContext *context;
971 priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
972 EMPATHY_TYPE_CALL_WINDOW, EmpathyCallWindowPriv);
974 filename = empathy_file_lookup ("empathy-call-window.ui", "src");
975 gui = empathy_builder_get_file (filename,
976 "call_window_vbox", &top_vbox,
977 "errors_vbox", &priv->errors_vbox,
979 "statusbar", &priv->statusbar,
980 "redial", &priv->redial_button,
981 "microphone", &priv->mic_button,
982 "camera", &priv->camera_button,
983 "dialpad", &priv->dialpad_button,
984 "toolbar", &priv->toolbar,
985 "menuredial", &priv->redial,
986 "menusidebar", &priv->menu_sidebar,
987 "ui_manager", &priv->ui_manager,
988 "menufullscreen", &priv->menu_fullscreen,
989 "details_vbox", &priv->details_vbox,
990 "vcodec_encoding_label", &priv->vcodec_encoding_label,
991 "acodec_encoding_label", &priv->acodec_encoding_label,
992 "acodec_decoding_label", &priv->acodec_decoding_label,
993 "vcodec_decoding_label", &priv->vcodec_decoding_label,
994 "audio_remote_candidate_label", &priv->audio_remote_candidate_label,
995 "audio_local_candidate_label", &priv->audio_local_candidate_label,
996 "video_remote_candidate_label", &priv->video_remote_candidate_label,
997 "video_local_candidate_label", &priv->video_local_candidate_label,
998 "video_remote_candidate_info_img", &priv->video_remote_candidate_info_img,
999 "video_local_candidate_info_img", &priv->video_local_candidate_info_img,
1000 "audio_remote_candidate_info_img", &priv->audio_remote_candidate_info_img,
1001 "audio_local_candidate_info_img", &priv->audio_local_candidate_info_img,
1005 empathy_builder_connect (gui, self,
1006 "menuhangup", "activate", empathy_call_window_hangup_cb,
1007 "hangup", "clicked", empathy_call_window_hangup_cb,
1008 "menuredial", "activate", empathy_call_window_redial_cb,
1009 "redial", "clicked", empathy_call_window_redial_cb,
1010 "menusidebar", "toggled", empathy_call_window_sidebar_cb,
1011 "microphone", "toggled", empathy_call_window_mic_toggled_cb,
1012 "camera", "toggled", empathy_call_window_camera_toggled_cb,
1013 "dialpad", "toggled", empathy_call_window_dialpad_cb,
1014 "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
1015 "menupreviewdisable", "activate", empathy_call_window_disable_camera_cb,
1016 "menupreviewminimise", "activate", empathy_call_window_minimise_camera_cb,
1017 "menupreviewmaximise", "activate", empathy_call_window_maximise_camera_cb,
1020 gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1022 priv->lock = g_mutex_new ();
1024 gtk_container_add (GTK_CONTAINER (self), top_vbox);
1026 priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
1027 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1028 CONTENT_HBOX_BORDER_WIDTH);
1029 gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
1031 /* avatar/video box */
1032 priv->video_layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
1033 CLUTTER_BIN_ALIGNMENT_CENTER);
1035 priv->video_box = clutter_box_new (priv->video_layout);
1037 priv->video_container = gtk_clutter_embed_new ();
1039 /* Set the background color to that of the rest of the window */
1040 context = gtk_widget_get_style_context (priv->content_hbox);
1041 gtk_style_context_get_background_color (context,
1042 GTK_STATE_FLAG_NORMAL, &rgba);
1043 bg.red = CLAMP (rgba.red * 255.0, 0, 255);
1044 bg.green = CLAMP (rgba.green * 255.0, 0, 255);
1045 bg.blue = CLAMP (rgba.blue * 255.0, 0, 255);
1046 bg.alpha = CLAMP (rgba.alpha * 255.0, 0, 255);
1047 clutter_stage_set_color (
1048 CLUTTER_STAGE (gtk_clutter_embed_get_stage (
1049 GTK_CLUTTER_EMBED (priv->video_container))),
1052 clutter_container_add (
1053 CLUTTER_CONTAINER (gtk_clutter_embed_get_stage (
1054 GTK_CLUTTER_EMBED (priv->video_container))),
1058 size_constraint = clutter_bind_constraint_new (
1059 gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->video_container)),
1060 CLUTTER_BIND_SIZE, 0);
1061 clutter_actor_add_constraint (priv->video_box, size_constraint);
1063 priv->remote_user_avatar_widget = gtk_image_new ();
1064 remote_avatar = gtk_clutter_actor_new_with_contents (
1065 priv->remote_user_avatar_widget);
1067 clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_box),
1070 gtk_box_pack_start (GTK_BOX (priv->content_hbox),
1071 priv->video_container, TRUE, TRUE,
1072 CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1074 create_pipeline (self);
1075 create_video_output_widget (self);
1076 create_audio_input (self);
1077 create_video_input (self);
1079 /* The call will be started as soon the pipeline is playing */
1080 priv->start_call_when_playing = TRUE;
1082 priv->vbox = gtk_vbox_new (FALSE, 3);
1083 gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
1084 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1086 empathy_call_window_setup_toolbar (self);
1088 h = gtk_hbox_new (FALSE, 3);
1089 gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
1091 priv->sidebar = ev_sidebar_new ();
1092 g_signal_connect (G_OBJECT (priv->sidebar),
1093 "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
1094 g_signal_connect (G_OBJECT (priv->sidebar),
1095 "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
1096 g_signal_connect (G_OBJECT (priv->sidebar), "changed",
1097 G_CALLBACK (empathy_call_window_sidebar_changed_cb), self);
1098 gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
1100 page = empathy_call_window_create_audio_input (self);
1101 ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "audio-input",
1102 _("Audio input"), page);
1104 page = empathy_call_window_create_video_input (self);
1105 ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "video-input",
1106 _("Video input"), page);
1108 priv->dtmf_panel = empathy_call_window_create_dtmf (self);
1109 ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "dialpad",
1110 _("Dialpad"), priv->dtmf_panel);
1112 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1114 /* Put the details vbox in a scroll window as it can require a lot of
1115 * horizontal space. */
1116 scroll = gtk_scrolled_window_new (NULL, NULL);
1117 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scroll),
1118 priv->details_vbox);
1120 ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "details", _("Details"),
1123 gtk_widget_show_all (top_vbox);
1125 gtk_widget_hide (priv->sidebar);
1127 priv->fullscreen = empathy_call_window_fullscreen_new (self);
1129 empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1130 priv->video_output);
1132 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1133 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1135 g_signal_connect (G_OBJECT (self), "realize",
1136 G_CALLBACK (empathy_call_window_realized_cb), self);
1138 g_signal_connect (G_OBJECT (self), "delete-event",
1139 G_CALLBACK (empathy_call_window_delete_cb), self);
1141 g_signal_connect (G_OBJECT (self), "window-state-event",
1142 G_CALLBACK (empathy_call_window_state_event_cb), self);
1144 g_signal_connect (G_OBJECT (self), "key-press-event",
1145 G_CALLBACK (empathy_call_window_key_press_cb), self);
1147 priv->timer = g_timer_new ();
1149 g_object_ref (priv->ui_manager);
1150 g_object_unref (gui);
1152 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
1154 empathy_geometry_bind (GTK_WINDOW (self), "call-window");
1157 /* Instead of specifying a width and a height, we specify only one size. That's
1158 because we want a square avatar icon. */
1160 init_contact_avatar_with_size (EmpathyContact *contact,
1161 GtkWidget *image_widget,
1164 GdkPixbuf *pixbuf_avatar = NULL;
1166 if (contact != NULL)
1168 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1172 if (pixbuf_avatar == NULL)
1174 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
1175 EMPATHY_IMAGE_AVATAR_DEFAULT, size);
1178 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1180 if (pixbuf_avatar != NULL)
1181 g_object_unref (pixbuf_avatar);
1185 set_window_title (EmpathyCallWindow *self)
1187 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1190 if (priv->contact != NULL)
1192 /* translators: Call is a noun and %s is the contact name. This string
1193 * is used in the window title */
1194 tmp = g_strdup_printf (_("Call with %s"),
1195 empathy_contact_get_alias (priv->contact));
1196 gtk_window_set_title (GTK_WINDOW (self), tmp);
1201 gtk_window_set_title (GTK_WINDOW (self), _("Call with %d participants"));
1206 contact_name_changed_cb (EmpathyContact *contact,
1207 GParamSpec *pspec, EmpathyCallWindow *self)
1209 set_window_title (self);
1213 contact_avatar_changed_cb (EmpathyContact *contact,
1214 GParamSpec *pspec, GtkWidget *avatar_widget)
1217 GtkAllocation allocation;
1219 gtk_widget_get_allocation (avatar_widget, &allocation);
1220 size = allocation.height;
1224 /* the widget is not allocated yet, set a default size */
1225 size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1226 REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1229 init_contact_avatar_with_size (contact, avatar_widget, size);
1233 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1234 EmpathyCallHandler *handler)
1236 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1238 g_signal_connect (priv->contact, "notify::name",
1239 G_CALLBACK (contact_name_changed_cb), self);
1240 g_signal_connect (priv->contact, "notify::avatar",
1241 G_CALLBACK (contact_avatar_changed_cb),
1242 priv->remote_user_avatar_widget);
1244 set_window_title (self);
1246 init_contact_avatar_with_size (priv->contact,
1247 priv->remote_user_avatar_widget,
1248 MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1249 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1251 /* The remote avatar is shown by default and will be hidden when we receive
1252 video from the remote side. */
1253 clutter_actor_hide (priv->video_output);
1254 gtk_widget_show (priv->remote_user_avatar_widget);
1258 update_send_codec (EmpathyCallWindow *self,
1261 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1268 codec = empathy_call_handler_get_send_audio_codec (priv->handler);
1269 widget = priv->acodec_encoding_label;
1273 codec = empathy_call_handler_get_send_video_codec (priv->handler);
1274 widget = priv->vcodec_encoding_label;
1280 tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
1281 gtk_label_set_text (GTK_LABEL (widget), tmp);
1286 send_audio_codec_notify_cb (GObject *object,
1290 EmpathyCallWindow *self = user_data;
1292 update_send_codec (self, TRUE);
1296 send_video_codec_notify_cb (GObject *object,
1300 EmpathyCallWindow *self = user_data;
1302 update_send_codec (self, FALSE);
1306 update_recv_codec (EmpathyCallWindow *self,
1309 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1312 GString *str = NULL;
1316 codecs = empathy_call_handler_get_recv_audio_codecs (priv->handler);
1317 widget = priv->acodec_decoding_label;
1321 codecs = empathy_call_handler_get_recv_video_codecs (priv->handler);
1322 widget = priv->vcodec_decoding_label;
1328 for (l = codecs; l != NULL; l = g_list_next (l))
1330 FsCodec *codec = l->data;
1333 str = g_string_new (NULL);
1335 g_string_append (str, ", ");
1337 g_string_append_printf (str, "%s/%u", codec->encoding_name,
1341 gtk_label_set_text (GTK_LABEL (widget), str->str);
1342 g_string_free (str, TRUE);
1346 recv_audio_codecs_notify_cb (GObject *object,
1350 EmpathyCallWindow *self = user_data;
1352 update_recv_codec (self, TRUE);
1356 recv_video_codecs_notify_cb (GObject *object,
1360 EmpathyCallWindow *self = user_data;
1362 update_recv_codec (self, FALSE);
1365 static const gchar *
1366 candidate_type_to_str (FsCandidate *candidate)
1368 switch (candidate->type)
1370 case FS_CANDIDATE_TYPE_HOST:
1372 case FS_CANDIDATE_TYPE_SRFLX:
1373 return "server reflexive";
1374 case FS_CANDIDATE_TYPE_PRFLX:
1375 return "peer reflexive";
1376 case FS_CANDIDATE_TYPE_RELAY:
1378 case FS_CANDIDATE_TYPE_MULTICAST:
1385 static const gchar *
1386 candidate_type_to_desc (FsCandidate *candidate)
1388 switch (candidate->type)
1390 case FS_CANDIDATE_TYPE_HOST:
1391 return _("The IP address as seen by the machine");
1392 case FS_CANDIDATE_TYPE_SRFLX:
1393 return _("The IP address as seen by a server on the Internet");
1394 case FS_CANDIDATE_TYPE_PRFLX:
1395 return _("The IP address of the peer as seen by the other side");
1396 case FS_CANDIDATE_TYPE_RELAY:
1397 return _("The IP address of a relay server");
1398 case FS_CANDIDATE_TYPE_MULTICAST:
1399 return _("The IP address of the multicast group");
1406 update_candidat_widget (EmpathyCallWindow *self,
1409 FsCandidate *candidate)
1413 g_assert (candidate != NULL);
1414 str = g_strdup_printf ("%s %u (%s)", candidate->ip,
1415 candidate->port, candidate_type_to_str (candidate));
1417 gtk_label_set_text (GTK_LABEL (label), str);
1418 gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
1424 candidates_changed_cb (GObject *object,
1426 EmpathyCallWindow *self)
1428 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1429 FsCandidate *candidate = NULL;
1431 if (type == FS_MEDIA_TYPE_VIDEO)
1433 /* Update remote candidate */
1434 candidate = empathy_call_handler_get_video_remote_candidate (
1437 update_candidat_widget (self, priv->video_remote_candidate_label,
1438 priv->video_remote_candidate_info_img, candidate);
1440 /* Update local candidate */
1441 candidate = empathy_call_handler_get_video_local_candidate (
1444 update_candidat_widget (self, priv->video_local_candidate_label,
1445 priv->video_local_candidate_info_img, candidate);
1449 /* Update remote candidate */
1450 candidate = empathy_call_handler_get_audio_remote_candidate (
1453 update_candidat_widget (self, priv->audio_remote_candidate_label,
1454 priv->audio_remote_candidate_info_img, candidate);
1456 /* Update local candidate */
1457 candidate = empathy_call_handler_get_audio_local_candidate (
1460 update_candidat_widget (self, priv->audio_local_candidate_label,
1461 priv->audio_local_candidate_info_img, candidate);
1466 empathy_call_window_constructed (GObject *object)
1468 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1469 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1470 TpyCallChannel *call;
1472 g_assert (priv->handler != NULL);
1474 g_object_get (priv->handler, "call-channel", &call, NULL);
1475 priv->outgoing = (call == NULL);
1477 g_object_unref (call);
1479 g_object_get (priv->handler, "target-contact", &priv->contact, NULL);
1480 g_assert (priv->contact != NULL);
1482 empathy_call_window_setup_avatars (self, priv->handler);
1483 empathy_call_window_set_state_connecting (self);
1485 if (!empathy_call_handler_has_initial_video (priv->handler))
1487 gtk_toggle_tool_button_set_active (
1488 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1490 /* If call has InitialVideo, the preview will be started once the call has
1491 * been started (start_call()). */
1493 update_send_codec (self, TRUE);
1494 update_send_codec (self, FALSE);
1495 update_recv_codec (self, TRUE);
1496 update_recv_codec (self, FALSE);
1498 tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
1499 G_CALLBACK (send_audio_codec_notify_cb), self, 0);
1500 tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
1501 G_CALLBACK (send_video_codec_notify_cb), self, 0);
1502 tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
1503 G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
1504 tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
1505 G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
1507 tp_g_signal_connect_object (priv->handler, "candidates-changed",
1508 G_CALLBACK (candidates_changed_cb), self, 0);
1511 static void empathy_call_window_dispose (GObject *object);
1512 static void empathy_call_window_finalize (GObject *object);
1515 empathy_call_window_set_property (GObject *object,
1516 guint property_id, const GValue *value, GParamSpec *pspec)
1518 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1520 switch (property_id)
1522 case PROP_CALL_HANDLER:
1523 priv->handler = g_value_dup_object (value);
1526 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1531 empathy_call_window_get_property (GObject *object,
1532 guint property_id, GValue *value, GParamSpec *pspec)
1534 EmpathyCallWindowPriv *priv = GET_PRIV (object);
1536 switch (property_id)
1538 case PROP_CALL_HANDLER:
1539 g_value_set_object (value, priv->handler);
1542 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1547 empathy_call_window_class_init (
1548 EmpathyCallWindowClass *empathy_call_window_class)
1550 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1551 GParamSpec *param_spec;
1553 g_type_class_add_private (empathy_call_window_class,
1554 sizeof (EmpathyCallWindowPriv));
1556 object_class->constructed = empathy_call_window_constructed;
1557 object_class->set_property = empathy_call_window_set_property;
1558 object_class->get_property = empathy_call_window_get_property;
1560 object_class->dispose = empathy_call_window_dispose;
1561 object_class->finalize = empathy_call_window_finalize;
1563 param_spec = g_param_spec_object ("handler",
1564 "handler", "The call handler",
1565 EMPATHY_TYPE_CALL_HANDLER,
1566 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1567 g_object_class_install_property (object_class,
1568 PROP_CALL_HANDLER, param_spec);
1572 empathy_call_window_dispose (GObject *object)
1574 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1575 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1577 if (priv->dispose_has_run)
1580 priv->dispose_has_run = TRUE;
1582 if (priv->handler != NULL)
1584 empathy_call_handler_stop_call (priv->handler);
1585 tp_clear_object (&priv->handler);
1588 if (priv->bus_message_source_id != 0)
1590 g_source_remove (priv->bus_message_source_id);
1591 priv->bus_message_source_id = 0;
1594 tp_clear_object (&priv->pipeline);
1595 tp_clear_object (&priv->video_input);
1596 tp_clear_object (&priv->audio_input);
1597 tp_clear_object (&priv->video_tee);
1598 tp_clear_object (&priv->ui_manager);
1599 tp_clear_object (&priv->fullscreen);
1601 g_list_free_full (priv->notifiers, g_object_unref);
1603 if (priv->timer_id != 0)
1604 g_source_remove (priv->timer_id);
1607 if (priv->contact != NULL)
1609 g_signal_handlers_disconnect_by_func (priv->contact,
1610 contact_name_changed_cb, self);
1611 priv->contact = NULL;
1615 tp_clear_object (&priv->sound_mgr);
1617 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1621 disconnect_video_output_motion_handler (EmpathyCallWindow *self)
1623 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1625 if (priv->video_output_motion_handler_id != 0)
1627 g_signal_handler_disconnect (G_OBJECT (priv->video_container),
1628 priv->video_output_motion_handler_id);
1629 priv->video_output_motion_handler_id = 0;
1634 empathy_call_window_finalize (GObject *object)
1636 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1637 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1639 disconnect_video_output_motion_handler (self);
1641 /* free any data held directly by the object here */
1642 g_mutex_free (priv->lock);
1644 g_timer_destroy (priv->timer);
1646 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1651 empathy_call_window_new (EmpathyCallHandler *handler)
1653 return EMPATHY_CALL_WINDOW (
1654 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1658 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1659 GstElement *conference, gpointer user_data)
1661 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1662 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1663 FsElementAddedNotifier *notifier;
1666 DEBUG ("Conference added");
1668 /* Add notifier to set the various element properties as needed */
1669 notifier = fs_element_added_notifier_new ();
1670 keyfile = fs_utils_get_default_element_properties (conference);
1672 if (keyfile != NULL)
1673 fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile);
1675 fs_element_added_notifier_add (notifier, GST_BIN (priv->pipeline));
1677 priv->notifiers = g_list_prepend (priv->notifiers, notifier);
1679 gst_bin_add (GST_BIN (priv->pipeline), conference);
1680 gst_element_set_state (conference, GST_STATE_PLAYING);
1684 empathy_call_window_conference_removed_cb (EmpathyCallHandler *handler,
1685 GstElement *conference, gpointer user_data)
1687 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1688 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1690 gst_bin_remove (GST_BIN (priv->pipeline), conference);
1691 gst_element_set_state (conference, GST_STATE_NULL);
1695 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1697 GstStateChangeReturn state_change_return;
1698 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1700 if (priv->pipeline == NULL)
1703 if (priv->bus_message_source_id != 0)
1705 g_source_remove (priv->bus_message_source_id);
1706 priv->bus_message_source_id = 0;
1709 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1711 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1712 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1714 if (priv->pipeline != NULL)
1715 g_object_unref (priv->pipeline);
1716 priv->pipeline = NULL;
1718 g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1719 empathy_call_window_mic_volume_changed_cb, self);
1721 if (priv->audio_output != NULL)
1722 g_object_unref (priv->audio_output);
1723 priv->audio_output = NULL;
1725 if (priv->video_tee != NULL)
1726 g_object_unref (priv->video_tee);
1727 priv->video_tee = NULL;
1729 if (priv->video_preview != NULL)
1730 clutter_actor_destroy (priv->video_preview);
1731 priv->video_preview = NULL;
1733 priv->funnel = NULL;
1735 create_pipeline (self);
1736 /* Call will be started when user will hit the 'redial' button */
1737 priv->start_call_when_playing = FALSE;
1738 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1744 g_message ("Error: could not destroy pipeline. Closing call window");
1745 gtk_widget_destroy (GTK_WIDGET (self));
1752 reset_details_pane (EmpathyCallWindow *self)
1754 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1756 gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label), _("Unknown"));
1757 gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label), _("Unknown"));
1758 gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label), _("Unknown"));
1759 gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label), _("Unknown"));
1763 empathy_call_window_disconnected (EmpathyCallWindow *self,
1766 gboolean could_disconnect = FALSE;
1767 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1768 gboolean could_reset_pipeline;
1770 /* Leave full screen mode if needed */
1771 gtk_window_unfullscreen (GTK_WINDOW (self));
1773 gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1774 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1776 could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1778 if (priv->call_state == CONNECTING)
1779 empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
1781 if (priv->call_state != REDIALING)
1782 priv->call_state = DISCONNECTED;
1784 if (could_reset_pipeline)
1786 g_mutex_lock (priv->lock);
1788 g_timer_stop (priv->timer);
1790 if (priv->timer_id != 0)
1791 g_source_remove (priv->timer_id);
1794 g_mutex_unlock (priv->lock);
1797 /* We are about to destroy the window, no need to update it or create
1798 * a video preview */
1801 empathy_call_window_status_message (self, _("Disconnected"));
1803 gtk_action_set_sensitive (priv->redial, TRUE);
1804 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1806 /* Unsensitive the camera and mic button */
1807 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1808 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1810 /* Be sure that the mic button is enabled */
1811 gtk_toggle_tool_button_set_active (
1812 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1814 if (priv->camera_state == CAMERA_STATE_ON)
1816 /* Restart the preview with the new pipeline. */
1817 display_video_preview (self, TRUE);
1820 gtk_progress_bar_set_fraction (
1821 GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1823 /* destroy the video output; it will be recreated when we'll redial */
1824 disconnect_video_output_motion_handler (self);
1825 if (priv->video_output != NULL)
1826 clutter_actor_destroy (priv->video_output);
1827 priv->video_output = NULL;
1829 gtk_widget_show (priv->remote_user_avatar_widget);
1831 reset_details_pane (self);
1833 priv->sending_video = FALSE;
1834 priv->call_started = FALSE;
1836 could_disconnect = TRUE;
1838 /* TODO: display the self avatar of the preview (depends if the "Always
1839 * Show Video Preview" is enabled or not) */
1842 return could_disconnect;
1847 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1850 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1851 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1853 if (empathy_call_window_disconnected (self, TRUE) &&
1854 priv->call_state == REDIALING)
1855 empathy_call_window_restart_call (self);
1859 empathy_call_window_sink_removed_cb (EmpathyCallHandler *handler,
1861 FsMediaType media_type,
1862 EmpathyCallWindow *self)
1864 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1866 DEBUG ("removing content");
1869 * This assumes that there is only one video stream per channel...
1872 if ((guint) media_type == FS_MEDIA_TYPE_VIDEO)
1874 if (priv->funnel != NULL)
1878 output = priv->video_output_sink;
1880 gst_element_set_state (output, GST_STATE_NULL);
1881 gst_element_set_state (priv->funnel, GST_STATE_NULL);
1883 gst_bin_remove (GST_BIN (priv->pipeline), output);
1884 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1885 priv->funnel = NULL;
1889 else if (media_type == FS_MEDIA_TYPE_AUDIO)
1891 if (priv->audio_output != NULL)
1893 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1895 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1896 priv->audio_output = NULL;
1904 /* Called with global lock held */
1906 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1908 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1912 if (priv->funnel == NULL)
1914 output = priv->video_output_sink;
1916 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1920 g_warning ("Could not create fsfunnel");
1924 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
1926 gst_object_unref (priv->funnel);
1927 priv->funnel = NULL;
1928 g_warning ("Could not add funnel to pipeline");
1932 if (!gst_bin_add (GST_BIN (priv->pipeline), output))
1934 g_warning ("Could not add the video output widget to the pipeline");
1938 if (!gst_element_link (priv->funnel, output))
1940 g_warning ("Could not link output sink to funnel");
1941 goto error_output_added;
1944 if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1946 g_warning ("Could not start video sink");
1947 goto error_output_added;
1950 if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1952 g_warning ("Could not start funnel");
1953 goto error_output_added;
1957 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1960 g_warning ("Could not get request pad from funnel");
1967 gst_element_set_locked_state (priv->funnel, TRUE);
1968 gst_element_set_locked_state (output, TRUE);
1970 gst_element_set_state (priv->funnel, GST_STATE_NULL);
1971 gst_element_set_state (output, GST_STATE_NULL);
1973 gst_bin_remove (GST_BIN (priv->pipeline), output);
1974 gst_element_set_locked_state (output, FALSE);
1978 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1979 priv->funnel = NULL;
1984 /* Called with global lock held */
1986 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1988 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1990 GstPadTemplate *template;
1992 if (priv->audio_output == NULL)
1994 priv->audio_output = empathy_audio_sink_new ();
1995 g_object_ref_sink (priv->audio_output);
1997 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
1999 g_warning ("Could not add audio sink to pipeline");
2000 g_object_unref (priv->audio_output);
2001 goto error_add_output;
2004 if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2006 g_warning ("Could not start audio sink");
2011 template = gst_element_class_get_pad_template (
2012 GST_ELEMENT_GET_CLASS (priv->audio_output), "sink%d");
2014 pad = gst_element_request_pad (priv->audio_output,
2015 template, NULL, NULL);
2019 g_warning ("Could not get sink pad from sink");
2026 gst_element_set_locked_state (priv->audio_output, TRUE);
2027 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
2028 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
2029 priv->audio_output = NULL;
2037 empathy_call_window_update_timer (gpointer user_data)
2039 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2040 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2044 time_ = g_timer_elapsed (priv->timer, NULL);
2046 /* Translators: 'status - minutes:seconds' the caller has been connected */
2047 str = g_strdup_printf (_("%s — %d:%02dm"),
2048 priv->call_state == HELD ? _("On hold") : _("Connected"),
2049 (int) time_ / 60, (int) time_ % 60);
2050 empathy_call_window_status_message (self, str);
2058 display_error (EmpathyCallWindow *self,
2059 TpyCallChannel *call,
2063 const gchar *details)
2065 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2066 GtkWidget *info_bar;
2067 GtkWidget *content_area;
2074 /* Create info bar */
2075 info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
2078 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
2080 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2082 /* hbox containing the image and the messages vbox */
2083 hbox = gtk_hbox_new (FALSE, 3);
2084 gtk_container_add (GTK_CONTAINER (content_area), hbox);
2087 image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
2088 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2090 /* vbox containing the main message and the details expander */
2091 vbox = gtk_vbox_new (FALSE, 3);
2092 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
2095 txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
2097 label = gtk_label_new (NULL);
2098 gtk_label_set_markup (GTK_LABEL (label), txt);
2099 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2100 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2103 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
2106 if (details != NULL)
2108 GtkWidget *expander;
2110 expander = gtk_expander_new (_("Technical Details"));
2112 txt = g_strdup_printf ("<i>%s</i>", details);
2114 label = gtk_label_new (NULL);
2115 gtk_label_set_markup (GTK_LABEL (label), txt);
2116 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2117 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2120 gtk_container_add (GTK_CONTAINER (expander), label);
2121 gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
2124 g_signal_connect (info_bar, "response",
2125 G_CALLBACK (gtk_widget_destroy), NULL);
2127 gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
2128 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
2129 gtk_widget_show_all (info_bar);
2133 media_stream_error_to_txt (EmpathyCallWindow *self,
2134 TpyCallChannel *call,
2136 TpMediaStreamError error)
2138 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2139 const gchar *cm = NULL;
2145 case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
2147 return g_strdup_printf (
2148 _("%s's software does not understand any of the audio formats "
2149 "supported by your computer"),
2150 empathy_contact_get_alias (priv->contact));
2152 return g_strdup_printf (
2153 _("%s's software does not understand any of the video formats "
2154 "supported by your computer"),
2155 empathy_contact_get_alias (priv->contact));
2157 case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
2158 return g_strdup_printf (
2159 _("Can't establish a connection to %s. "
2160 "One of you might be on a network that does not allow "
2161 "direct connections."),
2162 empathy_contact_get_alias (priv->contact));
2164 case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
2165 return g_strdup (_("There was a failure on the network"));
2167 case TP_MEDIA_STREAM_ERROR_NO_CODECS:
2169 return g_strdup (_("The audio formats necessary for this call "
2170 "are not installed on your computer"));
2172 return g_strdup (_("The video formats necessary for this call "
2173 "are not installed on your computer"));
2175 case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
2176 tp_connection_parse_object_path (
2177 tp_channel_borrow_connection (TP_CHANNEL (call)),
2180 url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
2181 "product=Telepathy&component=%s", cm);
2183 result = g_strdup_printf (
2184 _("Something unexpected happened in a Telepathy component. "
2185 "Please <a href=\"%s\">report this bug</a> and attach "
2186 "logs gathered from the 'Debug' window in the Help menu."), url);
2192 case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
2193 return g_strdup (_("There was a failure in the call engine"));
2195 case TP_MEDIA_STREAM_ERROR_EOS:
2196 return g_strdup (_("The end of the stream was reached"));
2198 case TP_MEDIA_STREAM_ERROR_UNKNOWN:
2205 empathy_call_window_stream_error (EmpathyCallWindow *self,
2206 TpyCallChannel *call,
2215 desc = media_stream_error_to_txt (self, call, audio, code);
2218 /* No description, use the error message. That's not great as it's not
2219 * localized but it's better than nothing. */
2220 display_error (self, call, icon, title, msg, NULL);
2224 display_error (self, call, icon, title, desc, msg);
2230 empathy_call_window_audio_stream_error (TpyCallChannel *call,
2233 EmpathyCallWindow *self)
2235 empathy_call_window_stream_error (self, call, TRUE, code, msg,
2236 "gnome-stock-mic", _("Can't establish audio stream"));
2240 empathy_call_window_video_stream_error (TpyCallChannel *call,
2243 EmpathyCallWindow *self)
2245 empathy_call_window_stream_error (self, call, FALSE, code, msg,
2246 "camera-web", _("Can't establish video stream"));
2251 empathy_call_window_state_changed_cb (EmpathyCallHandler *handler,
2253 EmpathyCallWindow *self)
2255 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2256 TpyCallChannel *call;
2257 gboolean can_send_video;
2259 if (state != TPY_CALL_STATE_ACCEPTED)
2262 if (priv->call_state == CONNECTED)
2265 g_timer_start (priv->timer);
2266 priv->call_state = CONNECTED;
2268 empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2270 can_send_video = priv->video_input != NULL &&
2271 empathy_contact_can_voip_video (priv->contact);
2273 g_object_get (priv->handler, "call-channel", &call, NULL);
2275 if (tpy_call_channel_has_dtmf (call))
2276 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2278 if (priv->video_input == NULL)
2279 empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
2281 gtk_widget_set_sensitive (priv->camera_button, can_send_video);
2283 gtk_action_set_sensitive (priv->redial, FALSE);
2284 gtk_widget_set_sensitive (priv->redial_button, FALSE);
2286 gtk_widget_set_sensitive (priv->mic_button, TRUE);
2288 clutter_actor_hide (priv->video_output);
2289 gtk_widget_show (priv->remote_user_avatar_widget);
2291 g_object_unref (call);
2293 g_mutex_lock (priv->lock);
2295 priv->timer_id = g_timeout_add_seconds (1,
2296 empathy_call_window_update_timer, self);
2298 g_mutex_unlock (priv->lock);
2300 empathy_call_window_update_timer (self);
2302 gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
2306 emapthy_call_window_show_video_output_cb (gpointer user_data)
2308 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2310 gtk_widget_hide (self->priv->remote_user_avatar_widget);
2311 clutter_actor_show (self->priv->video_output);
2316 /* Called from the streaming thread */
2318 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
2319 GstPad *src, guint media_type, gpointer user_data)
2321 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2322 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2323 gboolean retval = FALSE;
2327 g_mutex_lock (priv->lock);
2331 case TP_MEDIA_STREAM_TYPE_AUDIO:
2332 pad = empathy_call_window_get_audio_sink_pad (self);
2334 case TP_MEDIA_STREAM_TYPE_VIDEO:
2335 g_idle_add (emapthy_call_window_show_video_output_cb, self);
2336 pad = empathy_call_window_get_video_sink_pad (self);
2339 g_assert_not_reached ();
2345 if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2346 g_warning ("Could not link %s sink pad",
2347 media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2351 gst_object_unref (pad);
2355 /* If no sink could be linked, try to add fakesink to prevent the whole call
2360 GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2362 if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2364 GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2365 if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2366 GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2368 gst_element_set_locked_state (fakesink, TRUE);
2369 gst_element_set_state (fakesink, GST_STATE_NULL);
2370 gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2374 DEBUG ("Could not link real sink, linked fakesink instead");
2376 gst_object_unref (sinkpad);
2380 gst_object_unref (fakesink);
2385 g_mutex_unlock (priv->lock);
2391 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
2392 GstPad *sink, FsMediaType media_type, gpointer user_data)
2394 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2395 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2397 gboolean retval = FALSE;
2401 case FS_MEDIA_TYPE_AUDIO:
2402 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2404 g_warning ("Could not add audio source to pipeline");
2408 pad = gst_element_get_static_pad (priv->audio_input, "src");
2411 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2412 g_warning ("Could not get source pad from audio source");
2416 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2418 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2419 g_warning ("Could not link audio source to farsight");
2423 if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2425 g_warning ("Could not start audio source");
2426 gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2427 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2433 case FS_MEDIA_TYPE_VIDEO:
2434 if (priv->video_tee != NULL)
2436 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2437 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2439 g_warning ("Could not link video source input pipeline");
2442 gst_object_unref (pad);
2448 g_assert_not_reached ();
2455 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2457 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2458 GstElement *preview;
2460 disable_camera (self);
2462 DEBUG ("remove video input");
2463 preview = priv->video_preview_sink;
2465 gst_element_set_state (priv->video_input, GST_STATE_NULL);
2466 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2467 gst_element_set_state (preview, GST_STATE_NULL);
2469 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2472 g_object_unref (priv->video_input);
2473 priv->video_input = NULL;
2474 g_object_unref (priv->video_tee);
2475 priv->video_tee = NULL;
2476 clutter_actor_destroy (priv->video_preview);
2477 priv->video_preview = NULL;
2479 gtk_widget_set_sensitive (priv->camera_button, FALSE);
2483 start_call (EmpathyCallWindow *self)
2485 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2487 priv->call_started = TRUE;
2488 empathy_call_handler_start_call (priv->handler,
2489 gtk_get_current_event_time ());
2491 if (empathy_call_handler_has_initial_video (priv->handler))
2493 TpyCallChannel *call;
2496 g_object_get (priv->handler, "call-channel", &call, NULL);
2497 s = tpy_call_channel_get_video_state (call);
2499 if (s == TPY_SENDING_STATE_PENDING_SEND ||
2500 s == TPY_SENDING_STATE_SENDING)
2502 /* Enable 'send video' buttons and display the preview */
2503 gtk_toggle_tool_button_set_active (
2504 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), TRUE);
2508 gtk_toggle_tool_button_set_active (
2509 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
2511 if (priv->video_preview == NULL)
2513 create_video_preview (self);
2514 add_video_preview_to_pipeline (self);
2518 g_object_unref (call);
2523 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2526 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2527 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2530 empathy_call_handler_bus_message (priv->handler, bus, message);
2532 switch (GST_MESSAGE_TYPE (message))
2534 case GST_MESSAGE_STATE_CHANGED:
2535 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2537 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2538 if (newstate == GST_STATE_PAUSED)
2539 empathy_call_window_setup_video_input (self);
2541 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2542 !priv->call_started)
2544 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2545 if (newstate == GST_STATE_PAUSED)
2547 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2548 priv->pipeline_playing = TRUE;
2550 if (priv->start_call_when_playing)
2555 case GST_MESSAGE_ERROR:
2557 GError *error = NULL;
2558 GstElement *gst_error;
2561 gst_message_parse_error (message, &error, &debug);
2562 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2564 g_message ("Element error: %s -- %s\n", error->message, debug);
2566 if (g_str_has_prefix (gst_element_get_name (gst_error),
2567 VIDEO_INPUT_ERROR_PREFIX))
2569 /* Remove the video input and continue */
2570 if (priv->video_input != NULL)
2571 empathy_call_window_remove_video_input (self);
2572 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2576 empathy_call_window_disconnected (self, TRUE);
2578 g_error_free (error);
2581 case GST_MESSAGE_UNKNOWN:
2582 case GST_MESSAGE_EOS:
2583 case GST_MESSAGE_WARNING:
2584 case GST_MESSAGE_INFO:
2585 case GST_MESSAGE_TAG:
2586 case GST_MESSAGE_BUFFERING:
2587 case GST_MESSAGE_STATE_DIRTY:
2588 case GST_MESSAGE_STEP_DONE:
2589 case GST_MESSAGE_CLOCK_PROVIDE:
2590 case GST_MESSAGE_CLOCK_LOST:
2591 case GST_MESSAGE_NEW_CLOCK:
2592 case GST_MESSAGE_STRUCTURE_CHANGE:
2593 case GST_MESSAGE_STREAM_STATUS:
2594 case GST_MESSAGE_APPLICATION:
2595 case GST_MESSAGE_ELEMENT:
2596 case GST_MESSAGE_SEGMENT_START:
2597 case GST_MESSAGE_SEGMENT_DONE:
2598 case GST_MESSAGE_DURATION:
2599 case GST_MESSAGE_ANY:
2608 empathy_call_window_members_changed_cb (TpyCallChannel *call,
2609 GHashTable *members,
2610 EmpathyCallWindow *self)
2612 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2613 GHashTableIter iter;
2614 gpointer key, value;
2615 gboolean held = FALSE;
2617 g_hash_table_iter_init (&iter, members);
2618 while (g_hash_table_iter_next (&iter, &key, &value))
2620 if (GPOINTER_TO_INT (value) & TPY_CALL_MEMBER_FLAG_HELD)
2622 /* This assumes this is a 1-1 call, otherwise one participant
2623 * putting the call on hold wouldn't mean the call is on hold
2631 priv->call_state = HELD;
2632 else if (priv->call_state == HELD)
2633 priv->call_state = CONNECTED;
2637 call_handler_notify_call_cb (EmpathyCallHandler *handler,
2639 EmpathyCallWindow *self)
2641 EmpathyCallWindowPriv *priv = GET_PRIV (self);
2642 TpyCallChannel *call;
2644 g_object_get (priv->handler, "call-channel", &call, NULL);
2649 tp_g_signal_connect_object (call, "audio-stream-error",
2650 G_CALLBACK (empathy_call_window_audio_stream_error), self, 0);
2651 tp_g_signal_connect_object (call, "video-stream-error",
2652 G_CALLBACK (empathy_call_window_video_stream_error), self, 0);
2655 tp_g_signal_connect_object (call, "members-changed",
2656 G_CALLBACK (empathy_call_window_members_changed_cb), self, 0);
2658 g_object_unref (call);
2662 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2664 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2665 TpyCallChannel *call;
2667 g_signal_connect (priv->handler, "state-changed",
2668 G_CALLBACK (empathy_call_window_state_changed_cb), window);
2669 g_signal_connect (priv->handler, "conference-added",
2670 G_CALLBACK (empathy_call_window_conference_added_cb), window);
2671 g_signal_connect (priv->handler, "conference-removed",
2672 G_CALLBACK (empathy_call_window_conference_removed_cb), window);
2673 g_signal_connect (priv->handler, "closed",
2674 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2675 g_signal_connect (priv->handler, "src-pad-added",
2676 G_CALLBACK (empathy_call_window_src_added_cb), window);
2677 g_signal_connect (priv->handler, "sink-pad-added",
2678 G_CALLBACK (empathy_call_window_sink_added_cb), window);
2679 g_signal_connect (priv->handler, "sink-pad-removed",
2680 G_CALLBACK (empathy_call_window_sink_removed_cb), window);
2682 g_object_get (priv->handler, "call-channel", &call, NULL);
2685 call_handler_notify_call_cb (priv->handler, NULL, window);
2686 g_object_unref (call);
2690 /* call-channel doesn't exist yet, we'll connect signals once it has been
2692 g_signal_connect (priv->handler, "notify::call-channel",
2693 G_CALLBACK (call_handler_notify_call_cb), window);
2696 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2700 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2701 EmpathyCallWindow *window)
2703 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2705 if (priv->pipeline != NULL)
2707 if (priv->bus_message_source_id != 0)
2709 g_source_remove (priv->bus_message_source_id);
2710 priv->bus_message_source_id = 0;
2713 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2716 if (priv->call_state == CONNECTING)
2717 empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2723 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2726 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2728 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2733 gtk_widget_hide (priv->sidebar);
2734 gtk_widget_hide (menu);
2735 gtk_widget_hide (priv->vbox);
2736 gtk_widget_hide (priv->statusbar);
2737 gtk_widget_hide (priv->toolbar);
2741 if (priv->sidebar_was_visible_before_fs)
2742 gtk_widget_show (priv->sidebar);
2744 gtk_widget_show (menu);
2745 gtk_widget_show (priv->vbox);
2746 gtk_widget_show (priv->statusbar);
2747 gtk_widget_show (priv->toolbar);
2749 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2750 priv->original_height_before_fs);
2755 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2757 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2759 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2760 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2761 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2762 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2764 if (priv->video_output != NULL)
2767 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2768 priv->video_output, TRUE, TRUE,
2769 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2774 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2775 priv->vbox, TRUE, TRUE,
2776 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2781 empathy_call_window_state_event_cb (GtkWidget *widget,
2782 GdkEventWindowState *event, EmpathyCallWindow *window)
2784 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2786 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2787 gboolean set_fullscreen = event->new_window_state &
2788 GDK_WINDOW_STATE_FULLSCREEN;
2792 gboolean sidebar_was_visible;
2793 GtkAllocation allocation;
2794 gint original_width, original_height;
2796 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2797 original_width = allocation.width;
2798 original_height = allocation.height;
2800 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2802 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2803 priv->original_width_before_fs = original_width;
2804 priv->original_height_before_fs = original_height;
2806 if (priv->video_output_motion_handler_id == 0 &&
2807 priv->video_output != NULL)
2809 priv->video_output_motion_handler_id = g_signal_connect (
2810 G_OBJECT (priv->video_container), "motion-notify-event",
2811 G_CALLBACK (empathy_call_window_video_output_motion_notify),
2817 disconnect_video_output_motion_handler (window);
2820 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2822 show_controls (window, set_fullscreen);
2823 show_borders (window, set_fullscreen);
2824 gtk_action_set_stock_id (priv->menu_fullscreen,
2825 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2826 priv->is_fullscreen = set_fullscreen;
2833 empathy_call_window_update_sidebar_buttons (EmpathyCallWindow *window,
2836 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2838 /* Update dialpad button */
2839 g_signal_handlers_block_by_func (priv->dialpad_button,
2840 empathy_call_window_dialpad_cb, window);
2841 gtk_toggle_tool_button_set_active (
2842 GTK_TOGGLE_TOOL_BUTTON (priv->dialpad_button),
2844 g_signal_handlers_unblock_by_func (priv->dialpad_button,
2845 empathy_call_window_dialpad_cb, window);
2847 /* Update sidebar menu */
2848 g_signal_handlers_block_by_func (priv->menu_sidebar,
2849 empathy_call_window_sidebar_cb, window);
2850 gtk_toggle_action_set_active (
2851 GTK_TOGGLE_ACTION (priv->menu_sidebar),
2852 gtk_widget_get_visible (priv->sidebar));
2853 g_signal_handlers_unblock_by_func (priv->menu_sidebar,
2854 empathy_call_window_sidebar_cb, window);
2858 empathy_call_window_show_sidebar (EmpathyCallWindow *window,
2861 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2862 int w, h, sidebar_width, handle_size;
2863 GtkAllocation allocation;
2865 gboolean dialpad_shown;
2867 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2868 w = allocation.width;
2869 h = allocation.height;
2871 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2873 gtk_widget_get_preferred_width (priv->sidebar, &sidebar_width, NULL);
2877 gtk_widget_show (priv->sidebar);
2878 w += sidebar_width + handle_size;
2882 w -= sidebar_width + handle_size;
2883 gtk_widget_hide (priv->sidebar);
2887 gtk_window_resize (GTK_WINDOW (window), w, h);
2889 /* Update the 'Dialpad' menu */
2890 page = ev_sidebar_get_current_page (EV_SIDEBAR (priv->sidebar));
2891 dialpad_shown = active && !tp_strdiff (page, "dialpad");
2894 empathy_call_window_update_sidebar_buttons (window, dialpad_shown);
2898 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2901 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2902 TpyCallChannel *call;
2904 priv->sending_video = (state == CAMERA_STATE_ON);
2906 if (state == CAMERA_STATE_ON)
2908 /* When we start sending video, we want to show the video preview by
2910 display_video_preview (window, TRUE);
2914 display_video_preview (window, FALSE);
2917 if (priv->call_state != CONNECTED)
2920 g_object_get (priv->handler, "call-channel", &call, NULL);
2921 DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
2922 tpy_call_channel_send_video (call, priv->sending_video);
2923 g_object_unref (call);
2927 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2928 EmpathyCallWindow *window)
2930 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2933 active = (gtk_toggle_tool_button_get_active (toggle));
2937 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2939 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2943 /* TODO, Instead of setting the input volume to 0 we should probably
2944 * stop sending but this would cause the audio call to drop if both
2945 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2946 * in the future. GNOME #574574
2948 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2950 gtk_adjustment_set_value (priv->audio_input_adj, 0);
2955 empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
2956 EmpathyCallWindow *window)
2958 empathy_call_window_show_sidebar (window, FALSE);
2962 empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
2963 EmpathyCallWindow *window)
2965 empathy_call_window_show_sidebar (window, TRUE);
2969 empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar,
2971 EmpathyCallWindow *window)
2973 empathy_call_window_update_sidebar_buttons (window,
2974 !tp_strdiff (page, "dialpad"));
2978 empathy_call_window_hangup_cb (gpointer object,
2979 EmpathyCallWindow *window)
2981 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2983 empathy_call_handler_stop_call (priv->handler);
2985 if (empathy_call_window_disconnected (window, FALSE))
2986 gtk_widget_destroy (GTK_WIDGET (window));
2990 empathy_call_window_restart_call (EmpathyCallWindow *window)
2992 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2994 /* Remove error info bars */
2995 gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
2996 (GtkCallback) gtk_widget_destroy, NULL);
2998 create_video_output_widget (window);
3000 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
3001 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
3003 /* While the call was disconnected, the input volume might have changed.
3004 * However, since the audio_input source was destroyed, its volume has not
3005 * been updated during that time. That's why we manually update it here */
3006 empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
3008 priv->outgoing = TRUE;
3009 empathy_call_window_set_state_connecting (window);
3011 if (priv->pipeline_playing)
3012 start_call (window);
3014 /* call will be started when the pipeline is ready */
3015 priv->start_call_when_playing = TRUE;
3018 empathy_call_window_setup_avatars (window, priv->handler);
3020 gtk_action_set_sensitive (priv->redial, FALSE);
3021 gtk_widget_set_sensitive (priv->redial_button, FALSE);
3025 empathy_call_window_redial_cb (gpointer object,
3026 EmpathyCallWindow *window)
3028 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3030 if (priv->call_state == CONNECTED)
3031 priv->call_state = REDIALING;
3033 empathy_call_handler_stop_call (priv->handler);
3035 if (priv->call_state != CONNECTED)
3036 empathy_call_window_restart_call (window);
3040 empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
3041 EmpathyCallWindow *window)
3043 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3046 active = gtk_toggle_tool_button_get_active (button);
3049 ev_sidebar_set_current_page (EV_SIDEBAR (priv->sidebar), "dialpad");
3051 empathy_call_window_show_sidebar (window, active);
3055 empathy_call_window_sidebar_cb (GtkToggleAction *menu,
3056 EmpathyCallWindow *self)
3058 empathy_call_window_show_sidebar (self,
3059 gtk_toggle_action_get_active (menu));
3063 empathy_call_window_fullscreen_cb (gpointer object,
3064 EmpathyCallWindow *window)
3066 empathy_call_window_fullscreen_toggle (window);
3070 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
3072 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3074 if (priv->is_fullscreen)
3075 gtk_window_unfullscreen (GTK_WINDOW (window));
3077 gtk_window_fullscreen (GTK_WINDOW (window));
3081 empathy_call_window_video_button_press_cb (GtkWidget *video_preview,
3082 GdkEventButton *event, EmpathyCallWindow *window)
3084 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
3086 empathy_call_window_video_menu_popup (window, event->button);
3094 empathy_call_window_key_press_cb (GtkWidget *video_output,
3095 GdkEventKey *event, EmpathyCallWindow *window)
3097 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3099 if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
3101 /* Since we are in fullscreen mode, toggling will bring us back to
3103 empathy_call_window_fullscreen_toggle (window);
3111 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
3112 GdkEventMotion *event, EmpathyCallWindow *window)
3114 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3116 if (priv->is_fullscreen)
3118 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
3125 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
3129 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3131 menu = gtk_ui_manager_get_widget (priv->ui_manager,
3133 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
3134 button, gtk_get_current_event_time ());
3135 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
3139 empathy_call_window_status_message (EmpathyCallWindow *window,
3142 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3144 if (priv->context_id == 0)
3146 priv->context_id = gtk_statusbar_get_context_id (
3147 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
3151 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
3154 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
3159 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
3160 gdouble value, EmpathyCallWindow *window)
3162 EmpathyCallWindowPriv *priv = GET_PRIV (window);
3164 if (priv->audio_output == NULL)
3167 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),