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;
1351 can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1352 empathy_contact_can_voip_video (priv->contact);
1354 g_object_get (priv->handler, "tp-call", &call, NULL);
1356 g_signal_connect (call, "notify::video-stream",
1357 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1359 if (empathy_tp_call_has_dtmf (call))
1360 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1362 if (priv->video_input == NULL)
1363 empathy_call_window_set_send_video (self, FALSE);
1365 priv->sending_video = can_send_video ?
1366 empathy_tp_call_is_sending_video (call) : FALSE;
1368 gtk_action_set_sensitive (priv->show_preview, TRUE);
1369 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1371 || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
1372 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1373 priv->sending_video && priv->video_input != NULL);
1374 gtk_toggle_tool_button_set_active (
1375 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1376 priv->sending_video && priv->video_input != NULL);
1377 gtk_widget_set_sensitive (priv->camera_button, can_send_video);
1378 gtk_action_set_sensitive (priv->send_video, can_send_video);
1380 gtk_action_set_sensitive (priv->redial, FALSE);
1381 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1383 gtk_widget_set_sensitive (priv->mic_button, TRUE);
1385 empathy_call_window_update_avatars_visibility (call, self);
1387 g_object_unref (call);
1389 g_mutex_lock (priv->lock);
1391 priv->timer_id = g_timeout_add_seconds (1,
1392 empathy_call_window_update_timer, self);
1394 g_mutex_unlock (priv->lock);
1396 empathy_call_window_update_timer (self);
1402 /* Called from the streaming thread */
1404 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1405 GstPad *src, guint media_type, gpointer user_data)
1407 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1408 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1412 g_mutex_lock (priv->lock);
1414 if (priv->call_state != CONNECTED)
1416 g_timer_start (priv->timer);
1417 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
1418 priv->call_state = CONNECTED;
1419 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1424 case TP_MEDIA_STREAM_TYPE_AUDIO:
1425 pad = empathy_call_window_get_audio_sink_pad (self);
1427 case TP_MEDIA_STREAM_TYPE_VIDEO:
1428 gtk_widget_hide (priv->remote_user_avatar_widget);
1429 gtk_widget_show (priv->video_output);
1430 pad = empathy_call_window_get_video_sink_pad (self);
1433 g_assert_not_reached ();
1436 gst_pad_link (src, pad);
1437 gst_object_unref (pad);
1439 g_mutex_unlock (priv->lock);
1442 /* Called from the streaming thread */
1444 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1445 GstPad *sink, guint media_type, gpointer user_data)
1447 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1448 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1453 case TP_MEDIA_STREAM_TYPE_AUDIO:
1454 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1456 pad = gst_element_get_static_pad (priv->audio_input, "src");
1457 gst_pad_link (pad, sink);
1459 gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1461 case TP_MEDIA_STREAM_TYPE_VIDEO:
1462 if (priv->video_input != NULL)
1464 EmpathyTpCall *call;
1465 g_object_get (priv->handler, "tp-call", &call, NULL);
1467 if (empathy_tp_call_is_sending_video (call))
1469 empathy_call_window_setup_video_preview (self);
1471 gtk_toggle_action_set_active (
1472 GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1474 if (priv->video_preview != NULL)
1475 gtk_widget_show (priv->video_preview);
1476 gtk_widget_hide (priv->self_user_avatar_widget);
1479 g_object_unref (call);
1481 if (priv->video_tee != NULL)
1483 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1484 gst_pad_link (pad, sink);
1489 g_assert_not_reached ();
1495 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1497 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1498 GstElement *preview;
1500 preview = empathy_video_widget_get_element (
1501 EMPATHY_VIDEO_WIDGET (priv->video_preview));
1503 gst_element_set_state (priv->video_input, GST_STATE_NULL);
1504 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1505 gst_element_set_state (preview, GST_STATE_NULL);
1507 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1508 priv->video_tee, preview, NULL);
1510 g_object_unref (priv->video_input);
1511 priv->video_input = NULL;
1512 g_object_unref (priv->video_tee);
1513 priv->video_tee = NULL;
1514 gtk_widget_destroy (priv->video_preview);
1515 priv->video_preview = NULL;
1517 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1518 gtk_toggle_tool_button_set_active (
1519 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1520 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1521 gtk_action_set_sensitive (priv->send_video, FALSE);
1523 gtk_widget_show (priv->self_user_avatar_widget);
1528 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1531 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1532 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1535 empathy_call_handler_bus_message (priv->handler, bus, message);
1537 switch (GST_MESSAGE_TYPE (message))
1539 case GST_MESSAGE_STATE_CHANGED:
1540 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1542 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1543 if (newstate == GST_STATE_PAUSED)
1544 empathy_call_window_setup_video_input (self);
1546 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1547 !priv->call_started)
1549 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1550 if (newstate == GST_STATE_PAUSED)
1552 priv->call_started = TRUE;
1553 empathy_call_handler_start_call (priv->handler);
1554 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1558 case GST_MESSAGE_ERROR:
1560 GError *error = NULL;
1561 GstElement *gst_error;
1564 gst_message_parse_error (message, &error, &debug);
1565 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1567 g_message ("Element error: %s -- %s\n", error->message, debug);
1569 if (g_str_has_prefix (gst_element_get_name (gst_error),
1570 VIDEO_INPUT_ERROR_PREFIX))
1572 /* Remove the video input and continue */
1573 if (priv->video_input != NULL)
1574 empathy_call_window_remove_video_input (self);
1575 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1579 empathy_call_window_disconnected (self);
1581 g_error_free (error);
1592 empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
1594 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1596 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1598 if (priv->video_preview != NULL)
1600 gtk_widget_hide (priv->self_user_avatar_widget);
1601 gtk_widget_show (priv->video_preview);
1605 if (priv->video_preview != NULL)
1606 gtk_widget_hide (priv->video_preview);
1608 gtk_widget_show (priv->self_user_avatar_widget);
1614 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1615 EmpathyCallWindow *window)
1617 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1619 if (empathy_tp_call_is_receiving_video (call))
1621 gtk_widget_hide (priv->remote_user_avatar_widget);
1622 gtk_widget_show (priv->video_output);
1626 gtk_widget_hide (priv->video_output);
1627 gtk_widget_show (priv->remote_user_avatar_widget);
1630 empathy_call_window_update_self_avatar_visibility (window);
1634 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1636 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1638 g_signal_connect (priv->handler, "conference-added",
1639 G_CALLBACK (empathy_call_window_conference_added_cb), window);
1640 g_signal_connect (priv->handler, "request-resource",
1641 G_CALLBACK (empathy_call_window_request_resource_cb), window);
1642 g_signal_connect (priv->handler, "closed",
1643 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1644 g_signal_connect (priv->handler, "src-pad-added",
1645 G_CALLBACK (empathy_call_window_src_added_cb), window);
1646 g_signal_connect (priv->handler, "sink-pad-added",
1647 G_CALLBACK (empathy_call_window_sink_added_cb), window);
1649 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1653 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1654 EmpathyCallWindow *window)
1656 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1658 if (priv->pipeline != NULL)
1659 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1661 if (priv->call_state == CONNECTING)
1662 empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1668 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1671 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1673 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1678 gtk_widget_hide (priv->sidebar);
1679 gtk_widget_hide (menu);
1680 gtk_widget_hide (priv->vbox);
1681 gtk_widget_hide (priv->statusbar);
1682 gtk_widget_hide (priv->toolbar);
1686 if (priv->sidebar_was_visible_before_fs)
1687 gtk_widget_show (priv->sidebar);
1689 gtk_widget_show (menu);
1690 gtk_widget_show (priv->vbox);
1691 gtk_widget_show (priv->statusbar);
1692 gtk_widget_show (priv->toolbar);
1694 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1695 priv->original_height_before_fs);
1700 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1702 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1704 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1705 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1706 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1707 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1708 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1709 priv->video_output, TRUE, TRUE,
1710 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1712 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1713 priv->vbox, TRUE, TRUE,
1714 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1719 empathy_call_window_state_event_cb (GtkWidget *widget,
1720 GdkEventWindowState *event, EmpathyCallWindow *window)
1722 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1724 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1725 gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1729 gboolean sidebar_was_visible;
1730 gint original_width = GTK_WIDGET (window)->allocation.width;
1731 gint original_height = GTK_WIDGET (window)->allocation.height;
1733 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1735 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1736 priv->original_width_before_fs = original_width;
1737 priv->original_height_before_fs = original_height;
1739 if (priv->video_output_motion_handler_id == 0 &&
1740 priv->video_output != NULL)
1742 priv->video_output_motion_handler_id = g_signal_connect (
1743 G_OBJECT (priv->video_output), "motion-notify-event",
1744 G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1749 if (priv->video_output_motion_handler_id != 0)
1751 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1752 priv->video_output_motion_handler_id);
1753 priv->video_output_motion_handler_id = 0;
1757 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1759 show_controls (window, set_fullscreen);
1760 show_borders (window, set_fullscreen);
1761 gtk_action_set_stock_id (priv->menu_fullscreen,
1762 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1763 priv->is_fullscreen = set_fullscreen;
1770 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1771 EmpathyCallWindow *window)
1773 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1775 int w,h, handle_size;
1777 w = GTK_WIDGET (window)->allocation.width;
1778 h = GTK_WIDGET (window)->allocation.height;
1780 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1782 if (gtk_toggle_button_get_active (toggle))
1784 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1785 gtk_widget_show (priv->sidebar);
1786 w += priv->sidebar->allocation.width + handle_size;
1790 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1791 w -= priv->sidebar->allocation.width + handle_size;
1792 gtk_widget_hide (priv->sidebar);
1795 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1798 gtk_window_resize (GTK_WINDOW (window), w, h);
1802 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1805 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1806 EmpathyTpCall *call;
1808 priv->sending_video = send;
1810 /* When we start sending video, we want to show the video preview by
1814 empathy_call_window_setup_video_preview (window);
1815 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1819 g_object_get (priv->handler, "tp-call", &call, NULL);
1820 empathy_tp_call_request_video_stream_direction (call, send);
1821 g_object_unref (call);
1825 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1826 EmpathyCallWindow *window)
1828 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1831 if (priv->call_state != CONNECTED)
1834 active = (gtk_toggle_tool_button_get_active (toggle));
1836 if (priv->sending_video == active)
1839 empathy_call_window_set_send_video (window, active);
1840 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1844 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1845 EmpathyCallWindow *window)
1847 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1850 if (priv->call_state != CONNECTED)
1853 active = (gtk_toggle_action_get_active (toggle));
1855 if (priv->sending_video == active)
1858 empathy_call_window_set_send_video (window, active);
1859 gtk_toggle_tool_button_set_active (
1860 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1864 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1865 EmpathyCallWindow *window)
1867 gboolean show_preview_toggled;
1868 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1870 show_preview_toggled = gtk_toggle_action_get_active (toggle);
1872 if (show_preview_toggled)
1874 empathy_call_window_setup_video_preview (window);
1875 gtk_widget_show (priv->self_user_output_frame);
1876 empathy_call_window_update_self_avatar_visibility (window);
1880 gtk_widget_hide (priv->self_user_output_frame);
1885 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1886 EmpathyCallWindow *window)
1888 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1891 if (priv->audio_input == NULL)
1894 active = (gtk_toggle_tool_button_get_active (toggle));
1898 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1900 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1904 /* TODO, Instead of setting the input volume to 0 we should probably
1905 * stop sending but this would cause the audio call to drop if both
1906 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1907 * in the future. GNOME #574574
1909 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1911 gtk_adjustment_set_value (priv->audio_input_adj, 0);
1916 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1917 EmpathyCallWindow *window)
1919 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1921 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1926 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1927 EmpathyCallWindow *window)
1929 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1931 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1936 empathy_call_window_hangup_cb (gpointer object,
1937 EmpathyCallWindow *window)
1939 if (empathy_call_window_disconnected (window))
1940 gtk_widget_destroy (GTK_WIDGET (window));
1944 empathy_call_window_restart_call (EmpathyCallWindow *window)
1947 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1949 gtk_widget_destroy (priv->remote_user_output_hbox);
1950 gtk_widget_destroy (priv->self_user_output_hbox);
1952 priv->pipeline = gst_pipeline_new (NULL);
1953 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
1954 gst_bus_add_watch (bus, empathy_call_window_bus_message, window);
1956 empathy_call_window_setup_remote_frame (bus, window);
1957 empathy_call_window_setup_self_frame (bus, window);
1959 g_object_unref (bus);
1961 gtk_widget_show_all (priv->content_hbox);
1963 if (!empathy_call_handler_has_initial_video (priv->handler))
1964 gtk_widget_hide (priv->self_user_output_frame);
1966 priv->outgoing = TRUE;
1967 empathy_call_window_set_state_connecting (window);
1969 priv->call_started = TRUE;
1970 empathy_call_handler_start_call (priv->handler);
1971 empathy_call_window_setup_avatars (window, priv->handler);
1972 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1974 gtk_action_set_sensitive (priv->redial, FALSE);
1975 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1979 empathy_call_window_redial_cb (gpointer object,
1980 EmpathyCallWindow *window)
1982 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1984 if (priv->call_state == CONNECTED)
1985 priv->call_state = REDIALING;
1987 empathy_call_handler_stop_call (priv->handler);
1989 if (priv->call_state != CONNECTED)
1990 empathy_call_window_restart_call (window);
1994 empathy_call_window_fullscreen_cb (gpointer object,
1995 EmpathyCallWindow *window)
1997 empathy_call_window_fullscreen_toggle (window);
2001 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2003 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2005 if (priv->is_fullscreen)
2006 gtk_window_unfullscreen (GTK_WINDOW (window));
2008 gtk_window_fullscreen (GTK_WINDOW (window));
2012 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2013 GdkEventButton *event, EmpathyCallWindow *window)
2015 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2017 empathy_call_window_video_menu_popup (window, event->button);
2025 empathy_call_window_key_press_cb (GtkWidget *video_output,
2026 GdkEventKey *event, EmpathyCallWindow *window)
2028 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2030 if (priv->is_fullscreen && event->keyval == GDK_Escape)
2032 /* Since we are in fullscreen mode, toggling will bring us back to
2034 empathy_call_window_fullscreen_toggle (window);
2042 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2043 GdkEventMotion *event, EmpathyCallWindow *window)
2045 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2047 if (priv->is_fullscreen)
2049 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2056 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2060 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2062 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2064 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2065 button, gtk_get_current_event_time ());
2066 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2070 empathy_call_window_status_message (EmpathyCallWindow *window,
2073 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2075 if (priv->context_id == 0)
2077 priv->context_id = gtk_statusbar_get_context_id (
2078 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2082 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2085 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2090 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2091 gdouble value, EmpathyCallWindow *window)
2093 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2095 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),