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 <libempathy/empathy-tp-contact-factory.h>
35 #include <libempathy/empathy-call-factory.h>
36 #include <libempathy/empathy-utils.h>
37 #include <libempathy-gtk/empathy-avatar-image.h>
38 #include <libempathy-gtk/empathy-video-widget.h>
39 #include <libempathy-gtk/empathy-audio-src.h>
40 #include <libempathy-gtk/empathy-audio-sink.h>
41 #include <libempathy-gtk/empathy-video-src.h>
42 #include <libempathy-gtk/empathy-ui-utils.h>
43 #include <libempathy-gtk/empathy-sound.h>
45 #include "empathy-call-window.h"
46 #include "empathy-call-window-fullscreen.h"
47 #include "empathy-sidebar.h"
49 #define BUTTON_ID "empathy-call-dtmf-button-id"
51 #define CONTENT_HBOX_BORDER_WIDTH 6
52 #define CONTENT_HBOX_SPACING 3
53 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
55 #define SELF_VIDEO_SECTION_WIDTH 160
56 #define SELF_VIDEO_SECTION_HEIGTH 120
58 /* The avatar's default width and height are set to the same value because we
59 want a square icon. */
60 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
61 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
63 /* If an video input error occurs, the error message will start with "v4l" */
64 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
66 /* The time interval in milliseconds between 2 outgoing rings */
67 #define MS_BETWEEN_RING 500
69 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
78 static guint signals[LAST_SIGNAL] = {0};
82 PROP_CALL_HANDLER = 1,
92 /* private structure */
93 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
95 struct _EmpathyCallWindowPriv
97 gboolean dispose_has_run;
98 EmpathyCallHandler *handler;
99 EmpathyContact *contact;
104 GtkUIManager *ui_manager;
105 GtkWidget *video_output;
106 GtkWidget *video_preview;
107 GtkWidget *remote_user_avatar_widget;
108 GtkWidget *self_user_avatar_widget;
110 GtkWidget *sidebar_button;
111 GtkWidget *statusbar;
112 GtkWidget *volume_button;
113 GtkWidget *redial_button;
114 GtkWidget *mic_button;
115 GtkWidget *camera_button;
118 GtkAction *show_preview;
119 GtkAction *send_video;
121 GtkAction *menu_fullscreen;
123 /* The frames and boxes that contain self and remote avatar and video
124 input/output. When we redial, we destroy and re-create the boxes */
125 GtkWidget *remote_user_output_frame;
126 GtkWidget *self_user_output_frame;
127 GtkWidget *remote_user_output_hbox;
128 GtkWidget *self_user_output_hbox;
130 /* We keep a reference on the hbox which contains the main content so we can
131 easilly repack everything when toggling fullscreen */
132 GtkWidget *content_hbox;
134 /* This vbox is contained in the content_hbox and it contains the
135 self_user_output_frame and the sidebar button. When toggling fullscreen,
136 it needs to be repacked. We keep a reference on it for easier access. */
139 gulong video_output_motion_handler_id;
142 GtkWidget *volume_progress_bar;
143 GtkAdjustment *audio_input_adj;
145 GtkWidget *dtmf_panel;
147 GstElement *video_input;
148 GstElement *audio_input;
149 GstElement *audio_output;
150 GstElement *pipeline;
151 GstElement *video_tee;
154 GstElement *liveadder;
161 GtkWidget *video_contrast;
162 GtkWidget *video_brightness;
163 GtkWidget *video_gamma;
166 gboolean call_started;
167 gboolean sending_video;
169 EmpathyCallWindowFullscreen *fullscreen;
170 gboolean is_fullscreen;
172 /* Those fields represent the state of the window before it actually was in
174 gboolean sidebar_was_visible_before_fs;
175 gint original_width_before_fs;
176 gint original_height_before_fs;
179 #define GET_PRIV(o) \
180 (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW, \
181 EmpathyCallWindowPriv))
183 static void empathy_call_window_realized_cb (GtkWidget *widget,
184 EmpathyCallWindow *window);
186 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
187 GdkEvent *event, EmpathyCallWindow *window);
189 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
190 GdkEventWindowState *event, EmpathyCallWindow *window);
192 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
193 EmpathyCallWindow *window);
195 static void empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
196 EmpathyCallWindow *window);
198 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
201 static void empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
202 EmpathyCallWindow *window);
204 static void empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
205 EmpathyCallWindow *window);
207 static void empathy_call_window_mic_toggled_cb (
208 GtkToggleToolButton *toggle, EmpathyCallWindow *window);
210 static void empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
211 EmpathyCallWindow *window);
213 static void empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
214 EmpathyCallWindow *window);
216 static void empathy_call_window_hangup_cb (gpointer object,
217 EmpathyCallWindow *window);
219 static void empathy_call_window_fullscreen_cb (gpointer object,
220 EmpathyCallWindow *window);
222 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
224 static gboolean empathy_call_window_video_button_press_cb (GtkWidget *video_output,
225 GdkEventButton *event, EmpathyCallWindow *window);
227 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
228 GdkEventKey *event, EmpathyCallWindow *window);
230 static gboolean empathy_call_window_video_output_motion_notify (GtkWidget *widget,
231 GdkEventMotion *event, EmpathyCallWindow *window);
233 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
236 static void empathy_call_window_redial_cb (gpointer object,
237 EmpathyCallWindow *window);
239 static void empathy_call_window_restart_call (EmpathyCallWindow *window);
241 static void empathy_call_window_status_message (EmpathyCallWindow *window,
244 static void empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
245 EmpathyCallWindow *window);
247 static gboolean empathy_call_window_bus_message (GstBus *bus,
248 GstMessage *message, gpointer user_data);
251 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
252 gdouble value, EmpathyCallWindow *window);
255 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
257 EmpathyCallWindowPriv *priv = GET_PRIV (self);
258 GtkToolItem *tool_item;
260 /* Add an empty expanded GtkToolItem so the volume button is at the end of
262 tool_item = gtk_tool_item_new ();
263 gtk_tool_item_set_expand (tool_item, TRUE);
264 gtk_widget_show (GTK_WIDGET (tool_item));
265 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
267 priv->volume_button = gtk_volume_button_new ();
268 /* FIXME listen to the audiosinks signals and update the button according to
269 * that, for now starting out at 1.0 and assuming only the app changes the
271 gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
272 g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
273 G_CALLBACK (empathy_call_window_volume_changed_cb), self);
275 tool_item = gtk_tool_item_new ();
276 gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
277 gtk_widget_show_all (GTK_WIDGET (tool_item));
278 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
282 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
284 EmpathyCallWindowPriv *priv = GET_PRIV (window);
289 g_object_get (priv->handler, "tp-call", &call, NULL);
291 button_quark = g_quark_from_static_string (BUTTON_ID);
292 event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
295 empathy_tp_call_start_tone (call, event);
297 g_object_unref (call);
301 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
303 EmpathyCallWindowPriv *priv = GET_PRIV (window);
306 g_object_get (priv->handler, "tp-call", &call, NULL);
308 empathy_tp_call_stop_tone (call);
310 g_object_unref (call);
314 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
322 } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
323 { "2", TP_DTMF_EVENT_DIGIT_2 },
324 { "3", TP_DTMF_EVENT_DIGIT_3 },
325 { "4", TP_DTMF_EVENT_DIGIT_4 },
326 { "5", TP_DTMF_EVENT_DIGIT_5 },
327 { "6", TP_DTMF_EVENT_DIGIT_6 },
328 { "7", TP_DTMF_EVENT_DIGIT_7 },
329 { "8", TP_DTMF_EVENT_DIGIT_8 },
330 { "9", TP_DTMF_EVENT_DIGIT_9 },
331 { "#", TP_DTMF_EVENT_HASH },
332 { "0", TP_DTMF_EVENT_DIGIT_0 },
333 { "*", TP_DTMF_EVENT_ASTERISK },
336 button_quark = g_quark_from_static_string (BUTTON_ID);
338 table = gtk_table_new (4, 3, TRUE);
340 for (i = 0; dtmfbuttons[i].label != NULL; i++)
342 GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
343 gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
344 i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
346 g_object_set_qdata (G_OBJECT (button), button_quark,
347 GUINT_TO_POINTER (dtmfbuttons[i].event));
349 g_signal_connect (G_OBJECT (button), "pressed",
350 G_CALLBACK (dtmf_button_pressed_cb), self);
351 g_signal_connect (G_OBJECT (button), "released",
352 G_CALLBACK (dtmf_button_released_cb), self);
359 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
360 gchar *label_text, GtkWidget *bin)
362 GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
363 GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
364 GtkWidget *label = gtk_label_new (label_text);
366 gtk_widget_set_sensitive (scale, FALSE);
368 gtk_container_add (GTK_CONTAINER (bin), vbox);
370 gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
371 gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
372 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
378 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
379 EmpathyCallWindow *self)
382 EmpathyCallWindowPriv *priv = GET_PRIV (self);
384 empathy_video_src_set_channel (priv->video_input,
385 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
389 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
390 EmpathyCallWindow *self)
393 EmpathyCallWindowPriv *priv = GET_PRIV (self);
395 empathy_video_src_set_channel (priv->video_input,
396 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
400 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
401 EmpathyCallWindow *self)
404 EmpathyCallWindowPriv *priv = GET_PRIV (self);
406 empathy_video_src_set_channel (priv->video_input,
407 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
412 empathy_call_window_create_video_input (EmpathyCallWindow *self)
414 EmpathyCallWindowPriv *priv = GET_PRIV (self);
417 hbox = gtk_hbox_new (TRUE, 3);
419 priv->video_contrast = empathy_call_window_create_video_input_add_slider (
420 self, _("Contrast"), hbox);
422 priv->video_brightness = empathy_call_window_create_video_input_add_slider (
423 self, _("Brightness"), hbox);
425 priv->video_gamma = empathy_call_window_create_video_input_add_slider (
426 self, _("Gamma"), hbox);
432 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
434 EmpathyCallWindowPriv *priv = GET_PRIV (self);
438 supported = empathy_video_src_get_supported_channels (priv->video_input);
440 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
442 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
444 gtk_adjustment_set_value (adj,
445 empathy_video_src_get_channel (priv->video_input,
446 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
448 g_signal_connect (G_OBJECT (adj), "value-changed",
449 G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
451 gtk_widget_set_sensitive (priv->video_contrast, TRUE);
454 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
456 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
458 gtk_adjustment_set_value (adj,
459 empathy_video_src_get_channel (priv->video_input,
460 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
462 g_signal_connect (G_OBJECT (adj), "value-changed",
463 G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
464 gtk_widget_set_sensitive (priv->video_brightness, TRUE);
467 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
469 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
471 gtk_adjustment_set_value (adj,
472 empathy_video_src_get_channel (priv->video_input,
473 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
475 g_signal_connect (G_OBJECT (adj), "value-changed",
476 G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
477 gtk_widget_set_sensitive (priv->video_gamma, TRUE);
482 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
483 EmpathyCallWindow *self)
485 EmpathyCallWindowPriv *priv = GET_PRIV (self);
488 volume = gtk_adjustment_get_value (adj)/100.0;
490 /* Don't store the volume because of muting */
491 if (volume > 0 || gtk_toggle_tool_button_get_active (
492 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
493 priv->volume = volume;
495 /* Ensure that the toggle button is active if the volume is > 0 and inactive
496 * if it's smaller than 0 */
497 if ((volume > 0) != gtk_toggle_tool_button_get_active (
498 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
499 gtk_toggle_tool_button_set_active (
500 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
502 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
507 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
508 gdouble level, EmpathyCallWindow *window)
511 EmpathyCallWindowPriv *priv = GET_PRIV (window);
513 value = CLAMP (pow (10, level / 20), 0.0, 1.0);
514 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar), value);
518 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
520 EmpathyCallWindowPriv *priv = GET_PRIV (self);
521 GtkWidget *hbox, *vbox, *scale, *label;
524 hbox = gtk_hbox_new (TRUE, 3);
526 vbox = gtk_vbox_new (FALSE, 3);
527 gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
529 scale = gtk_vscale_new_with_range (0, 150, 100);
530 gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
531 label = gtk_label_new (_("Volume"));
533 priv->audio_input_adj = adj = gtk_range_get_adjustment (GTK_RANGE (scale));
534 priv->volume = empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
535 (priv->audio_input));
536 gtk_adjustment_set_value (adj, priv->volume * 100);
538 g_signal_connect (G_OBJECT (adj), "value-changed",
539 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
541 gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 3);
542 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
544 priv->volume_progress_bar = gtk_progress_bar_new ();
545 gtk_progress_bar_set_orientation (
546 GTK_PROGRESS_BAR (priv->volume_progress_bar), GTK_PROGRESS_BOTTOM_TO_TOP);
547 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
550 gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
557 empathy_call_window_setup_remote_frame (GstBus *bus, EmpathyCallWindow *self)
559 EmpathyCallWindowPriv *priv = GET_PRIV (self);
561 /* Initializing all the content (UI and output gst elements) related to the
563 priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
565 priv->remote_user_avatar_widget = gtk_image_new ();
566 gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
567 priv->remote_user_avatar_widget, TRUE, TRUE, 0);
569 priv->video_output = empathy_video_widget_new (bus);
570 gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
571 priv->video_output, TRUE, TRUE, 0);
573 gtk_widget_add_events (priv->video_output,
574 GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
575 g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
576 G_CALLBACK (empathy_call_window_video_button_press_cb), self);
578 gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
579 priv->remote_user_output_hbox);
581 priv->audio_output = empathy_audio_sink_new ();
582 gst_object_ref (priv->audio_output);
583 gst_object_sink (priv->audio_output);
587 empathy_call_window_setup_self_frame (GstBus *bus, EmpathyCallWindow *self)
589 EmpathyCallWindowPriv *priv = GET_PRIV (self);
591 /* Initializing all the content (UI and input gst elements) related to the
592 self contact, except for the video preview widget. This widget is only
593 initialized when the "show video preview" option is activated */
594 priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
596 priv->self_user_avatar_widget = gtk_image_new ();
597 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
598 priv->self_user_avatar_widget, TRUE, TRUE, 0);
600 gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
601 priv->self_user_output_hbox);
603 priv->video_input = empathy_video_src_new ();
604 gst_object_ref (priv->video_input);
605 gst_object_sink (priv->video_input);
607 priv->audio_input = empathy_audio_src_new ();
608 gst_object_ref (priv->audio_input);
609 gst_object_sink (priv->audio_input);
611 g_signal_connect (priv->audio_input, "peak-level-changed",
612 G_CALLBACK (empathy_call_window_audio_input_level_changed_cb), self);
616 empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
618 EmpathyCallWindowPriv *priv = GET_PRIV (window);
620 GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
622 if (priv->video_preview != NULL)
624 /* Since the video preview and the video tee are initialized and freed
625 at the same time, if one is initialized, then the other one should
627 g_assert (priv->video_tee != NULL);
631 g_assert (priv->video_tee == NULL);
633 priv->video_tee = gst_element_factory_make ("tee", NULL);
634 gst_object_ref (priv->video_tee);
635 gst_object_sink (priv->video_tee);
637 priv->video_preview = empathy_video_widget_new_with_size (bus,
638 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
639 g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
640 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
641 priv->video_preview, TRUE, TRUE, 0);
643 preview = empathy_video_widget_get_element (
644 EMPATHY_VIDEO_WIDGET (priv->video_preview));
645 gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
646 priv->video_tee, preview, NULL);
647 gst_element_link_many (priv->video_input, priv->video_tee,
650 g_object_unref (bus);
652 gst_element_set_state (preview, GST_STATE_PLAYING);
653 gst_element_set_state (priv->video_input, GST_STATE_PLAYING);
654 gst_element_set_state (priv->video_tee, GST_STATE_PLAYING);
658 empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
660 EmpathyCallWindowPriv *priv = GET_PRIV (window);
662 empathy_call_window_status_message (window, _("Connecting..."));
663 priv->call_state = CONNECTING;
666 empathy_sound_start_playing (GTK_WIDGET (window),
667 EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
671 empathy_call_window_init (EmpathyCallWindow *self)
673 EmpathyCallWindowPriv *priv = GET_PRIV (self);
682 filename = empathy_file_lookup ("empathy-call-window.ui", "src");
683 gui = empathy_builder_get_file (filename,
684 "call_window_vbox", &top_vbox,
686 "statusbar", &priv->statusbar,
687 "redial", &priv->redial_button,
688 "microphone", &priv->mic_button,
689 "camera", &priv->camera_button,
690 "toolbar", &priv->toolbar,
691 "send_video", &priv->send_video,
692 "menuredial", &priv->redial,
693 "show_preview", &priv->show_preview,
694 "ui_manager", &priv->ui_manager,
695 "menufullscreen", &priv->menu_fullscreen,
698 empathy_builder_connect (gui, self,
699 "menuhangup", "activate", empathy_call_window_hangup_cb,
700 "hangup", "clicked", empathy_call_window_hangup_cb,
701 "menuredial", "activate", empathy_call_window_redial_cb,
702 "redial", "clicked", empathy_call_window_redial_cb,
703 "microphone", "toggled", empathy_call_window_mic_toggled_cb,
704 "camera", "toggled", empathy_call_window_camera_toggled_cb,
705 "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
706 "show_preview", "toggled", empathy_call_window_show_preview_toggled_cb,
707 "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
710 priv->lock = g_mutex_new ();
712 gtk_container_add (GTK_CONTAINER (self), top_vbox);
714 empathy_call_window_setup_toolbar (self);
716 priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
717 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
718 CONTENT_HBOX_BORDER_WIDTH);
719 gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
721 priv->pipeline = gst_pipeline_new (NULL);
722 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
723 gst_bus_add_watch (bus, empathy_call_window_bus_message, self);
725 priv->remote_user_output_frame = gtk_frame_new (NULL);
726 gtk_widget_set_size_request (priv->remote_user_output_frame,
727 EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
728 gtk_box_pack_start (GTK_BOX (priv->content_hbox),
729 priv->remote_user_output_frame, TRUE, TRUE,
730 CONTENT_HBOX_CHILDREN_PACKING_PADDING);
731 empathy_call_window_setup_remote_frame (bus, self);
733 priv->self_user_output_frame = gtk_frame_new (NULL);
734 gtk_widget_set_size_request (priv->self_user_output_frame,
735 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
737 priv->vbox = gtk_vbox_new (FALSE, 3);
738 gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
739 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
740 gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame, FALSE,
742 empathy_call_window_setup_self_frame (bus, self);
744 g_object_unref (bus);
746 priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
747 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
748 g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
749 G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
751 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
753 h = gtk_hbox_new (FALSE, 3);
754 gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
755 gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
757 priv->sidebar = empathy_sidebar_new ();
758 g_signal_connect (G_OBJECT (priv->sidebar),
759 "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
760 g_signal_connect (G_OBJECT (priv->sidebar),
761 "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
762 gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
764 priv->dtmf_panel = empathy_call_window_create_dtmf (self);
765 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
768 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
770 page = empathy_call_window_create_audio_input (self);
771 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
774 page = empathy_call_window_create_video_input (self);
775 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
778 gtk_widget_show_all (top_vbox);
780 gtk_widget_hide (priv->sidebar);
782 priv->fullscreen = empathy_call_window_fullscreen_new (self);
783 empathy_call_window_fullscreen_set_video_widget (priv->fullscreen, priv->video_output);
784 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
785 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
787 g_signal_connect (G_OBJECT (self), "realize",
788 G_CALLBACK (empathy_call_window_realized_cb), self);
790 g_signal_connect (G_OBJECT (self), "delete-event",
791 G_CALLBACK (empathy_call_window_delete_cb), self);
793 g_signal_connect (G_OBJECT (self), "window-state-event",
794 G_CALLBACK (empathy_call_window_state_event_cb), self);
796 g_signal_connect (G_OBJECT (self), "key-press-event",
797 G_CALLBACK (empathy_call_window_key_press_cb), self);
799 priv->timer = g_timer_new ();
801 g_object_ref (priv->ui_manager);
802 g_object_unref (gui);
806 /* Instead of specifying a width and a height, we specify only one size. That's
807 because we want a square avatar icon. */
809 init_contact_avatar_with_size (EmpathyContact *contact, GtkWidget *image_widget,
813 GdkPixbuf *pixbuf_avatar = NULL;
817 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
821 if (pixbuf_avatar == NULL)
823 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
827 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
831 set_window_title (EmpathyCallWindow *self)
833 EmpathyCallWindowPriv *priv = GET_PRIV (self);
836 tmp = g_strdup_printf (_("Call with %s"),
837 empathy_contact_get_name (priv->contact));
838 gtk_window_set_title (GTK_WINDOW (self), tmp);
843 contact_name_changed_cb (EmpathyContact *contact,
844 GParamSpec *pspec, EmpathyCallWindow *self)
846 set_window_title (self);
850 contact_avatar_changed_cb (EmpathyContact *contact,
851 GParamSpec *pspec, GtkWidget *avatar_widget)
853 init_contact_avatar_with_size (contact, avatar_widget,
854 avatar_widget->allocation.height);
858 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
859 EmpathyContact *contact, const GError *error, gpointer user_data,
860 GObject *weak_object)
862 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
863 EmpathyCallWindowPriv *priv = GET_PRIV (self);
865 init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
866 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
868 g_signal_connect (contact, "notify::avatar",
869 G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
873 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
874 EmpathyCallHandler *handler)
876 EmpathyCallWindowPriv *priv = GET_PRIV (self);
878 g_object_get (handler, "contact", &(priv->contact), NULL);
880 if (priv->contact != NULL)
882 TpConnection *connection;
883 EmpathyTpContactFactory *factory;
885 set_window_title (self);
887 g_signal_connect (priv->contact, "notify::name",
888 G_CALLBACK (contact_name_changed_cb), self);
889 g_signal_connect (priv->contact, "notify::avatar",
890 G_CALLBACK (contact_avatar_changed_cb),
891 priv->remote_user_avatar_widget);
893 /* Retreiving the self avatar */
894 connection = empathy_contact_get_connection (priv->contact);
895 factory = empathy_tp_contact_factory_dup_singleton (connection);
896 empathy_tp_contact_factory_get_from_handle (factory,
897 tp_connection_get_self_handle (connection),
898 empathy_call_window_got_self_contact_cb, self, NULL, NULL);
900 g_object_unref (factory);
904 g_warning ("call handler doesn't have a contact");
905 gtk_window_set_title (GTK_WINDOW (self), _("Call"));
907 /* Since we can't access the remote contact, we can't get a connection
908 to it and can't get the self contact (and its avatar). This means
909 that we have to manually set the self avatar. */
910 init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
911 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
914 init_contact_avatar_with_size (priv->contact,
915 priv->remote_user_avatar_widget, MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
916 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
918 /* The remote avatar is shown by default and will be hidden when we receive
919 video from the remote side. */
920 gtk_widget_hide (priv->video_output);
921 gtk_widget_show (priv->remote_user_avatar_widget);
925 empathy_call_window_setup_video_preview_visibility (EmpathyCallWindow *self,
926 EmpathyCallHandler *handler)
928 EmpathyCallWindowPriv *priv = GET_PRIV (self);
929 gboolean initial_video = empathy_call_handler_has_initial_video (priv->handler);
931 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
936 empathy_call_window_constructed (GObject *object)
938 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
939 EmpathyCallWindowPriv *priv = GET_PRIV (self);
942 g_assert (priv->handler != NULL);
944 g_object_get (priv->handler, "tp-call", &call, NULL);
945 priv->outgoing = (call == NULL);
947 g_object_unref (call);
949 empathy_call_window_setup_avatars (self, priv->handler);
950 empathy_call_window_setup_video_preview_visibility (self, priv->handler);
951 empathy_call_window_set_state_connecting (self);
954 static void empathy_call_window_dispose (GObject *object);
955 static void empathy_call_window_finalize (GObject *object);
958 empathy_call_window_set_property (GObject *object,
959 guint property_id, const GValue *value, GParamSpec *pspec)
961 EmpathyCallWindowPriv *priv = GET_PRIV (object);
965 case PROP_CALL_HANDLER:
966 priv->handler = g_value_dup_object (value);
969 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
974 empathy_call_window_get_property (GObject *object,
975 guint property_id, GValue *value, GParamSpec *pspec)
977 EmpathyCallWindowPriv *priv = GET_PRIV (object);
981 case PROP_CALL_HANDLER:
982 g_value_set_object (value, priv->handler);
985 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
990 empathy_call_window_class_init (
991 EmpathyCallWindowClass *empathy_call_window_class)
993 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
994 GParamSpec *param_spec;
996 g_type_class_add_private (empathy_call_window_class,
997 sizeof (EmpathyCallWindowPriv));
999 object_class->constructed = empathy_call_window_constructed;
1000 object_class->set_property = empathy_call_window_set_property;
1001 object_class->get_property = empathy_call_window_get_property;
1003 object_class->dispose = empathy_call_window_dispose;
1004 object_class->finalize = empathy_call_window_finalize;
1006 param_spec = g_param_spec_object ("handler",
1007 "handler", "The call handler",
1008 EMPATHY_TYPE_CALL_HANDLER,
1009 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1010 g_object_class_install_property (object_class,
1011 PROP_CALL_HANDLER, param_spec);
1015 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1016 GParamSpec *property, EmpathyCallWindow *self)
1018 empathy_call_window_update_avatars_visibility (call, self);
1022 empathy_call_window_dispose (GObject *object)
1024 EmpathyTpCall *call;
1025 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1026 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1028 if (priv->dispose_has_run)
1031 priv->dispose_has_run = TRUE;
1033 g_object_get (priv->handler, "tp-call", &call, NULL);
1037 g_signal_handlers_disconnect_by_func (call,
1038 empathy_call_window_video_stream_changed_cb, object);
1041 g_object_unref (call);
1043 if (priv->handler != NULL)
1044 g_object_unref (priv->handler);
1046 priv->handler = NULL;
1048 if (priv->pipeline != NULL)
1049 g_object_unref (priv->pipeline);
1050 priv->pipeline = NULL;
1052 if (priv->video_input != NULL)
1053 g_object_unref (priv->video_input);
1054 priv->video_input = NULL;
1056 if (priv->audio_input != NULL)
1057 g_object_unref (priv->audio_input);
1058 priv->audio_input = NULL;
1060 if (priv->audio_output != NULL)
1061 g_object_unref (priv->audio_output);
1062 priv->audio_output = NULL;
1064 if (priv->video_tee != NULL)
1065 g_object_unref (priv->video_tee);
1066 priv->video_tee = NULL;
1068 if (priv->timer_id != 0)
1069 g_source_remove (priv->timer_id);
1072 if (priv->ui_manager != NULL)
1073 g_object_unref (priv->ui_manager);
1074 priv->ui_manager = NULL;
1076 if (priv->contact != NULL)
1078 g_signal_handlers_disconnect_by_func (priv->contact,
1079 contact_name_changed_cb, self);
1080 g_object_unref (priv->contact);
1081 priv->contact = NULL;
1084 /* release any references held by the object here */
1085 if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1086 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1090 empathy_call_window_finalize (GObject *object)
1092 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1093 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1095 if (priv->video_output_motion_handler_id != 0)
1097 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1098 priv->video_output_motion_handler_id);
1099 priv->video_output_motion_handler_id = 0;
1102 /* free any data held directly by the object here */
1103 g_mutex_free (priv->lock);
1105 g_timer_destroy (priv->timer);
1107 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1112 empathy_call_window_new (EmpathyCallHandler *handler)
1114 return EMPATHY_CALL_WINDOW (
1115 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1119 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1120 GstElement *conference, gpointer user_data)
1122 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1123 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1125 gst_bin_add (GST_BIN (priv->pipeline), conference);
1127 gst_element_set_state (conference, GST_STATE_PLAYING);
1131 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1132 FsMediaType type, FsStreamDirection direction, gpointer user_data)
1134 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1135 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1137 if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1140 if (direction == FS_DIRECTION_RECV)
1143 /* video and direction is send */
1144 return priv->video_input != NULL;
1148 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1150 GstStateChangeReturn state_change_return;
1151 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1153 if (priv->pipeline == NULL)
1156 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1158 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1159 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1161 if (priv->pipeline != NULL)
1162 g_object_unref (priv->pipeline);
1163 priv->pipeline = NULL;
1165 if (priv->video_input != NULL)
1166 g_object_unref (priv->video_input);
1167 priv->video_input = NULL;
1169 if (priv->audio_input != NULL)
1170 g_object_unref (priv->audio_input);
1171 priv->audio_input = NULL;
1173 if (priv->audio_output != NULL)
1174 g_object_unref (priv->audio_output);
1175 priv->audio_output = NULL;
1177 if (priv->video_tee != NULL)
1178 g_object_unref (priv->video_tee);
1179 priv->video_tee = NULL;
1181 if (priv->video_preview != NULL)
1182 gtk_widget_destroy (priv->video_preview);
1183 priv->video_preview = NULL;
1185 priv->liveadder = NULL;
1186 priv->funnel = NULL;
1192 g_message ("Error: could not destroy pipeline. Closing call window");
1193 gtk_widget_destroy (GTK_WIDGET (self));
1200 empathy_call_window_disconnected (EmpathyCallWindow *self)
1202 gboolean could_disconnect = FALSE;
1203 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1204 gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1206 if (priv->call_state == CONNECTING)
1207 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1209 if (priv->call_state != REDIALING)
1210 priv->call_state = DISCONNECTED;
1212 if (could_reset_pipeline)
1214 gboolean initial_video = empathy_call_handler_has_initial_video (
1216 g_mutex_lock (priv->lock);
1218 g_timer_stop (priv->timer);
1220 if (priv->timer_id != 0)
1221 g_source_remove (priv->timer_id);
1224 g_mutex_unlock (priv->lock);
1226 empathy_call_window_status_message (self, _("Disconnected"));
1228 gtk_action_set_sensitive (priv->redial, TRUE);
1229 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1231 /* Reseting the send_video, camera_buton and mic_button to their
1233 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1234 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1235 gtk_action_set_sensitive (priv->send_video, FALSE);
1236 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1238 gtk_toggle_tool_button_set_active (
1239 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), initial_video);
1240 gtk_toggle_tool_button_set_active (
1241 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1243 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1245 gtk_action_set_sensitive (priv->show_preview, FALSE);
1247 gtk_widget_hide (priv->video_output);
1248 gtk_widget_show (priv->remote_user_avatar_widget);
1250 priv->sending_video = FALSE;
1251 priv->call_started = FALSE;
1253 could_disconnect = TRUE;
1256 return could_disconnect;
1261 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
1263 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1264 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1266 if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1267 empathy_call_window_restart_call (self);
1270 /* Called with global lock held */
1272 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1274 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1277 if (priv->funnel == NULL)
1281 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1282 (priv->video_output));
1284 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1286 gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1287 gst_bin_add (GST_BIN (priv->pipeline), output);
1289 gst_element_link (priv->funnel, output);
1291 gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1292 gst_element_set_state (output, GST_STATE_PLAYING);
1295 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1300 /* Called with global lock held */
1302 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1304 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1307 if (priv->liveadder == NULL)
1309 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1311 gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1312 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1314 gst_element_link (priv->liveadder, priv->audio_output);
1316 gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1317 gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1320 pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1326 empathy_call_window_update_timer (gpointer user_data)
1328 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1329 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1333 time = g_timer_elapsed (priv->timer, NULL);
1335 /* Translators: number of minutes:seconds the caller has been connected */
1336 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time / 60,
1338 empathy_call_window_status_message (self, str);
1345 empathy_call_window_connected (gpointer user_data)
1347 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1348 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1349 EmpathyTpCall *call;
1351 g_object_get (priv->handler, "tp-call", &call, NULL);
1353 g_signal_connect (call, "notify::video-stream",
1354 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1356 if (empathy_tp_call_has_dtmf (call))
1357 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1359 if (priv->video_input == NULL)
1360 empathy_call_window_set_send_video (self, FALSE);
1362 priv->sending_video = empathy_tp_call_is_sending_video (call);
1364 gtk_action_set_sensitive (priv->show_preview, TRUE);
1365 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1367 || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
1368 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1369 priv->sending_video && priv->video_input != NULL);
1370 gtk_toggle_tool_button_set_active (
1371 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1372 priv->sending_video && priv->video_input != NULL);
1373 gtk_widget_set_sensitive (priv->camera_button, priv->video_input != NULL);
1374 gtk_action_set_sensitive (priv->send_video, priv->video_input != NULL);
1376 gtk_action_set_sensitive (priv->redial, FALSE);
1377 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1379 gtk_widget_set_sensitive (priv->mic_button, TRUE);
1381 empathy_call_window_update_avatars_visibility (call, self);
1383 g_object_unref (call);
1385 g_mutex_lock (priv->lock);
1387 priv->timer_id = g_timeout_add_seconds (1,
1388 empathy_call_window_update_timer, self);
1390 g_mutex_unlock (priv->lock);
1392 empathy_call_window_update_timer (self);
1398 /* Called from the streaming thread */
1400 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1401 GstPad *src, guint media_type, gpointer user_data)
1403 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1404 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1408 g_mutex_lock (priv->lock);
1410 if (priv->call_state != CONNECTED)
1412 g_timer_start (priv->timer);
1413 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
1414 priv->call_state = CONNECTED;
1415 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1420 case TP_MEDIA_STREAM_TYPE_AUDIO:
1421 pad = empathy_call_window_get_audio_sink_pad (self);
1423 case TP_MEDIA_STREAM_TYPE_VIDEO:
1424 gtk_widget_hide (priv->remote_user_avatar_widget);
1425 gtk_widget_show (priv->video_output);
1426 pad = empathy_call_window_get_video_sink_pad (self);
1429 g_assert_not_reached ();
1432 gst_pad_link (src, pad);
1433 gst_object_unref (pad);
1435 g_mutex_unlock (priv->lock);
1438 /* Called from the streaming thread */
1440 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1441 GstPad *sink, guint media_type, gpointer user_data)
1443 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1444 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1449 case TP_MEDIA_STREAM_TYPE_AUDIO:
1450 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1452 pad = gst_element_get_static_pad (priv->audio_input, "src");
1453 gst_pad_link (pad, sink);
1455 gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1457 case TP_MEDIA_STREAM_TYPE_VIDEO:
1458 if (priv->video_input != NULL)
1460 EmpathyTpCall *call;
1461 g_object_get (priv->handler, "tp-call", &call, NULL);
1463 if (empathy_tp_call_is_sending_video (call))
1465 empathy_call_window_setup_video_preview (self);
1467 gtk_toggle_action_set_active (
1468 GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1470 if (priv->video_preview != NULL)
1471 gtk_widget_show (priv->video_preview);
1472 gtk_widget_hide (priv->self_user_avatar_widget);
1475 g_object_unref (call);
1477 if (priv->video_tee != NULL)
1479 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1480 gst_pad_link (pad, sink);
1485 g_assert_not_reached ();
1491 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1493 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1494 GstElement *preview;
1496 preview = empathy_video_widget_get_element (
1497 EMPATHY_VIDEO_WIDGET (priv->video_preview));
1499 gst_element_set_state (priv->video_input, GST_STATE_NULL);
1500 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1501 gst_element_set_state (preview, GST_STATE_NULL);
1503 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1504 priv->video_tee, preview, NULL);
1506 g_object_unref (priv->video_input);
1507 priv->video_input = NULL;
1508 g_object_unref (priv->video_tee);
1509 priv->video_tee = NULL;
1510 gtk_widget_destroy (priv->video_preview);
1511 priv->video_preview = NULL;
1513 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1514 gtk_toggle_tool_button_set_active (
1515 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1516 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1517 gtk_action_set_sensitive (priv->send_video, FALSE);
1519 gtk_widget_show (priv->self_user_avatar_widget);
1524 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1527 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1528 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1531 empathy_call_handler_bus_message (priv->handler, bus, message);
1533 switch (GST_MESSAGE_TYPE (message))
1535 case GST_MESSAGE_STATE_CHANGED:
1536 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1538 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1539 if (newstate == GST_STATE_PAUSED)
1540 empathy_call_window_setup_video_input (self);
1542 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1543 !priv->call_started)
1545 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1546 if (newstate == GST_STATE_PAUSED)
1548 priv->call_started = TRUE;
1549 empathy_call_handler_start_call (priv->handler);
1550 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1554 case GST_MESSAGE_ERROR:
1556 GError *error = NULL;
1557 GstElement *gst_error;
1560 gst_message_parse_error (message, &error, &debug);
1561 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1563 g_message ("Element error: %s -- %s\n", error->message, debug);
1565 if (g_str_has_prefix (gst_element_get_name (gst_error),
1566 VIDEO_INPUT_ERROR_PREFIX))
1568 /* Remove the video input and continue */
1569 if (priv->video_input != NULL)
1570 empathy_call_window_remove_video_input (self);
1571 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1575 empathy_call_window_disconnected (self);
1577 g_error_free (error);
1588 empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
1590 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1592 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1594 if (priv->video_preview != NULL)
1596 gtk_widget_hide (priv->self_user_avatar_widget);
1597 gtk_widget_show (priv->video_preview);
1601 if (priv->video_preview != NULL)
1602 gtk_widget_hide (priv->video_preview);
1604 gtk_widget_show (priv->self_user_avatar_widget);
1610 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1611 EmpathyCallWindow *window)
1613 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1615 if (empathy_tp_call_is_receiving_video (call))
1617 gtk_widget_hide (priv->remote_user_avatar_widget);
1618 gtk_widget_show (priv->video_output);
1622 gtk_widget_hide (priv->video_output);
1623 gtk_widget_show (priv->remote_user_avatar_widget);
1626 empathy_call_window_update_self_avatar_visibility (window);
1630 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1632 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1634 g_signal_connect (priv->handler, "conference-added",
1635 G_CALLBACK (empathy_call_window_conference_added_cb), window);
1636 g_signal_connect (priv->handler, "request-resource",
1637 G_CALLBACK (empathy_call_window_request_resource_cb), window);
1638 g_signal_connect (priv->handler, "closed",
1639 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1640 g_signal_connect (priv->handler, "src-pad-added",
1641 G_CALLBACK (empathy_call_window_src_added_cb), window);
1642 g_signal_connect (priv->handler, "sink-pad-added",
1643 G_CALLBACK (empathy_call_window_sink_added_cb), window);
1645 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1649 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1650 EmpathyCallWindow *window)
1652 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1654 if (priv->pipeline != NULL)
1655 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1657 if (priv->call_state == CONNECTING)
1658 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1664 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1667 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1669 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1674 gtk_widget_hide (priv->sidebar);
1675 gtk_widget_hide (menu);
1676 gtk_widget_hide (priv->vbox);
1677 gtk_widget_hide (priv->statusbar);
1678 gtk_widget_hide (priv->toolbar);
1682 if (priv->sidebar_was_visible_before_fs)
1683 gtk_widget_show (priv->sidebar);
1685 gtk_widget_show (menu);
1686 gtk_widget_show (priv->vbox);
1687 gtk_widget_show (priv->statusbar);
1688 gtk_widget_show (priv->toolbar);
1690 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1691 priv->original_height_before_fs);
1696 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1698 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1700 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1701 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1702 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1703 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1704 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1705 priv->video_output, TRUE, TRUE,
1706 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1708 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1709 priv->vbox, TRUE, TRUE,
1710 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1715 empathy_call_window_state_event_cb (GtkWidget *widget,
1716 GdkEventWindowState *event, EmpathyCallWindow *window)
1718 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1720 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1721 gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1725 gboolean sidebar_was_visible;
1726 gint original_width = GTK_WIDGET (window)->allocation.width;
1727 gint original_height = GTK_WIDGET (window)->allocation.height;
1729 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1731 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1732 priv->original_width_before_fs = original_width;
1733 priv->original_height_before_fs = original_height;
1735 if (priv->video_output_motion_handler_id == 0 &&
1736 priv->video_output != NULL)
1738 priv->video_output_motion_handler_id = g_signal_connect (
1739 G_OBJECT (priv->video_output), "motion-notify-event",
1740 G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1745 if (priv->video_output_motion_handler_id != 0)
1747 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1748 priv->video_output_motion_handler_id);
1749 priv->video_output_motion_handler_id = 0;
1753 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1755 show_controls (window, set_fullscreen);
1756 show_borders (window, set_fullscreen);
1757 gtk_action_set_stock_id (priv->menu_fullscreen,
1758 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1759 priv->is_fullscreen = set_fullscreen;
1766 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1767 EmpathyCallWindow *window)
1769 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1771 int w,h, handle_size;
1773 w = GTK_WIDGET (window)->allocation.width;
1774 h = GTK_WIDGET (window)->allocation.height;
1776 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1778 if (gtk_toggle_button_get_active (toggle))
1780 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1781 gtk_widget_show (priv->sidebar);
1782 w += priv->sidebar->allocation.width + handle_size;
1786 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1787 w -= priv->sidebar->allocation.width + handle_size;
1788 gtk_widget_hide (priv->sidebar);
1791 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1794 gtk_window_resize (GTK_WINDOW (window), w, h);
1798 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1801 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1802 EmpathyTpCall *call;
1804 priv->sending_video = send;
1806 /* When we start sending video, we want to show the video preview by
1810 empathy_call_window_setup_video_preview (window);
1811 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1815 g_object_get (priv->handler, "tp-call", &call, NULL);
1816 empathy_tp_call_request_video_stream_direction (call, send);
1817 g_object_unref (call);
1821 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1822 EmpathyCallWindow *window)
1824 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1827 if (priv->call_state != CONNECTED)
1830 active = (gtk_toggle_tool_button_get_active (toggle));
1832 if (priv->sending_video == active)
1835 empathy_call_window_set_send_video (window, active);
1836 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1840 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1841 EmpathyCallWindow *window)
1843 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1846 if (priv->call_state != CONNECTED)
1849 active = (gtk_toggle_action_get_active (toggle));
1851 if (priv->sending_video == active)
1854 empathy_call_window_set_send_video (window, active);
1855 gtk_toggle_tool_button_set_active (
1856 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1860 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1861 EmpathyCallWindow *window)
1863 gboolean show_preview_toggled;
1864 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1866 show_preview_toggled = gtk_toggle_action_get_active (toggle);
1868 if (show_preview_toggled)
1870 empathy_call_window_setup_video_preview (window);
1871 gtk_widget_show (priv->self_user_output_frame);
1872 empathy_call_window_update_self_avatar_visibility (window);
1876 gtk_widget_hide (priv->self_user_output_frame);
1881 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1882 EmpathyCallWindow *window)
1884 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1887 if (priv->audio_input == NULL)
1890 active = (gtk_toggle_tool_button_get_active (toggle));
1894 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1896 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1900 /* TODO, Instead of setting the input volume to 0 we should probably
1901 * stop sending but this would cause the audio call to drop if both
1902 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1903 * in the future. GNOME #574574
1905 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1907 gtk_adjustment_set_value (priv->audio_input_adj, 0);
1912 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1913 EmpathyCallWindow *window)
1915 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1917 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1922 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1923 EmpathyCallWindow *window)
1925 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1927 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1932 empathy_call_window_hangup_cb (gpointer object,
1933 EmpathyCallWindow *window)
1935 if (empathy_call_window_disconnected (window))
1936 gtk_widget_destroy (GTK_WIDGET (window));
1940 empathy_call_window_restart_call (EmpathyCallWindow *window)
1943 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1945 gtk_widget_destroy (priv->remote_user_output_hbox);
1946 gtk_widget_destroy (priv->self_user_output_hbox);
1948 priv->pipeline = gst_pipeline_new (NULL);
1949 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
1950 gst_bus_add_watch (bus, empathy_call_window_bus_message, window);
1952 empathy_call_window_setup_remote_frame (bus, window);
1953 empathy_call_window_setup_self_frame (bus, window);
1955 g_object_unref (bus);
1957 gtk_widget_show_all (priv->content_hbox);
1959 if (!empathy_call_handler_has_initial_video (priv->handler))
1960 gtk_widget_hide (priv->self_user_output_frame);
1962 priv->outgoing = TRUE;
1963 empathy_call_window_set_state_connecting (window);
1965 priv->call_started = TRUE;
1966 empathy_call_handler_start_call (priv->handler);
1967 empathy_call_window_setup_avatars (window, priv->handler);
1968 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1970 gtk_action_set_sensitive (priv->redial, FALSE);
1971 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1975 empathy_call_window_redial_cb (gpointer object,
1976 EmpathyCallWindow *window)
1978 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1980 if (priv->call_state == CONNECTED)
1981 priv->call_state = REDIALING;
1983 empathy_call_handler_stop_call (priv->handler);
1985 if (priv->call_state != CONNECTED)
1986 empathy_call_window_restart_call (window);
1990 empathy_call_window_fullscreen_cb (gpointer object,
1991 EmpathyCallWindow *window)
1993 empathy_call_window_fullscreen_toggle (window);
1997 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
1999 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2001 if (priv->is_fullscreen)
2002 gtk_window_unfullscreen (GTK_WINDOW (window));
2004 gtk_window_fullscreen (GTK_WINDOW (window));
2008 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2009 GdkEventButton *event, EmpathyCallWindow *window)
2011 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2013 empathy_call_window_video_menu_popup (window, event->button);
2021 empathy_call_window_key_press_cb (GtkWidget *video_output,
2022 GdkEventKey *event, EmpathyCallWindow *window)
2024 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2026 if (priv->is_fullscreen && event->keyval == GDK_Escape)
2028 /* Since we are in fullscreen mode, toggling will bring us back to
2030 empathy_call_window_fullscreen_toggle (window);
2038 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2039 GdkEventMotion *event, EmpathyCallWindow *window)
2041 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2043 if (priv->is_fullscreen)
2045 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2052 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2056 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2058 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2060 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2061 button, gtk_get_current_event_time ());
2062 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2066 empathy_call_window_status_message (EmpathyCallWindow *window,
2069 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2071 if (priv->context_id == 0)
2073 priv->context_id = gtk_statusbar_get_context_id (
2074 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2078 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2081 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2086 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2087 gdouble value, EmpathyCallWindow *window)
2089 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2091 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),