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,
812 GdkPixbuf *pixbuf_avatar = NULL;
816 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
820 if (pixbuf_avatar == NULL)
822 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
826 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
830 set_window_title (EmpathyCallWindow *self)
832 EmpathyCallWindowPriv *priv = GET_PRIV (self);
835 tmp = g_strdup_printf (_("Call with %s"),
836 empathy_contact_get_name (priv->contact));
837 gtk_window_set_title (GTK_WINDOW (self), tmp);
842 contact_name_changed_cb (EmpathyContact *contact,
843 GParamSpec *pspec, EmpathyCallWindow *self)
845 set_window_title (self);
849 contact_avatar_changed_cb (EmpathyContact *contact,
850 GParamSpec *pspec, GtkWidget *avatar_widget)
852 init_contact_avatar_with_size (contact, avatar_widget,
853 avatar_widget->allocation.height);
857 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
858 EmpathyContact *contact, const GError *error, gpointer user_data,
859 GObject *weak_object)
861 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
862 EmpathyCallWindowPriv *priv = GET_PRIV (self);
864 init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
865 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
867 g_signal_connect (contact, "notify::avatar",
868 G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
872 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
873 EmpathyCallHandler *handler)
875 EmpathyCallWindowPriv *priv = GET_PRIV (self);
877 g_object_get (handler, "contact", &(priv->contact), NULL);
879 if (priv->contact != NULL)
881 TpConnection *connection;
882 EmpathyTpContactFactory *factory;
884 set_window_title (self);
886 g_signal_connect (priv->contact, "notify::name",
887 G_CALLBACK (contact_name_changed_cb), self);
888 g_signal_connect (priv->contact, "notify::avatar",
889 G_CALLBACK (contact_avatar_changed_cb),
890 priv->remote_user_avatar_widget);
892 /* Retreiving the self avatar */
893 connection = empathy_contact_get_connection (priv->contact);
894 factory = empathy_tp_contact_factory_dup_singleton (connection);
895 empathy_tp_contact_factory_get_from_handle (factory,
896 tp_connection_get_self_handle (connection),
897 empathy_call_window_got_self_contact_cb, self, NULL, NULL);
899 g_object_unref (factory);
903 g_warning ("call handler doesn't have a contact");
904 gtk_window_set_title (GTK_WINDOW (self), _("Call"));
906 /* Since we can't access the remote contact, we can't get a connection
907 to it and can't get the self contact (and its avatar). This means
908 that we have to manually set the self avatar. */
909 init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
910 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
913 init_contact_avatar_with_size (priv->contact,
914 priv->remote_user_avatar_widget, MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
915 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
917 /* The remote avatar is shown by default and will be hidden when we receive
918 video from the remote side. */
919 gtk_widget_hide (priv->video_output);
920 gtk_widget_show (priv->remote_user_avatar_widget);
924 empathy_call_window_setup_video_preview_visibility (EmpathyCallWindow *self,
925 EmpathyCallHandler *handler)
927 EmpathyCallWindowPriv *priv = GET_PRIV (self);
928 gboolean initial_video = empathy_call_handler_has_initial_video (priv->handler);
930 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
935 empathy_call_window_constructed (GObject *object)
937 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
938 EmpathyCallWindowPriv *priv = GET_PRIV (self);
941 g_assert (priv->handler != NULL);
943 g_object_get (priv->handler, "tp-call", &call, NULL);
944 priv->outgoing = (call == NULL);
946 g_object_unref (call);
948 empathy_call_window_setup_avatars (self, priv->handler);
949 empathy_call_window_setup_video_preview_visibility (self, priv->handler);
950 empathy_call_window_set_state_connecting (self);
953 static void empathy_call_window_dispose (GObject *object);
954 static void empathy_call_window_finalize (GObject *object);
957 empathy_call_window_set_property (GObject *object,
958 guint property_id, const GValue *value, GParamSpec *pspec)
960 EmpathyCallWindowPriv *priv = GET_PRIV (object);
964 case PROP_CALL_HANDLER:
965 priv->handler = g_value_dup_object (value);
968 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
973 empathy_call_window_get_property (GObject *object,
974 guint property_id, GValue *value, GParamSpec *pspec)
976 EmpathyCallWindowPriv *priv = GET_PRIV (object);
980 case PROP_CALL_HANDLER:
981 g_value_set_object (value, priv->handler);
984 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
989 empathy_call_window_class_init (
990 EmpathyCallWindowClass *empathy_call_window_class)
992 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
993 GParamSpec *param_spec;
995 g_type_class_add_private (empathy_call_window_class,
996 sizeof (EmpathyCallWindowPriv));
998 object_class->constructed = empathy_call_window_constructed;
999 object_class->set_property = empathy_call_window_set_property;
1000 object_class->get_property = empathy_call_window_get_property;
1002 object_class->dispose = empathy_call_window_dispose;
1003 object_class->finalize = empathy_call_window_finalize;
1005 param_spec = g_param_spec_object ("handler",
1006 "handler", "The call handler",
1007 EMPATHY_TYPE_CALL_HANDLER,
1008 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1009 g_object_class_install_property (object_class,
1010 PROP_CALL_HANDLER, param_spec);
1014 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1015 GParamSpec *property, EmpathyCallWindow *self)
1017 empathy_call_window_update_avatars_visibility (call, self);
1021 empathy_call_window_dispose (GObject *object)
1023 EmpathyTpCall *call;
1024 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1025 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1027 if (priv->dispose_has_run)
1030 priv->dispose_has_run = TRUE;
1032 g_object_get (priv->handler, "tp-call", &call, NULL);
1036 g_signal_handlers_disconnect_by_func (call,
1037 empathy_call_window_video_stream_changed_cb, object);
1040 g_object_unref (call);
1042 if (priv->handler != NULL)
1043 g_object_unref (priv->handler);
1045 priv->handler = NULL;
1047 if (priv->pipeline != NULL)
1048 g_object_unref (priv->pipeline);
1049 priv->pipeline = NULL;
1051 if (priv->video_input != NULL)
1052 g_object_unref (priv->video_input);
1053 priv->video_input = NULL;
1055 if (priv->audio_input != NULL)
1056 g_object_unref (priv->audio_input);
1057 priv->audio_input = NULL;
1059 if (priv->audio_output != NULL)
1060 g_object_unref (priv->audio_output);
1061 priv->audio_output = NULL;
1063 if (priv->video_tee != NULL)
1064 g_object_unref (priv->video_tee);
1065 priv->video_tee = NULL;
1067 if (priv->timer_id != 0)
1068 g_source_remove (priv->timer_id);
1071 if (priv->ui_manager != NULL)
1072 g_object_unref (priv->ui_manager);
1073 priv->ui_manager = NULL;
1075 if (priv->contact != NULL)
1077 g_signal_handlers_disconnect_by_func (priv->contact,
1078 contact_name_changed_cb, self);
1079 g_object_unref (priv->contact);
1080 priv->contact = NULL;
1083 /* release any references held by the object here */
1084 if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1085 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1089 empathy_call_window_finalize (GObject *object)
1091 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1092 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1094 if (priv->video_output_motion_handler_id != 0)
1096 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1097 priv->video_output_motion_handler_id);
1098 priv->video_output_motion_handler_id = 0;
1101 /* free any data held directly by the object here */
1102 g_mutex_free (priv->lock);
1104 g_timer_destroy (priv->timer);
1106 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1111 empathy_call_window_new (EmpathyCallHandler *handler)
1113 return EMPATHY_CALL_WINDOW (
1114 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1118 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1119 GstElement *conference, gpointer user_data)
1121 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1122 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1124 gst_bin_add (GST_BIN (priv->pipeline), conference);
1126 gst_element_set_state (conference, GST_STATE_PLAYING);
1130 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1131 FsMediaType type, FsStreamDirection direction, gpointer user_data)
1133 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1134 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1136 if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1139 if (direction == FS_DIRECTION_RECV)
1142 /* video and direction is send */
1143 return priv->video_input != NULL;
1147 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1149 GstStateChangeReturn state_change_return;
1150 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1152 if (priv->pipeline == NULL)
1155 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1157 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1158 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1160 if (priv->pipeline != NULL)
1161 g_object_unref (priv->pipeline);
1162 priv->pipeline = NULL;
1164 if (priv->video_input != NULL)
1165 g_object_unref (priv->video_input);
1166 priv->video_input = NULL;
1168 if (priv->audio_input != NULL)
1169 g_object_unref (priv->audio_input);
1170 priv->audio_input = NULL;
1172 if (priv->audio_output != NULL)
1173 g_object_unref (priv->audio_output);
1174 priv->audio_output = NULL;
1176 if (priv->video_tee != NULL)
1177 g_object_unref (priv->video_tee);
1178 priv->video_tee = NULL;
1180 if (priv->video_preview != NULL)
1181 gtk_widget_destroy (priv->video_preview);
1182 priv->video_preview = NULL;
1184 priv->liveadder = NULL;
1185 priv->funnel = NULL;
1191 g_message ("Error: could not destroy pipeline. Closing call window");
1192 gtk_widget_destroy (GTK_WIDGET (self));
1199 empathy_call_window_disconnected (EmpathyCallWindow *self)
1201 gboolean could_disconnect = FALSE;
1202 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1203 gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1205 if (priv->call_state == CONNECTING)
1206 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1208 if (priv->call_state != REDIALING)
1209 priv->call_state = DISCONNECTED;
1211 if (could_reset_pipeline)
1213 gboolean initial_video = empathy_call_handler_has_initial_video (
1215 g_mutex_lock (priv->lock);
1217 g_timer_stop (priv->timer);
1219 if (priv->timer_id != 0)
1220 g_source_remove (priv->timer_id);
1223 g_mutex_unlock (priv->lock);
1225 empathy_call_window_status_message (self, _("Disconnected"));
1227 gtk_action_set_sensitive (priv->redial, TRUE);
1228 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1230 /* Reseting the send_video, camera_buton and mic_button to their
1232 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1233 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1234 gtk_action_set_sensitive (priv->send_video, FALSE);
1235 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1237 gtk_toggle_tool_button_set_active (
1238 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), initial_video);
1239 gtk_toggle_tool_button_set_active (
1240 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1242 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1244 gtk_action_set_sensitive (priv->show_preview, FALSE);
1246 gtk_widget_hide (priv->video_output);
1247 gtk_widget_show (priv->remote_user_avatar_widget);
1249 priv->sending_video = FALSE;
1250 priv->call_started = FALSE;
1252 could_disconnect = TRUE;
1255 return could_disconnect;
1260 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
1262 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1263 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1265 if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1266 empathy_call_window_restart_call (self);
1269 /* Called with global lock held */
1271 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1273 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1276 if (priv->funnel == NULL)
1280 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1281 (priv->video_output));
1283 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1285 gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1286 gst_bin_add (GST_BIN (priv->pipeline), output);
1288 gst_element_link (priv->funnel, output);
1290 gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1291 gst_element_set_state (output, GST_STATE_PLAYING);
1294 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1299 /* Called with global lock held */
1301 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1303 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1306 if (priv->liveadder == NULL)
1308 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1310 gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1311 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1313 gst_element_link (priv->liveadder, priv->audio_output);
1315 gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1316 gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1319 pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1325 empathy_call_window_update_timer (gpointer user_data)
1327 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1328 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1332 time = g_timer_elapsed (priv->timer, NULL);
1334 /* Translators: number of minutes:seconds the caller has been connected */
1335 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time / 60,
1337 empathy_call_window_status_message (self, str);
1344 empathy_call_window_connected (gpointer user_data)
1346 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1347 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1348 EmpathyTpCall *call;
1349 gboolean can_send_video = priv->video_input != NULL && priv->contact != NULL
1350 && empathy_contact_can_voip_video (priv->contact);
1352 g_object_get (priv->handler, "tp-call", &call, NULL);
1354 g_signal_connect (call, "notify::video-stream",
1355 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1357 if (empathy_tp_call_has_dtmf (call))
1358 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1360 if (priv->video_input == NULL)
1361 empathy_call_window_set_send_video (self, FALSE);
1363 priv->sending_video = can_send_video ?
1364 empathy_tp_call_is_sending_video (call) : FALSE;
1366 gtk_action_set_sensitive (priv->show_preview, TRUE);
1367 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1369 || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
1370 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1371 priv->sending_video && priv->video_input != NULL);
1372 gtk_toggle_tool_button_set_active (
1373 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1374 priv->sending_video && priv->video_input != NULL);
1375 gtk_widget_set_sensitive (priv->camera_button, can_send_video);
1376 gtk_action_set_sensitive (priv->send_video, can_send_video);
1378 gtk_action_set_sensitive (priv->redial, FALSE);
1379 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1381 gtk_widget_set_sensitive (priv->mic_button, TRUE);
1383 empathy_call_window_update_avatars_visibility (call, self);
1385 g_object_unref (call);
1387 g_mutex_lock (priv->lock);
1389 priv->timer_id = g_timeout_add_seconds (1,
1390 empathy_call_window_update_timer, self);
1392 g_mutex_unlock (priv->lock);
1394 empathy_call_window_update_timer (self);
1400 /* Called from the streaming thread */
1402 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1403 GstPad *src, guint media_type, gpointer user_data)
1405 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1406 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1410 g_mutex_lock (priv->lock);
1412 if (priv->call_state != CONNECTED)
1414 g_timer_start (priv->timer);
1415 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
1416 priv->call_state = CONNECTED;
1417 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1422 case TP_MEDIA_STREAM_TYPE_AUDIO:
1423 pad = empathy_call_window_get_audio_sink_pad (self);
1425 case TP_MEDIA_STREAM_TYPE_VIDEO:
1426 gtk_widget_hide (priv->remote_user_avatar_widget);
1427 gtk_widget_show (priv->video_output);
1428 pad = empathy_call_window_get_video_sink_pad (self);
1431 g_assert_not_reached ();
1434 gst_pad_link (src, pad);
1435 gst_object_unref (pad);
1437 g_mutex_unlock (priv->lock);
1440 /* Called from the streaming thread */
1442 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1443 GstPad *sink, guint media_type, gpointer user_data)
1445 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1446 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1451 case TP_MEDIA_STREAM_TYPE_AUDIO:
1452 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1454 pad = gst_element_get_static_pad (priv->audio_input, "src");
1455 gst_pad_link (pad, sink);
1457 gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1459 case TP_MEDIA_STREAM_TYPE_VIDEO:
1460 if (priv->video_input != NULL)
1462 EmpathyTpCall *call;
1463 g_object_get (priv->handler, "tp-call", &call, NULL);
1465 if (empathy_tp_call_is_sending_video (call))
1467 empathy_call_window_setup_video_preview (self);
1469 gtk_toggle_action_set_active (
1470 GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1472 if (priv->video_preview != NULL)
1473 gtk_widget_show (priv->video_preview);
1474 gtk_widget_hide (priv->self_user_avatar_widget);
1477 g_object_unref (call);
1479 if (priv->video_tee != NULL)
1481 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1482 gst_pad_link (pad, sink);
1487 g_assert_not_reached ();
1493 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1495 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1496 GstElement *preview;
1498 preview = empathy_video_widget_get_element (
1499 EMPATHY_VIDEO_WIDGET (priv->video_preview));
1501 gst_element_set_state (priv->video_input, GST_STATE_NULL);
1502 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1503 gst_element_set_state (preview, GST_STATE_NULL);
1505 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1506 priv->video_tee, preview, NULL);
1508 g_object_unref (priv->video_input);
1509 priv->video_input = NULL;
1510 g_object_unref (priv->video_tee);
1511 priv->video_tee = NULL;
1512 gtk_widget_destroy (priv->video_preview);
1513 priv->video_preview = NULL;
1515 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1516 gtk_toggle_tool_button_set_active (
1517 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1518 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1519 gtk_action_set_sensitive (priv->send_video, FALSE);
1521 gtk_widget_show (priv->self_user_avatar_widget);
1526 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1529 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1530 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1533 empathy_call_handler_bus_message (priv->handler, bus, message);
1535 switch (GST_MESSAGE_TYPE (message))
1537 case GST_MESSAGE_STATE_CHANGED:
1538 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1540 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1541 if (newstate == GST_STATE_PAUSED)
1542 empathy_call_window_setup_video_input (self);
1544 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1545 !priv->call_started)
1547 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1548 if (newstate == GST_STATE_PAUSED)
1550 priv->call_started = TRUE;
1551 empathy_call_handler_start_call (priv->handler);
1552 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1556 case GST_MESSAGE_ERROR:
1558 GError *error = NULL;
1559 GstElement *gst_error;
1562 gst_message_parse_error (message, &error, &debug);
1563 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1565 g_message ("Element error: %s -- %s\n", error->message, debug);
1567 if (g_str_has_prefix (gst_element_get_name (gst_error),
1568 VIDEO_INPUT_ERROR_PREFIX))
1570 /* Remove the video input and continue */
1571 if (priv->video_input != NULL)
1572 empathy_call_window_remove_video_input (self);
1573 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1577 empathy_call_window_disconnected (self);
1579 g_error_free (error);
1590 empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
1592 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1594 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1596 if (priv->video_preview != NULL)
1598 gtk_widget_hide (priv->self_user_avatar_widget);
1599 gtk_widget_show (priv->video_preview);
1603 if (priv->video_preview != NULL)
1604 gtk_widget_hide (priv->video_preview);
1606 gtk_widget_show (priv->self_user_avatar_widget);
1612 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1613 EmpathyCallWindow *window)
1615 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1617 if (empathy_tp_call_is_receiving_video (call))
1619 gtk_widget_hide (priv->remote_user_avatar_widget);
1620 gtk_widget_show (priv->video_output);
1624 gtk_widget_hide (priv->video_output);
1625 gtk_widget_show (priv->remote_user_avatar_widget);
1628 empathy_call_window_update_self_avatar_visibility (window);
1632 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1634 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1636 g_signal_connect (priv->handler, "conference-added",
1637 G_CALLBACK (empathy_call_window_conference_added_cb), window);
1638 g_signal_connect (priv->handler, "request-resource",
1639 G_CALLBACK (empathy_call_window_request_resource_cb), window);
1640 g_signal_connect (priv->handler, "closed",
1641 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1642 g_signal_connect (priv->handler, "src-pad-added",
1643 G_CALLBACK (empathy_call_window_src_added_cb), window);
1644 g_signal_connect (priv->handler, "sink-pad-added",
1645 G_CALLBACK (empathy_call_window_sink_added_cb), window);
1647 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1651 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1652 EmpathyCallWindow *window)
1654 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1656 if (priv->pipeline != NULL)
1657 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1659 if (priv->call_state == CONNECTING)
1660 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1666 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1669 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1671 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1676 gtk_widget_hide (priv->sidebar);
1677 gtk_widget_hide (menu);
1678 gtk_widget_hide (priv->vbox);
1679 gtk_widget_hide (priv->statusbar);
1680 gtk_widget_hide (priv->toolbar);
1684 if (priv->sidebar_was_visible_before_fs)
1685 gtk_widget_show (priv->sidebar);
1687 gtk_widget_show (menu);
1688 gtk_widget_show (priv->vbox);
1689 gtk_widget_show (priv->statusbar);
1690 gtk_widget_show (priv->toolbar);
1692 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1693 priv->original_height_before_fs);
1698 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1700 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1702 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1703 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1704 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1705 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1706 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1707 priv->video_output, TRUE, TRUE,
1708 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1710 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1711 priv->vbox, TRUE, TRUE,
1712 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1717 empathy_call_window_state_event_cb (GtkWidget *widget,
1718 GdkEventWindowState *event, EmpathyCallWindow *window)
1720 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1722 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1723 gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1727 gboolean sidebar_was_visible;
1728 gint original_width = GTK_WIDGET (window)->allocation.width;
1729 gint original_height = GTK_WIDGET (window)->allocation.height;
1731 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1733 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1734 priv->original_width_before_fs = original_width;
1735 priv->original_height_before_fs = original_height;
1737 if (priv->video_output_motion_handler_id == 0 &&
1738 priv->video_output != NULL)
1740 priv->video_output_motion_handler_id = g_signal_connect (
1741 G_OBJECT (priv->video_output), "motion-notify-event",
1742 G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1747 if (priv->video_output_motion_handler_id != 0)
1749 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1750 priv->video_output_motion_handler_id);
1751 priv->video_output_motion_handler_id = 0;
1755 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1757 show_controls (window, set_fullscreen);
1758 show_borders (window, set_fullscreen);
1759 gtk_action_set_stock_id (priv->menu_fullscreen,
1760 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1761 priv->is_fullscreen = set_fullscreen;
1768 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1769 EmpathyCallWindow *window)
1771 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1773 int w,h, handle_size;
1775 w = GTK_WIDGET (window)->allocation.width;
1776 h = GTK_WIDGET (window)->allocation.height;
1778 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1780 if (gtk_toggle_button_get_active (toggle))
1782 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1783 gtk_widget_show (priv->sidebar);
1784 w += priv->sidebar->allocation.width + handle_size;
1788 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1789 w -= priv->sidebar->allocation.width + handle_size;
1790 gtk_widget_hide (priv->sidebar);
1793 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1796 gtk_window_resize (GTK_WINDOW (window), w, h);
1800 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1803 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1804 EmpathyTpCall *call;
1806 priv->sending_video = send;
1808 /* When we start sending video, we want to show the video preview by
1812 empathy_call_window_setup_video_preview (window);
1813 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1817 g_object_get (priv->handler, "tp-call", &call, NULL);
1818 empathy_tp_call_request_video_stream_direction (call, send);
1819 g_object_unref (call);
1823 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1824 EmpathyCallWindow *window)
1826 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1829 if (priv->call_state != CONNECTED)
1832 active = (gtk_toggle_tool_button_get_active (toggle));
1834 if (priv->sending_video == active)
1837 empathy_call_window_set_send_video (window, active);
1838 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1842 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1843 EmpathyCallWindow *window)
1845 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1848 if (priv->call_state != CONNECTED)
1851 active = (gtk_toggle_action_get_active (toggle));
1853 if (priv->sending_video == active)
1856 empathy_call_window_set_send_video (window, active);
1857 gtk_toggle_tool_button_set_active (
1858 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1862 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1863 EmpathyCallWindow *window)
1865 gboolean show_preview_toggled;
1866 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1868 show_preview_toggled = gtk_toggle_action_get_active (toggle);
1870 if (show_preview_toggled)
1872 empathy_call_window_setup_video_preview (window);
1873 gtk_widget_show (priv->self_user_output_frame);
1874 empathy_call_window_update_self_avatar_visibility (window);
1878 gtk_widget_hide (priv->self_user_output_frame);
1883 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1884 EmpathyCallWindow *window)
1886 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1889 if (priv->audio_input == NULL)
1892 active = (gtk_toggle_tool_button_get_active (toggle));
1896 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1898 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1902 /* TODO, Instead of setting the input volume to 0 we should probably
1903 * stop sending but this would cause the audio call to drop if both
1904 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1905 * in the future. GNOME #574574
1907 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1909 gtk_adjustment_set_value (priv->audio_input_adj, 0);
1914 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1915 EmpathyCallWindow *window)
1917 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1919 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1924 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1925 EmpathyCallWindow *window)
1927 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1929 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1934 empathy_call_window_hangup_cb (gpointer object,
1935 EmpathyCallWindow *window)
1937 if (empathy_call_window_disconnected (window))
1938 gtk_widget_destroy (GTK_WIDGET (window));
1942 empathy_call_window_restart_call (EmpathyCallWindow *window)
1945 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1947 gtk_widget_destroy (priv->remote_user_output_hbox);
1948 gtk_widget_destroy (priv->self_user_output_hbox);
1950 priv->pipeline = gst_pipeline_new (NULL);
1951 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
1952 gst_bus_add_watch (bus, empathy_call_window_bus_message, window);
1954 empathy_call_window_setup_remote_frame (bus, window);
1955 empathy_call_window_setup_self_frame (bus, window);
1957 g_object_unref (bus);
1959 gtk_widget_show_all (priv->content_hbox);
1961 if (!empathy_call_handler_has_initial_video (priv->handler))
1962 gtk_widget_hide (priv->self_user_output_frame);
1964 priv->outgoing = TRUE;
1965 empathy_call_window_set_state_connecting (window);
1967 priv->call_started = TRUE;
1968 empathy_call_handler_start_call (priv->handler);
1969 empathy_call_window_setup_avatars (window, priv->handler);
1970 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1972 gtk_action_set_sensitive (priv->redial, FALSE);
1973 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1977 empathy_call_window_redial_cb (gpointer object,
1978 EmpathyCallWindow *window)
1980 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1982 if (priv->call_state == CONNECTED)
1983 priv->call_state = REDIALING;
1985 empathy_call_handler_stop_call (priv->handler);
1987 if (priv->call_state != CONNECTED)
1988 empathy_call_window_restart_call (window);
1992 empathy_call_window_fullscreen_cb (gpointer object,
1993 EmpathyCallWindow *window)
1995 empathy_call_window_fullscreen_toggle (window);
1999 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2001 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2003 if (priv->is_fullscreen)
2004 gtk_window_unfullscreen (GTK_WINDOW (window));
2006 gtk_window_fullscreen (GTK_WINDOW (window));
2010 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2011 GdkEventButton *event, EmpathyCallWindow *window)
2013 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2015 empathy_call_window_video_menu_popup (window, event->button);
2023 empathy_call_window_key_press_cb (GtkWidget *video_output,
2024 GdkEventKey *event, EmpathyCallWindow *window)
2026 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2028 if (priv->is_fullscreen && event->keyval == GDK_Escape)
2030 /* Since we are in fullscreen mode, toggling will bring us back to
2032 empathy_call_window_fullscreen_toggle (window);
2040 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2041 GdkEventMotion *event, EmpathyCallWindow *window)
2043 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2045 if (priv->is_fullscreen)
2047 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2054 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2058 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2060 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2062 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2063 button, gtk_get_current_event_time ());
2064 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2068 empathy_call_window_status_message (EmpathyCallWindow *window,
2071 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2073 if (priv->context_id == 0)
2075 priv->context_id = gtk_statusbar_get_context_id (
2076 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2080 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2083 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2088 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2089 gdouble value, EmpathyCallWindow *window)
2091 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2093 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),