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>
44 #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 #define CONNECTING_STATUS_TEXT _("Connecting...")
65 /* If an video input error occurs, the error message will start with "v4l" */
66 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
68 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
77 static guint signals[LAST_SIGNAL] = {0};
81 PROP_CALL_HANDLER = 1,
84 /* private structure */
85 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
87 struct _EmpathyCallWindowPriv
89 gboolean dispose_has_run;
90 EmpathyCallHandler *handler;
91 EmpathyContact *contact;
95 GtkUIManager *ui_manager;
96 GtkWidget *video_output;
97 GtkWidget *video_preview;
98 GtkWidget *remote_user_avatar_widget;
99 GtkWidget *self_user_avatar_widget;
101 GtkWidget *sidebar_button;
102 GtkWidget *statusbar;
103 GtkWidget *volume_button;
104 GtkWidget *redial_button;
105 GtkWidget *mic_button;
106 GtkWidget *camera_button;
109 GtkAction *show_preview;
110 GtkAction *send_video;
112 GtkAction *menu_fullscreen;
114 /* The frames and boxes that contain self and remote avatar and video
115 input/output. When we redial, we destroy and re-create the boxes */
116 GtkWidget *remote_user_output_frame;
117 GtkWidget *self_user_output_frame;
118 GtkWidget *remote_user_output_hbox;
119 GtkWidget *self_user_output_hbox;
121 /* We keep a reference on the hbox which contains the main content so we can
122 easilly repack everything when toggling fullscreen */
123 GtkWidget *content_hbox;
125 /* This vbox is contained in the content_hbox and it contains the
126 self_user_output_frame and the sidebar button. When toggling fullscreen,
127 it needs to be repacked. We keep a reference on it for easier access. */
130 gulong video_output_motion_handler_id;
133 GtkWidget *volume_progress_bar;
134 GtkAdjustment *audio_input_adj;
136 GtkWidget *dtmf_panel;
138 GstElement *video_input;
139 GstElement *audio_input;
140 GstElement *audio_output;
141 GstElement *pipeline;
142 GstElement *video_tee;
145 GstElement *liveadder;
152 GtkWidget *video_contrast;
153 GtkWidget *video_brightness;
154 GtkWidget *video_gamma;
157 gboolean call_started;
158 gboolean sending_video;
160 EmpathyCallWindowFullscreen *fullscreen;
161 gboolean is_fullscreen;
163 /* Those fields represent the state of the window before it actually was in
165 gboolean sidebar_was_visible_before_fs;
166 gint original_width_before_fs;
167 gint original_height_before_fs;
169 /* Used to indicate if we are currently redialing. If we are, as soon as the
170 channel is closed, the call is automatically re-initiated.*/
174 #define GET_PRIV(o) \
175 (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW, \
176 EmpathyCallWindowPriv))
178 static void empathy_call_window_realized_cb (GtkWidget *widget,
179 EmpathyCallWindow *window);
181 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
182 GdkEvent *event, EmpathyCallWindow *window);
184 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
185 GdkEventWindowState *event, EmpathyCallWindow *window);
187 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
188 EmpathyCallWindow *window);
190 static void empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
191 EmpathyCallWindow *window);
193 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
196 static void empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
197 EmpathyCallWindow *window);
199 static void empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
200 EmpathyCallWindow *window);
202 static void empathy_call_window_mic_toggled_cb (
203 GtkToggleToolButton *toggle, EmpathyCallWindow *window);
205 static void empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
206 EmpathyCallWindow *window);
208 static void empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
209 EmpathyCallWindow *window);
211 static void empathy_call_window_hangup_cb (gpointer object,
212 EmpathyCallWindow *window);
214 static void empathy_call_window_fullscreen_cb (gpointer object,
215 EmpathyCallWindow *window);
217 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
219 static gboolean empathy_call_window_video_button_press_cb (GtkWidget *video_output,
220 GdkEventButton *event, EmpathyCallWindow *window);
222 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
223 GdkEventKey *event, EmpathyCallWindow *window);
225 static gboolean empathy_call_window_video_output_motion_notify (GtkWidget *widget,
226 GdkEventMotion *event, EmpathyCallWindow *window);
228 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
231 static void empathy_call_window_redial_cb (gpointer object,
232 EmpathyCallWindow *window);
234 static void empathy_call_window_restart_call (EmpathyCallWindow *window);
236 static void empathy_call_window_status_message (EmpathyCallWindow *window,
239 static void empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
240 EmpathyCallWindow *window);
242 static gboolean empathy_call_window_bus_message (GstBus *bus,
243 GstMessage *message, gpointer user_data);
246 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
247 gdouble value, EmpathyCallWindow *window);
250 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
252 EmpathyCallWindowPriv *priv = GET_PRIV (self);
253 GtkToolItem *tool_item;
255 /* Add an empty expanded GtkToolItem so the volume button is at the end of
257 tool_item = gtk_tool_item_new ();
258 gtk_tool_item_set_expand (tool_item, TRUE);
259 gtk_widget_show (GTK_WIDGET (tool_item));
260 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
262 priv->volume_button = gtk_volume_button_new ();
263 /* FIXME listen to the audiosinks signals and update the button according to
264 * that, for now starting out at 1.0 and assuming only the app changes the
266 gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
267 g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
268 G_CALLBACK (empathy_call_window_volume_changed_cb), self);
270 tool_item = gtk_tool_item_new ();
271 gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
272 gtk_widget_show_all (GTK_WIDGET (tool_item));
273 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
277 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
279 EmpathyCallWindowPriv *priv = GET_PRIV (window);
284 g_object_get (priv->handler, "tp-call", &call, NULL);
286 button_quark = g_quark_from_static_string (BUTTON_ID);
287 event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
290 empathy_tp_call_start_tone (call, event);
292 g_object_unref (call);
296 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
298 EmpathyCallWindowPriv *priv = GET_PRIV (window);
301 g_object_get (priv->handler, "tp-call", &call, NULL);
303 empathy_tp_call_stop_tone (call);
305 g_object_unref (call);
309 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
317 } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
318 { "2", TP_DTMF_EVENT_DIGIT_2 },
319 { "3", TP_DTMF_EVENT_DIGIT_3 },
320 { "4", TP_DTMF_EVENT_DIGIT_4 },
321 { "5", TP_DTMF_EVENT_DIGIT_5 },
322 { "6", TP_DTMF_EVENT_DIGIT_6 },
323 { "7", TP_DTMF_EVENT_DIGIT_7 },
324 { "8", TP_DTMF_EVENT_DIGIT_8 },
325 { "9", TP_DTMF_EVENT_DIGIT_9 },
326 { "#", TP_DTMF_EVENT_HASH },
327 { "0", TP_DTMF_EVENT_DIGIT_0 },
328 { "*", TP_DTMF_EVENT_ASTERISK },
331 button_quark = g_quark_from_static_string (BUTTON_ID);
333 table = gtk_table_new (4, 3, TRUE);
335 for (i = 0; dtmfbuttons[i].label != NULL; i++)
337 GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
338 gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
339 i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
341 g_object_set_qdata (G_OBJECT (button), button_quark,
342 GUINT_TO_POINTER (dtmfbuttons[i].event));
344 g_signal_connect (G_OBJECT (button), "pressed",
345 G_CALLBACK (dtmf_button_pressed_cb), self);
346 g_signal_connect (G_OBJECT (button), "released",
347 G_CALLBACK (dtmf_button_released_cb), self);
354 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
355 gchar *label_text, GtkWidget *bin)
357 GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
358 GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
359 GtkWidget *label = gtk_label_new (label_text);
361 gtk_widget_set_sensitive (scale, FALSE);
363 gtk_container_add (GTK_CONTAINER (bin), vbox);
365 gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
366 gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
367 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
373 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
374 EmpathyCallWindow *self)
377 EmpathyCallWindowPriv *priv = GET_PRIV (self);
379 empathy_video_src_set_channel (priv->video_input,
380 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
384 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
385 EmpathyCallWindow *self)
388 EmpathyCallWindowPriv *priv = GET_PRIV (self);
390 empathy_video_src_set_channel (priv->video_input,
391 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
395 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
396 EmpathyCallWindow *self)
399 EmpathyCallWindowPriv *priv = GET_PRIV (self);
401 empathy_video_src_set_channel (priv->video_input,
402 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
407 empathy_call_window_create_video_input (EmpathyCallWindow *self)
409 EmpathyCallWindowPriv *priv = GET_PRIV (self);
412 hbox = gtk_hbox_new (TRUE, 3);
414 priv->video_contrast = empathy_call_window_create_video_input_add_slider (
415 self, _("Contrast"), hbox);
417 priv->video_brightness = empathy_call_window_create_video_input_add_slider (
418 self, _("Brightness"), hbox);
420 priv->video_gamma = empathy_call_window_create_video_input_add_slider (
421 self, _("Gamma"), hbox);
427 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
429 EmpathyCallWindowPriv *priv = GET_PRIV (self);
433 supported = empathy_video_src_get_supported_channels (priv->video_input);
435 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
437 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
439 gtk_adjustment_set_value (adj,
440 empathy_video_src_get_channel (priv->video_input,
441 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
443 g_signal_connect (G_OBJECT (adj), "value-changed",
444 G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
446 gtk_widget_set_sensitive (priv->video_contrast, TRUE);
449 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
451 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
453 gtk_adjustment_set_value (adj,
454 empathy_video_src_get_channel (priv->video_input,
455 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
457 g_signal_connect (G_OBJECT (adj), "value-changed",
458 G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
459 gtk_widget_set_sensitive (priv->video_brightness, TRUE);
462 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
464 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
466 gtk_adjustment_set_value (adj,
467 empathy_video_src_get_channel (priv->video_input,
468 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
470 g_signal_connect (G_OBJECT (adj), "value-changed",
471 G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
472 gtk_widget_set_sensitive (priv->video_gamma, TRUE);
477 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
478 EmpathyCallWindow *self)
480 EmpathyCallWindowPriv *priv = GET_PRIV (self);
483 volume = gtk_adjustment_get_value (adj)/100.0;
485 /* Don't store the volume because of muting */
486 if (volume > 0 || gtk_toggle_tool_button_get_active (
487 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
488 priv->volume = volume;
490 /* Ensure that the toggle button is active if the volume is > 0 and inactive
491 * if it's smaller than 0 */
492 if ((volume > 0) != gtk_toggle_tool_button_get_active (
493 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
494 gtk_toggle_tool_button_set_active (
495 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
497 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
502 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
503 gdouble level, EmpathyCallWindow *window)
506 EmpathyCallWindowPriv *priv = GET_PRIV (window);
508 value = CLAMP (pow (10, level / 20), 0.0, 1.0);
509 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar), value);
513 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
515 EmpathyCallWindowPriv *priv = GET_PRIV (self);
516 GtkWidget *hbox, *vbox, *scale, *label;
519 hbox = gtk_hbox_new (TRUE, 3);
521 vbox = gtk_vbox_new (FALSE, 3);
522 gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
524 scale = gtk_vscale_new_with_range (0, 150, 100);
525 gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
526 label = gtk_label_new (_("Volume"));
528 priv->audio_input_adj = adj = gtk_range_get_adjustment (GTK_RANGE (scale));
529 priv->volume = empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
530 (priv->audio_input));
531 gtk_adjustment_set_value (adj, priv->volume * 100);
533 g_signal_connect (G_OBJECT (adj), "value-changed",
534 G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
536 gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 3);
537 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
539 priv->volume_progress_bar = gtk_progress_bar_new ();
540 gtk_progress_bar_set_orientation (
541 GTK_PROGRESS_BAR (priv->volume_progress_bar), GTK_PROGRESS_BOTTOM_TO_TOP);
542 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
545 gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
552 empathy_call_window_setup_remote_frame (GstBus *bus, EmpathyCallWindow *self)
554 EmpathyCallWindowPriv *priv = GET_PRIV (self);
556 /* Initializing all the content (UI and output gst elements) related to the
558 priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
560 priv->remote_user_avatar_widget = gtk_image_new ();
561 gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
562 priv->remote_user_avatar_widget, TRUE, TRUE, 0);
564 priv->video_output = empathy_video_widget_new (bus);
565 gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
566 priv->video_output, TRUE, TRUE, 0);
568 gtk_widget_add_events (priv->video_output,
569 GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
570 g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
571 G_CALLBACK (empathy_call_window_video_button_press_cb), self);
573 gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
574 priv->remote_user_output_hbox);
576 priv->audio_output = empathy_audio_sink_new ();
577 gst_object_ref (priv->audio_output);
578 gst_object_sink (priv->audio_output);
582 empathy_call_window_setup_self_frame (GstBus *bus, EmpathyCallWindow *self)
584 EmpathyCallWindowPriv *priv = GET_PRIV (self);
586 /* Initializing all the content (UI and input gst elements) related to the
587 self contact, except for the video preview widget. This widget is only
588 initialized when the "show video preview" option is activated */
589 priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
591 priv->self_user_avatar_widget = gtk_image_new ();
592 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
593 priv->self_user_avatar_widget, TRUE, TRUE, 0);
595 gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
596 priv->self_user_output_hbox);
598 priv->video_input = empathy_video_src_new ();
599 gst_object_ref (priv->video_input);
600 gst_object_sink (priv->video_input);
602 priv->audio_input = empathy_audio_src_new ();
603 gst_object_ref (priv->audio_input);
604 gst_object_sink (priv->audio_input);
606 g_signal_connect (priv->audio_input, "peak-level-changed",
607 G_CALLBACK (empathy_call_window_audio_input_level_changed_cb), self);
611 empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
613 EmpathyCallWindowPriv *priv = GET_PRIV (window);
615 GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
617 if (priv->video_preview != NULL)
619 /* Since the video preview and the video tee are initialized and freed
620 at the same time, if one is initialized, then the other one should
622 g_assert (priv->video_tee != NULL);
626 g_assert (priv->video_tee == NULL);
628 priv->video_tee = gst_element_factory_make ("tee", NULL);
629 gst_object_ref (priv->video_tee);
630 gst_object_sink (priv->video_tee);
632 priv->video_preview = empathy_video_widget_new_with_size (bus,
633 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
634 g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
635 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
636 priv->video_preview, TRUE, TRUE, 0);
638 preview = empathy_video_widget_get_element (
639 EMPATHY_VIDEO_WIDGET (priv->video_preview));
640 gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
641 priv->video_tee, preview, NULL);
642 gst_element_link_many (priv->video_input, priv->video_tee,
645 g_object_unref (bus);
647 gst_element_set_state (preview, GST_STATE_PLAYING);
648 gst_element_set_state (priv->video_input, GST_STATE_PLAYING);
649 gst_element_set_state (priv->video_tee, GST_STATE_PLAYING);
653 empathy_call_window_init (EmpathyCallWindow *self)
655 EmpathyCallWindowPriv *priv = GET_PRIV (self);
664 filename = empathy_file_lookup ("empathy-call-window.ui", "src");
665 gui = empathy_builder_get_file (filename,
666 "call_window_vbox", &top_vbox,
668 "statusbar", &priv->statusbar,
669 "redial", &priv->redial_button,
670 "microphone", &priv->mic_button,
671 "camera", &priv->camera_button,
672 "toolbar", &priv->toolbar,
673 "send_video", &priv->send_video,
674 "menuredial", &priv->redial,
675 "show_preview", &priv->show_preview,
676 "ui_manager", &priv->ui_manager,
677 "menufullscreen", &priv->menu_fullscreen,
680 empathy_builder_connect (gui, self,
681 "menuhangup", "activate", empathy_call_window_hangup_cb,
682 "hangup", "clicked", empathy_call_window_hangup_cb,
683 "menuredial", "activate", empathy_call_window_redial_cb,
684 "redial", "clicked", empathy_call_window_redial_cb,
685 "microphone", "toggled", empathy_call_window_mic_toggled_cb,
686 "camera", "toggled", empathy_call_window_camera_toggled_cb,
687 "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
688 "show_preview", "toggled", empathy_call_window_show_preview_toggled_cb,
689 "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
692 priv->lock = g_mutex_new ();
694 gtk_container_add (GTK_CONTAINER (self), top_vbox);
696 empathy_call_window_setup_toolbar (self);
698 priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
699 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
700 CONTENT_HBOX_BORDER_WIDTH);
701 gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
703 priv->pipeline = gst_pipeline_new (NULL);
704 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
705 gst_bus_add_watch (bus, empathy_call_window_bus_message, self);
707 priv->remote_user_output_frame = gtk_frame_new (NULL);
708 gtk_widget_set_size_request (priv->remote_user_output_frame,
709 EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
710 gtk_box_pack_start (GTK_BOX (priv->content_hbox),
711 priv->remote_user_output_frame, TRUE, TRUE,
712 CONTENT_HBOX_CHILDREN_PACKING_PADDING);
713 empathy_call_window_setup_remote_frame (bus, self);
715 priv->self_user_output_frame = gtk_frame_new (NULL);
716 gtk_widget_set_size_request (priv->self_user_output_frame,
717 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
719 priv->vbox = gtk_vbox_new (FALSE, 3);
720 gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
721 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
722 gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame, FALSE,
724 empathy_call_window_setup_self_frame (bus, self);
726 g_object_unref (bus);
728 priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
729 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
730 g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
731 G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
733 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
735 h = gtk_hbox_new (FALSE, 3);
736 gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
737 gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
739 priv->sidebar = empathy_sidebar_new ();
740 g_signal_connect (G_OBJECT (priv->sidebar),
741 "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
742 g_signal_connect (G_OBJECT (priv->sidebar),
743 "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
744 gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
746 priv->dtmf_panel = empathy_call_window_create_dtmf (self);
747 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
750 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
752 page = empathy_call_window_create_audio_input (self);
753 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
756 page = empathy_call_window_create_video_input (self);
757 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
760 gtk_widget_show_all (top_vbox);
762 gtk_widget_hide (priv->sidebar);
764 priv->fullscreen = empathy_call_window_fullscreen_new (self);
765 empathy_call_window_fullscreen_set_video_widget (priv->fullscreen, priv->video_output);
766 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
767 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
769 g_signal_connect (G_OBJECT (self), "realize",
770 G_CALLBACK (empathy_call_window_realized_cb), self);
772 g_signal_connect (G_OBJECT (self), "delete-event",
773 G_CALLBACK (empathy_call_window_delete_cb), self);
775 g_signal_connect (G_OBJECT (self), "window-state-event",
776 G_CALLBACK (empathy_call_window_state_event_cb), self);
778 g_signal_connect (G_OBJECT (self), "key-press-event",
779 G_CALLBACK (empathy_call_window_key_press_cb), self);
781 empathy_call_window_status_message (self, CONNECTING_STATUS_TEXT);
783 priv->timer = g_timer_new ();
785 g_object_ref (priv->ui_manager);
786 g_object_unref (gui);
790 /* Instead of specifying a width and a height, we specify only one size. That's
791 because we want a square avatar icon. */
793 init_contact_avatar_with_size (EmpathyContact *contact, GtkWidget *image_widget,
797 GdkPixbuf *pixbuf_avatar = NULL;
801 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
805 if (pixbuf_avatar == NULL)
807 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
811 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
815 set_window_title (EmpathyCallWindow *self)
817 EmpathyCallWindowPriv *priv = GET_PRIV (self);
820 tmp = g_strdup_printf (_("Call with %s"),
821 empathy_contact_get_name (priv->contact));
822 gtk_window_set_title (GTK_WINDOW (self), tmp);
827 contact_name_changed_cb (EmpathyContact *contact,
828 GParamSpec *pspec, EmpathyCallWindow *self)
830 set_window_title (self);
834 contact_avatar_changed_cb (EmpathyContact *contact,
835 GParamSpec *pspec, GtkWidget *avatar_widget)
837 init_contact_avatar_with_size (contact, avatar_widget,
838 avatar_widget->allocation.height);
842 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
843 EmpathyContact *contact, const GError *error, gpointer user_data,
844 GObject *weak_object)
846 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
847 EmpathyCallWindowPriv *priv = GET_PRIV (self);
849 init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
850 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
852 g_signal_connect (contact, "notify::avatar",
853 G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
857 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
858 EmpathyCallHandler *handler)
860 EmpathyCallWindowPriv *priv = GET_PRIV (self);
862 g_object_get (handler, "contact", &(priv->contact), NULL);
864 if (priv->contact != NULL)
866 TpConnection *connection;
867 EmpathyTpContactFactory *factory;
869 set_window_title (self);
871 g_signal_connect (priv->contact, "notify::name",
872 G_CALLBACK (contact_name_changed_cb), self);
873 g_signal_connect (priv->contact, "notify::avatar",
874 G_CALLBACK (contact_avatar_changed_cb),
875 priv->remote_user_avatar_widget);
877 /* Retreiving the self avatar */
878 connection = empathy_contact_get_connection (priv->contact);
879 factory = empathy_tp_contact_factory_dup_singleton (connection);
880 empathy_tp_contact_factory_get_from_handle (factory,
881 tp_connection_get_self_handle (connection),
882 empathy_call_window_got_self_contact_cb, self, NULL, NULL);
884 g_object_unref (factory);
888 g_warning ("call handler doesn't have a contact");
889 gtk_window_set_title (GTK_WINDOW (self), _("Call"));
891 /* Since we can't access the remote contact, we can't get a connection
892 to it and can't get the self contact (and its avatar). This means
893 that we have to manually set the self avatar. */
894 init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
895 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
898 init_contact_avatar_with_size (priv->contact,
899 priv->remote_user_avatar_widget, MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
900 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
902 /* The remote avatar is shown by default and will be hidden when we receive
903 video from the remote side. */
904 gtk_widget_hide (priv->video_output);
905 gtk_widget_show (priv->remote_user_avatar_widget);
909 empathy_call_window_setup_video_preview_visibility (EmpathyCallWindow *self,
910 EmpathyCallHandler *handler)
912 EmpathyCallWindowPriv *priv = GET_PRIV (self);
913 gboolean initial_video = empathy_call_handler_has_initial_video (priv->handler);
915 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
920 empathy_call_window_constructed (GObject *object)
922 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
923 EmpathyCallWindowPriv *priv = GET_PRIV (self);
925 g_assert (priv->handler != NULL);
926 empathy_call_window_setup_avatars (self, priv->handler);
927 empathy_call_window_setup_video_preview_visibility (self, priv->handler);
930 static void empathy_call_window_dispose (GObject *object);
931 static void empathy_call_window_finalize (GObject *object);
934 empathy_call_window_set_property (GObject *object,
935 guint property_id, const GValue *value, GParamSpec *pspec)
937 EmpathyCallWindowPriv *priv = GET_PRIV (object);
941 case PROP_CALL_HANDLER:
942 priv->handler = g_value_dup_object (value);
945 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
950 empathy_call_window_get_property (GObject *object,
951 guint property_id, GValue *value, GParamSpec *pspec)
953 EmpathyCallWindowPriv *priv = GET_PRIV (object);
957 case PROP_CALL_HANDLER:
958 g_value_set_object (value, priv->handler);
961 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
966 empathy_call_window_class_init (
967 EmpathyCallWindowClass *empathy_call_window_class)
969 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
970 GParamSpec *param_spec;
972 g_type_class_add_private (empathy_call_window_class,
973 sizeof (EmpathyCallWindowPriv));
975 object_class->constructed = empathy_call_window_constructed;
976 object_class->set_property = empathy_call_window_set_property;
977 object_class->get_property = empathy_call_window_get_property;
979 object_class->dispose = empathy_call_window_dispose;
980 object_class->finalize = empathy_call_window_finalize;
982 param_spec = g_param_spec_object ("handler",
983 "handler", "The call handler",
984 EMPATHY_TYPE_CALL_HANDLER,
985 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
986 g_object_class_install_property (object_class,
987 PROP_CALL_HANDLER, param_spec);
992 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
993 GParamSpec *property, EmpathyCallWindow *self)
995 empathy_call_window_update_avatars_visibility (call, self);
999 empathy_call_window_dispose (GObject *object)
1001 EmpathyTpCall *call;
1002 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1003 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1005 if (priv->dispose_has_run)
1008 priv->dispose_has_run = TRUE;
1010 g_object_get (priv->handler, "tp-call", &call, NULL);
1014 g_signal_handlers_disconnect_by_func (call,
1015 empathy_call_window_video_stream_changed_cb, object);
1018 g_object_unref (call);
1020 if (priv->handler != NULL)
1021 g_object_unref (priv->handler);
1023 priv->handler = NULL;
1025 if (priv->pipeline != NULL)
1026 g_object_unref (priv->pipeline);
1027 priv->pipeline = NULL;
1029 if (priv->video_input != NULL)
1030 g_object_unref (priv->video_input);
1031 priv->video_input = NULL;
1033 if (priv->audio_input != NULL)
1034 g_object_unref (priv->audio_input);
1035 priv->audio_input = NULL;
1037 if (priv->audio_output != NULL)
1038 g_object_unref (priv->audio_output);
1039 priv->audio_output = NULL;
1041 if (priv->video_tee != NULL)
1042 g_object_unref (priv->video_tee);
1043 priv->video_tee = NULL;
1045 if (priv->timer_id != 0)
1046 g_source_remove (priv->timer_id);
1049 if (priv->ui_manager != NULL)
1050 g_object_unref (priv->ui_manager);
1051 priv->ui_manager = NULL;
1053 if (priv->contact != NULL)
1055 g_signal_handlers_disconnect_by_func (priv->contact,
1056 contact_name_changed_cb, self);
1057 g_object_unref (priv->contact);
1058 priv->contact = NULL;
1061 /* release any references held by the object here */
1062 if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1063 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1067 empathy_call_window_finalize (GObject *object)
1069 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1070 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1072 if (priv->video_output_motion_handler_id != 0)
1074 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1075 priv->video_output_motion_handler_id);
1076 priv->video_output_motion_handler_id = 0;
1079 /* free any data held directly by the object here */
1080 g_mutex_free (priv->lock);
1082 g_timer_destroy (priv->timer);
1084 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1089 empathy_call_window_new (EmpathyCallHandler *handler)
1091 return EMPATHY_CALL_WINDOW (
1092 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1096 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1097 GstElement *conference, gpointer user_data)
1099 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1100 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1102 gst_bin_add (GST_BIN (priv->pipeline), conference);
1104 gst_element_set_state (conference, GST_STATE_PLAYING);
1108 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1109 FsMediaType type, FsStreamDirection direction, gpointer user_data)
1111 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1112 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1114 if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1117 if (direction == FS_DIRECTION_RECV)
1120 /* video and direction is send */
1121 return priv->video_input != NULL;
1125 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1127 GstStateChangeReturn state_change_return;
1128 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1130 if (priv->pipeline == NULL)
1133 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1135 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1136 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1138 if (priv->pipeline != NULL)
1139 g_object_unref (priv->pipeline);
1140 priv->pipeline = NULL;
1142 if (priv->video_input != NULL)
1143 g_object_unref (priv->video_input);
1144 priv->video_input = NULL;
1146 if (priv->audio_input != NULL)
1147 g_object_unref (priv->audio_input);
1148 priv->audio_input = NULL;
1150 if (priv->audio_output != NULL)
1151 g_object_unref (priv->audio_output);
1152 priv->audio_output = NULL;
1154 if (priv->video_tee != NULL)
1155 g_object_unref (priv->video_tee);
1156 priv->video_tee = NULL;
1158 if (priv->video_preview != NULL)
1159 gtk_widget_destroy (priv->video_preview);
1160 priv->video_preview = NULL;
1162 priv->liveadder = NULL;
1163 priv->funnel = NULL;
1169 g_message ("Error: could not destroy pipeline. Closing call window");
1170 gtk_widget_destroy (GTK_WIDGET (self));
1177 empathy_call_window_disconnected (EmpathyCallWindow *self)
1179 gboolean could_disconnect = FALSE;
1180 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1181 gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1183 priv->connected = FALSE;
1185 if (could_reset_pipeline)
1187 gboolean initial_video = empathy_call_handler_has_initial_video (
1189 g_mutex_lock (priv->lock);
1191 g_timer_stop (priv->timer);
1193 if (priv->timer_id != 0)
1194 g_source_remove (priv->timer_id);
1197 g_mutex_unlock (priv->lock);
1199 empathy_call_window_status_message (self, _("Disconnected"));
1201 gtk_action_set_sensitive (priv->redial, TRUE);
1202 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1204 /* Reseting the send_video, camera_buton and mic_button to their
1206 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1207 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1208 gtk_action_set_sensitive (priv->send_video, FALSE);
1209 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1211 gtk_toggle_tool_button_set_active (
1212 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), initial_video);
1213 gtk_toggle_tool_button_set_active (
1214 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1216 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1218 gtk_action_set_sensitive (priv->show_preview, FALSE);
1220 gtk_widget_hide (priv->video_output);
1221 gtk_widget_show (priv->remote_user_avatar_widget);
1223 priv->sending_video = FALSE;
1224 priv->call_started = FALSE;
1226 could_disconnect = TRUE;
1229 return could_disconnect;
1234 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
1236 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1237 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1239 if (empathy_call_window_disconnected (self) && priv->redialing)
1241 empathy_call_window_restart_call (self);
1242 priv->redialing = FALSE;
1246 /* Called with global lock held */
1248 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1250 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1253 if (priv->funnel == NULL)
1257 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1258 (priv->video_output));
1260 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1262 gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1263 gst_bin_add (GST_BIN (priv->pipeline), output);
1265 gst_element_link (priv->funnel, output);
1267 gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1268 gst_element_set_state (output, GST_STATE_PLAYING);
1271 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1276 /* Called with global lock held */
1278 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1280 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1283 if (priv->liveadder == NULL)
1285 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1287 gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1288 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1290 gst_element_link (priv->liveadder, priv->audio_output);
1292 gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1293 gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1296 pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1302 empathy_call_window_update_timer (gpointer user_data)
1304 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1305 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1309 time = g_timer_elapsed (priv->timer, NULL);
1311 /* Translators: number of minutes:seconds the caller has been connected */
1312 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time / 60,
1314 empathy_call_window_status_message (self, str);
1321 empathy_call_window_connected (gpointer user_data)
1323 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1324 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1325 EmpathyTpCall *call;
1327 g_object_get (priv->handler, "tp-call", &call, NULL);
1329 g_signal_connect (call, "notify::video-stream",
1330 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1332 if (empathy_tp_call_has_dtmf (call))
1333 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1335 if (priv->video_input == NULL)
1336 empathy_call_window_set_send_video (self, FALSE);
1338 priv->sending_video = empathy_tp_call_is_sending_video (call);
1340 gtk_action_set_sensitive (priv->show_preview, TRUE);
1341 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1343 || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
1344 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1345 priv->sending_video && priv->video_input != NULL);
1346 gtk_toggle_tool_button_set_active (
1347 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1348 priv->sending_video && priv->video_input != NULL);
1349 gtk_widget_set_sensitive (priv->camera_button, priv->video_input != NULL);
1350 gtk_action_set_sensitive (priv->send_video, priv->video_input != NULL);
1352 gtk_action_set_sensitive (priv->redial, FALSE);
1353 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1355 gtk_widget_set_sensitive (priv->mic_button, TRUE);
1357 empathy_call_window_update_avatars_visibility (call, self);
1359 g_object_unref (call);
1361 g_mutex_lock (priv->lock);
1363 priv->timer_id = g_timeout_add_seconds (1,
1364 empathy_call_window_update_timer, self);
1366 g_mutex_unlock (priv->lock);
1368 empathy_call_window_update_timer (self);
1374 /* Called from the streaming thread */
1376 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1377 GstPad *src, guint media_type, gpointer user_data)
1379 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1380 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1384 g_mutex_lock (priv->lock);
1386 if (priv->connected == FALSE)
1388 g_timer_start (priv->timer);
1389 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
1390 priv->connected = TRUE;
1395 case TP_MEDIA_STREAM_TYPE_AUDIO:
1396 pad = empathy_call_window_get_audio_sink_pad (self);
1398 case TP_MEDIA_STREAM_TYPE_VIDEO:
1399 gtk_widget_hide (priv->remote_user_avatar_widget);
1400 gtk_widget_show (priv->video_output);
1401 pad = empathy_call_window_get_video_sink_pad (self);
1404 g_assert_not_reached ();
1407 gst_pad_link (src, pad);
1408 gst_object_unref (pad);
1410 g_mutex_unlock (priv->lock);
1413 /* Called from the streaming thread */
1415 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1416 GstPad *sink, guint media_type, gpointer user_data)
1418 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1419 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1424 case TP_MEDIA_STREAM_TYPE_AUDIO:
1425 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1427 pad = gst_element_get_static_pad (priv->audio_input, "src");
1428 gst_pad_link (pad, sink);
1430 gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1432 case TP_MEDIA_STREAM_TYPE_VIDEO:
1433 if (priv->video_input != NULL)
1435 EmpathyTpCall *call;
1436 g_object_get (priv->handler, "tp-call", &call, NULL);
1438 if (empathy_tp_call_is_sending_video (call))
1440 empathy_call_window_setup_video_preview (self);
1442 gtk_toggle_action_set_active (
1443 GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1445 if (priv->video_preview != NULL)
1446 gtk_widget_show (priv->video_preview);
1447 gtk_widget_hide (priv->self_user_avatar_widget);
1450 g_object_unref (call);
1452 if (priv->video_tee != NULL)
1454 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1455 gst_pad_link (pad, sink);
1460 g_assert_not_reached ();
1466 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1468 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1469 GstElement *preview;
1471 preview = empathy_video_widget_get_element (
1472 EMPATHY_VIDEO_WIDGET (priv->video_preview));
1474 gst_element_set_state (priv->video_input, GST_STATE_NULL);
1475 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1476 gst_element_set_state (preview, GST_STATE_NULL);
1478 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1479 priv->video_tee, preview, NULL);
1481 g_object_unref (priv->video_input);
1482 priv->video_input = NULL;
1483 g_object_unref (priv->video_tee);
1484 priv->video_tee = NULL;
1485 gtk_widget_destroy (priv->video_preview);
1486 priv->video_preview = NULL;
1488 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1489 gtk_toggle_tool_button_set_active (
1490 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1491 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1492 gtk_action_set_sensitive (priv->send_video, FALSE);
1494 gtk_widget_show (priv->self_user_avatar_widget);
1499 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1502 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1503 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1506 empathy_call_handler_bus_message (priv->handler, bus, message);
1508 switch (GST_MESSAGE_TYPE (message))
1510 case GST_MESSAGE_STATE_CHANGED:
1511 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1513 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1514 if (newstate == GST_STATE_PAUSED)
1515 empathy_call_window_setup_video_input (self);
1517 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1518 !priv->call_started)
1520 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1521 if (newstate == GST_STATE_PAUSED)
1523 priv->call_started = TRUE;
1524 empathy_call_handler_start_call (priv->handler);
1525 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1529 case GST_MESSAGE_ERROR:
1531 GError *error = NULL;
1532 GstElement *gst_error;
1535 gst_message_parse_error (message, &error, &debug);
1536 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1538 g_message ("Element error: %s -- %s\n", error->message, debug);
1540 if (g_str_has_prefix (gst_element_get_name (gst_error),
1541 VIDEO_INPUT_ERROR_PREFIX))
1543 /* Remove the video input and continue */
1544 if (priv->video_input != NULL)
1545 empathy_call_window_remove_video_input (self);
1546 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1550 empathy_call_window_disconnected (self);
1552 g_error_free (error);
1563 empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
1565 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1567 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1569 if (priv->video_preview != NULL)
1571 gtk_widget_hide (priv->self_user_avatar_widget);
1572 gtk_widget_show (priv->video_preview);
1576 if (priv->video_preview != NULL)
1577 gtk_widget_hide (priv->video_preview);
1579 gtk_widget_show (priv->self_user_avatar_widget);
1585 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1586 EmpathyCallWindow *window)
1588 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1590 if (empathy_tp_call_is_receiving_video (call))
1592 gtk_widget_hide (priv->remote_user_avatar_widget);
1593 gtk_widget_show (priv->video_output);
1597 gtk_widget_hide (priv->video_output);
1598 gtk_widget_show (priv->remote_user_avatar_widget);
1601 empathy_call_window_update_self_avatar_visibility (window);
1605 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1607 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1609 g_signal_connect (priv->handler, "conference-added",
1610 G_CALLBACK (empathy_call_window_conference_added_cb), window);
1611 g_signal_connect (priv->handler, "request-resource",
1612 G_CALLBACK (empathy_call_window_request_resource_cb), window);
1613 g_signal_connect (priv->handler, "closed",
1614 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1615 g_signal_connect (priv->handler, "src-pad-added",
1616 G_CALLBACK (empathy_call_window_src_added_cb), window);
1617 g_signal_connect (priv->handler, "sink-pad-added",
1618 G_CALLBACK (empathy_call_window_sink_added_cb), window);
1620 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1624 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1625 EmpathyCallWindow *window)
1627 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1629 if (priv->pipeline != NULL)
1630 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1636 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1639 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1641 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1646 gtk_widget_hide (priv->sidebar);
1647 gtk_widget_hide (menu);
1648 gtk_widget_hide (priv->vbox);
1649 gtk_widget_hide (priv->statusbar);
1650 gtk_widget_hide (priv->toolbar);
1654 if (priv->sidebar_was_visible_before_fs)
1655 gtk_widget_show (priv->sidebar);
1657 gtk_widget_show (menu);
1658 gtk_widget_show (priv->vbox);
1659 gtk_widget_show (priv->statusbar);
1660 gtk_widget_show (priv->toolbar);
1662 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1663 priv->original_height_before_fs);
1668 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1670 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1672 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1673 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1674 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1675 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1676 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1677 priv->video_output, TRUE, TRUE,
1678 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1680 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1681 priv->vbox, TRUE, TRUE,
1682 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1687 empathy_call_window_state_event_cb (GtkWidget *widget,
1688 GdkEventWindowState *event, EmpathyCallWindow *window)
1690 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1692 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1693 gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1697 gboolean sidebar_was_visible;
1698 gint original_width = GTK_WIDGET (window)->allocation.width;
1699 gint original_height = GTK_WIDGET (window)->allocation.height;
1701 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1703 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1704 priv->original_width_before_fs = original_width;
1705 priv->original_height_before_fs = original_height;
1707 if (priv->video_output_motion_handler_id == 0 &&
1708 priv->video_output != NULL)
1710 priv->video_output_motion_handler_id = g_signal_connect (
1711 G_OBJECT (priv->video_output), "motion-notify-event",
1712 G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1717 if (priv->video_output_motion_handler_id != 0)
1719 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1720 priv->video_output_motion_handler_id);
1721 priv->video_output_motion_handler_id = 0;
1725 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1727 show_controls (window, set_fullscreen);
1728 show_borders (window, set_fullscreen);
1729 gtk_action_set_stock_id (priv->menu_fullscreen,
1730 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1731 priv->is_fullscreen = set_fullscreen;
1738 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1739 EmpathyCallWindow *window)
1741 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1743 int w,h, handle_size;
1745 w = GTK_WIDGET (window)->allocation.width;
1746 h = GTK_WIDGET (window)->allocation.height;
1748 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1750 if (gtk_toggle_button_get_active (toggle))
1752 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1753 gtk_widget_show (priv->sidebar);
1754 w += priv->sidebar->allocation.width + handle_size;
1758 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1759 w -= priv->sidebar->allocation.width + handle_size;
1760 gtk_widget_hide (priv->sidebar);
1763 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1766 gtk_window_resize (GTK_WINDOW (window), w, h);
1770 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1773 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1774 EmpathyTpCall *call;
1776 priv->sending_video = send;
1778 /* When we start sending video, we want to show the video preview by
1782 empathy_call_window_setup_video_preview (window);
1783 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1787 g_object_get (priv->handler, "tp-call", &call, NULL);
1788 empathy_tp_call_request_video_stream_direction (call, send);
1789 g_object_unref (call);
1793 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1794 EmpathyCallWindow *window)
1796 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1799 if (!priv->connected)
1802 active = (gtk_toggle_tool_button_get_active (toggle));
1804 if (priv->sending_video == active)
1807 empathy_call_window_set_send_video (window, active);
1808 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1812 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1813 EmpathyCallWindow *window)
1815 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1818 if (!priv->connected)
1821 active = (gtk_toggle_action_get_active (toggle));
1823 if (priv->sending_video == active)
1826 empathy_call_window_set_send_video (window, active);
1827 gtk_toggle_tool_button_set_active (
1828 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1832 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1833 EmpathyCallWindow *window)
1835 gboolean show_preview_toggled;
1836 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1838 show_preview_toggled = gtk_toggle_action_get_active (toggle);
1840 if (show_preview_toggled)
1842 empathy_call_window_setup_video_preview (window);
1843 gtk_widget_show (priv->self_user_output_frame);
1844 empathy_call_window_update_self_avatar_visibility (window);
1848 gtk_widget_hide (priv->self_user_output_frame);
1853 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1854 EmpathyCallWindow *window)
1856 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1859 if (priv->audio_input == NULL)
1862 active = (gtk_toggle_tool_button_get_active (toggle));
1866 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1868 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1872 /* TODO, Instead of setting the input volume to 0 we should probably
1873 * stop sending but this would cause the audio call to drop if both
1874 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1875 * in the future. GNOME #574574
1877 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1879 gtk_adjustment_set_value (priv->audio_input_adj, 0);
1884 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1885 EmpathyCallWindow *window)
1887 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1889 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1894 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1895 EmpathyCallWindow *window)
1897 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1899 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1904 empathy_call_window_hangup_cb (gpointer object,
1905 EmpathyCallWindow *window)
1907 if (empathy_call_window_disconnected (window))
1908 gtk_widget_destroy (GTK_WIDGET (window));
1912 empathy_call_window_restart_call (EmpathyCallWindow *window)
1915 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1917 gtk_widget_destroy (priv->remote_user_output_hbox);
1918 gtk_widget_destroy (priv->self_user_output_hbox);
1920 priv->pipeline = gst_pipeline_new (NULL);
1921 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
1922 gst_bus_add_watch (bus, empathy_call_window_bus_message, window);
1924 empathy_call_window_setup_remote_frame (bus, window);
1925 empathy_call_window_setup_self_frame (bus, window);
1927 g_object_unref (bus);
1929 gtk_widget_show_all (priv->content_hbox);
1931 if (!empathy_call_handler_has_initial_video (priv->handler))
1932 gtk_widget_hide (priv->self_user_output_frame);
1934 empathy_call_window_status_message (window, CONNECTING_STATUS_TEXT);
1935 priv->call_started = TRUE;
1936 empathy_call_handler_start_call (priv->handler);
1937 empathy_call_window_setup_avatars (window, priv->handler);
1938 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1940 gtk_action_set_sensitive (priv->redial, FALSE);
1941 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1945 empathy_call_window_redial_cb (gpointer object,
1946 EmpathyCallWindow *window)
1948 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1950 if (priv->connected)
1951 priv->redialing = TRUE;
1953 empathy_call_handler_stop_call (priv->handler);
1955 if (!priv->connected)
1956 empathy_call_window_restart_call (window);
1960 empathy_call_window_fullscreen_cb (gpointer object,
1961 EmpathyCallWindow *window)
1963 empathy_call_window_fullscreen_toggle (window);
1967 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
1969 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1971 if (priv->is_fullscreen)
1972 gtk_window_unfullscreen (GTK_WINDOW (window));
1974 gtk_window_fullscreen (GTK_WINDOW (window));
1978 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
1979 GdkEventButton *event, EmpathyCallWindow *window)
1981 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1983 empathy_call_window_video_menu_popup (window, event->button);
1991 empathy_call_window_key_press_cb (GtkWidget *video_output,
1992 GdkEventKey *event, EmpathyCallWindow *window)
1994 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1996 if (priv->is_fullscreen && event->keyval == GDK_Escape)
1998 /* Since we are in fullscreen mode, toggling will bring us back to
2000 empathy_call_window_fullscreen_toggle (window);
2008 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2009 GdkEventMotion *event, EmpathyCallWindow *window)
2011 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2013 if (priv->is_fullscreen)
2015 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2022 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2026 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2028 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2030 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2031 button, gtk_get_current_event_time ());
2032 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2036 empathy_call_window_status_message (EmpathyCallWindow *window,
2039 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2041 if (priv->context_id == 0)
2043 priv->context_id = gtk_statusbar_get_context_id (
2044 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2048 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2051 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2056 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2057 gdouble value, EmpathyCallWindow *window)
2059 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2061 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),