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 then 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 and camera_buton to their initial state */
1205 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1206 gtk_action_set_sensitive (priv->send_video, FALSE);
1207 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1209 gtk_toggle_tool_button_set_active (
1210 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), initial_video);
1212 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1214 gtk_action_set_sensitive (priv->show_preview, FALSE);
1216 gtk_widget_hide (priv->video_output);
1217 gtk_widget_show (priv->remote_user_avatar_widget);
1219 priv->sending_video = FALSE;
1220 priv->call_started = FALSE;
1222 could_disconnect = TRUE;
1225 return could_disconnect;
1230 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
1232 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1233 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1235 if (empathy_call_window_disconnected (self) && priv->redialing)
1237 empathy_call_window_restart_call (self);
1238 priv->redialing = FALSE;
1242 /* Called with global lock held */
1244 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1246 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1249 if (priv->funnel == NULL)
1253 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1254 (priv->video_output));
1256 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1258 gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1259 gst_bin_add (GST_BIN (priv->pipeline), output);
1261 gst_element_link (priv->funnel, output);
1263 gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1264 gst_element_set_state (output, GST_STATE_PLAYING);
1267 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1272 /* Called with global lock held */
1274 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1276 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1279 if (priv->liveadder == NULL)
1281 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1283 gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1284 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1286 gst_element_link (priv->liveadder, priv->audio_output);
1288 gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1289 gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1292 pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1298 empathy_call_window_update_timer (gpointer user_data)
1300 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1301 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1305 time = g_timer_elapsed (priv->timer, NULL);
1307 /* Translators: number of minutes:seconds the caller has been connected */
1308 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time / 60,
1310 empathy_call_window_status_message (self, str);
1317 empathy_call_window_connected (gpointer user_data)
1319 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1320 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1321 EmpathyTpCall *call;
1323 g_object_get (priv->handler, "tp-call", &call, NULL);
1325 g_signal_connect (call, "notify::video-stream",
1326 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1328 if (empathy_tp_call_has_dtmf (call))
1329 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1331 if (priv->video_input == NULL)
1332 empathy_call_window_set_send_video (self, FALSE);
1334 priv->sending_video = empathy_tp_call_is_sending_video (call);
1336 gtk_action_set_sensitive (priv->show_preview, TRUE);
1337 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1339 || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
1340 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1341 priv->sending_video && priv->video_input != NULL);
1342 gtk_toggle_tool_button_set_active (
1343 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1344 priv->sending_video && priv->video_input != NULL);
1345 gtk_widget_set_sensitive (priv->camera_button, priv->video_input != NULL);
1346 gtk_action_set_sensitive (priv->send_video, priv->video_input != NULL);
1348 gtk_action_set_sensitive (priv->redial, FALSE);
1349 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1351 empathy_call_window_update_avatars_visibility (call, self);
1353 g_object_unref (call);
1355 g_mutex_lock (priv->lock);
1357 priv->timer_id = g_timeout_add_seconds (1,
1358 empathy_call_window_update_timer, self);
1360 g_mutex_unlock (priv->lock);
1362 empathy_call_window_update_timer (self);
1368 /* Called from the streaming thread */
1370 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1371 GstPad *src, guint media_type, gpointer user_data)
1373 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1374 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1378 g_mutex_lock (priv->lock);
1380 if (priv->connected == FALSE)
1382 g_timer_start (priv->timer);
1383 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
1384 priv->connected = TRUE;
1389 case TP_MEDIA_STREAM_TYPE_AUDIO:
1390 pad = empathy_call_window_get_audio_sink_pad (self);
1392 case TP_MEDIA_STREAM_TYPE_VIDEO:
1393 gtk_widget_hide (priv->remote_user_avatar_widget);
1394 gtk_widget_show (priv->video_output);
1395 pad = empathy_call_window_get_video_sink_pad (self);
1398 g_assert_not_reached ();
1401 gst_pad_link (src, pad);
1402 gst_object_unref (pad);
1404 g_mutex_unlock (priv->lock);
1407 /* Called from the streaming thread */
1409 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1410 GstPad *sink, guint media_type, gpointer user_data)
1412 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1413 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1418 case TP_MEDIA_STREAM_TYPE_AUDIO:
1419 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1421 pad = gst_element_get_static_pad (priv->audio_input, "src");
1422 gst_pad_link (pad, sink);
1424 gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1426 case TP_MEDIA_STREAM_TYPE_VIDEO:
1427 if (priv->video_input != NULL)
1429 EmpathyTpCall *call;
1430 g_object_get (priv->handler, "tp-call", &call, NULL);
1432 if (empathy_tp_call_is_sending_video (call))
1434 empathy_call_window_setup_video_preview (self);
1436 gtk_toggle_action_set_active (
1437 GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1439 if (priv->video_preview != NULL)
1440 gtk_widget_show (priv->video_preview);
1441 gtk_widget_hide (priv->self_user_avatar_widget);
1444 g_object_unref (call);
1446 if (priv->video_tee != NULL)
1448 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1449 gst_pad_link (pad, sink);
1454 g_assert_not_reached ();
1460 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1462 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1463 GstElement *preview;
1465 preview = empathy_video_widget_get_element (
1466 EMPATHY_VIDEO_WIDGET (priv->video_preview));
1468 gst_element_set_state (priv->video_input, GST_STATE_NULL);
1469 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1470 gst_element_set_state (preview, GST_STATE_NULL);
1472 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1473 priv->video_tee, preview, NULL);
1475 g_object_unref (priv->video_input);
1476 priv->video_input = NULL;
1477 g_object_unref (priv->video_tee);
1478 priv->video_tee = NULL;
1479 gtk_widget_destroy (priv->video_preview);
1480 priv->video_preview = NULL;
1482 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1483 gtk_toggle_tool_button_set_active (
1484 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1485 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1486 gtk_action_set_sensitive (priv->send_video, FALSE);
1488 gtk_widget_show (priv->self_user_avatar_widget);
1493 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1496 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1497 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1500 empathy_call_handler_bus_message (priv->handler, bus, message);
1502 switch (GST_MESSAGE_TYPE (message))
1504 case GST_MESSAGE_STATE_CHANGED:
1505 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1507 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1508 if (newstate == GST_STATE_PAUSED)
1509 empathy_call_window_setup_video_input (self);
1511 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1512 !priv->call_started)
1514 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1515 if (newstate == GST_STATE_PAUSED)
1517 priv->call_started = TRUE;
1518 empathy_call_handler_start_call (priv->handler);
1519 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1523 case GST_MESSAGE_ERROR:
1525 GError *error = NULL;
1526 GstElement *gst_error;
1529 gst_message_parse_error (message, &error, &debug);
1530 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1532 g_message ("Element error: %s -- %s\n", error->message, debug);
1534 if (g_str_has_prefix (gst_element_get_name (gst_error),
1535 VIDEO_INPUT_ERROR_PREFIX))
1537 /* Remove the video input and continue */
1538 if (priv->video_input != NULL)
1539 empathy_call_window_remove_video_input (self);
1540 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1544 empathy_call_window_disconnected (self);
1546 g_error_free (error);
1557 empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
1559 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1561 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1563 if (priv->video_preview != NULL)
1565 gtk_widget_hide (priv->self_user_avatar_widget);
1566 gtk_widget_show (priv->video_preview);
1570 if (priv->video_preview != NULL)
1571 gtk_widget_hide (priv->video_preview);
1573 gtk_widget_show (priv->self_user_avatar_widget);
1579 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1580 EmpathyCallWindow *window)
1582 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1584 if (empathy_tp_call_is_receiving_video (call))
1586 gtk_widget_hide (priv->remote_user_avatar_widget);
1587 gtk_widget_show (priv->video_output);
1591 gtk_widget_hide (priv->video_output);
1592 gtk_widget_show (priv->remote_user_avatar_widget);
1595 empathy_call_window_update_self_avatar_visibility (window);
1599 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1601 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1603 g_signal_connect (priv->handler, "conference-added",
1604 G_CALLBACK (empathy_call_window_conference_added_cb), window);
1605 g_signal_connect (priv->handler, "request-resource",
1606 G_CALLBACK (empathy_call_window_request_resource_cb), window);
1607 g_signal_connect (priv->handler, "closed",
1608 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1609 g_signal_connect (priv->handler, "src-pad-added",
1610 G_CALLBACK (empathy_call_window_src_added_cb), window);
1611 g_signal_connect (priv->handler, "sink-pad-added",
1612 G_CALLBACK (empathy_call_window_sink_added_cb), window);
1614 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1618 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1619 EmpathyCallWindow *window)
1621 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1623 if (priv->pipeline != NULL)
1624 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1630 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1633 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1635 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1640 gtk_widget_hide (priv->sidebar);
1641 gtk_widget_hide (menu);
1642 gtk_widget_hide (priv->vbox);
1643 gtk_widget_hide (priv->statusbar);
1644 gtk_widget_hide (priv->toolbar);
1648 if (priv->sidebar_was_visible_before_fs)
1649 gtk_widget_show (priv->sidebar);
1651 gtk_widget_show (menu);
1652 gtk_widget_show (priv->vbox);
1653 gtk_widget_show (priv->statusbar);
1654 gtk_widget_show (priv->toolbar);
1656 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1657 priv->original_height_before_fs);
1662 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1664 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1666 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1667 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1668 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1669 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1670 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1671 priv->video_output, TRUE, TRUE,
1672 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1674 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1675 priv->vbox, TRUE, TRUE,
1676 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1681 empathy_call_window_state_event_cb (GtkWidget *widget,
1682 GdkEventWindowState *event, EmpathyCallWindow *window)
1684 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1686 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1687 gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1691 gboolean sidebar_was_visible;
1692 gint original_width = GTK_WIDGET (window)->allocation.width;
1693 gint original_height = GTK_WIDGET (window)->allocation.height;
1695 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1697 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1698 priv->original_width_before_fs = original_width;
1699 priv->original_height_before_fs = original_height;
1701 if (priv->video_output_motion_handler_id == 0 &&
1702 priv->video_output != NULL)
1704 priv->video_output_motion_handler_id = g_signal_connect (
1705 G_OBJECT (priv->video_output), "motion-notify-event",
1706 G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1711 if (priv->video_output_motion_handler_id != 0)
1713 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1714 priv->video_output_motion_handler_id);
1715 priv->video_output_motion_handler_id = 0;
1719 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1721 show_controls (window, set_fullscreen);
1722 show_borders (window, set_fullscreen);
1723 gtk_action_set_stock_id (priv->menu_fullscreen,
1724 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1725 priv->is_fullscreen = set_fullscreen;
1732 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1733 EmpathyCallWindow *window)
1735 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1737 int w,h, handle_size;
1739 w = GTK_WIDGET (window)->allocation.width;
1740 h = GTK_WIDGET (window)->allocation.height;
1742 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1744 if (gtk_toggle_button_get_active (toggle))
1746 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1747 gtk_widget_show (priv->sidebar);
1748 w += priv->sidebar->allocation.width + handle_size;
1752 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1753 w -= priv->sidebar->allocation.width + handle_size;
1754 gtk_widget_hide (priv->sidebar);
1757 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1760 gtk_window_resize (GTK_WINDOW (window), w, h);
1764 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1767 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1768 EmpathyTpCall *call;
1770 priv->sending_video = send;
1772 /* When we start sending video, we want to show the video preview by
1776 empathy_call_window_setup_video_preview (window);
1777 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1781 g_object_get (priv->handler, "tp-call", &call, NULL);
1782 empathy_tp_call_request_video_stream_direction (call, send);
1783 g_object_unref (call);
1787 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1788 EmpathyCallWindow *window)
1790 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1793 if (!priv->connected)
1796 active = (gtk_toggle_tool_button_get_active (toggle));
1798 if (priv->sending_video == active)
1801 empathy_call_window_set_send_video (window, active);
1802 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1806 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1807 EmpathyCallWindow *window)
1809 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1812 if (!priv->connected)
1815 active = (gtk_toggle_action_get_active (toggle));
1817 if (priv->sending_video == active)
1820 empathy_call_window_set_send_video (window, active);
1821 gtk_toggle_tool_button_set_active (
1822 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1826 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1827 EmpathyCallWindow *window)
1829 gboolean show_preview_toggled;
1830 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1832 show_preview_toggled = gtk_toggle_action_get_active (toggle);
1834 if (show_preview_toggled)
1836 empathy_call_window_setup_video_preview (window);
1837 gtk_widget_show (priv->self_user_output_frame);
1838 empathy_call_window_update_self_avatar_visibility (window);
1842 gtk_widget_hide (priv->self_user_output_frame);
1847 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1848 EmpathyCallWindow *window)
1850 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1853 active = (gtk_toggle_tool_button_get_active (toggle));
1857 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1859 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1863 /* TODO, Instead of setting the input volume to 0 we should probably
1864 * stop sending but this would cause the audio call to drop if both
1865 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1866 * in the future. GNOME #574574
1868 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1870 gtk_adjustment_set_value (priv->audio_input_adj, 0);
1875 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1876 EmpathyCallWindow *window)
1878 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1880 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1885 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1886 EmpathyCallWindow *window)
1888 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1890 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1895 empathy_call_window_hangup_cb (gpointer object,
1896 EmpathyCallWindow *window)
1898 if (empathy_call_window_disconnected (window))
1899 gtk_widget_destroy (GTK_WIDGET (window));
1903 empathy_call_window_restart_call (EmpathyCallWindow *window)
1906 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1908 gtk_widget_destroy (priv->remote_user_output_hbox);
1909 gtk_widget_destroy (priv->self_user_output_hbox);
1911 priv->pipeline = gst_pipeline_new (NULL);
1912 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
1913 gst_bus_add_watch (bus, empathy_call_window_bus_message, window);
1915 empathy_call_window_setup_remote_frame (bus, window);
1916 empathy_call_window_setup_self_frame (bus, window);
1918 g_object_unref (bus);
1920 gtk_widget_show_all (priv->content_hbox);
1922 if (!empathy_call_handler_has_initial_video (priv->handler))
1923 gtk_widget_hide (priv->self_user_output_frame);
1925 empathy_call_window_status_message (window, CONNECTING_STATUS_TEXT);
1926 priv->call_started = TRUE;
1927 empathy_call_handler_start_call (priv->handler);
1928 empathy_call_window_setup_avatars (window, priv->handler);
1929 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1931 gtk_action_set_sensitive (priv->redial, FALSE);
1932 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1936 empathy_call_window_redial_cb (gpointer object,
1937 EmpathyCallWindow *window)
1939 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1941 if (priv->connected)
1942 priv->redialing = TRUE;
1944 empathy_call_handler_stop_call (priv->handler);
1946 if (!priv->connected)
1947 empathy_call_window_restart_call (window);
1951 empathy_call_window_fullscreen_cb (gpointer object,
1952 EmpathyCallWindow *window)
1954 empathy_call_window_fullscreen_toggle (window);
1958 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
1960 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1962 if (priv->is_fullscreen)
1963 gtk_window_unfullscreen (GTK_WINDOW (window));
1965 gtk_window_fullscreen (GTK_WINDOW (window));
1969 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
1970 GdkEventButton *event, EmpathyCallWindow *window)
1972 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1974 empathy_call_window_video_menu_popup (window, event->button);
1982 empathy_call_window_key_press_cb (GtkWidget *video_output,
1983 GdkEventKey *event, EmpathyCallWindow *window)
1985 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1987 if (priv->is_fullscreen && event->keyval == GDK_Escape)
1989 /* Since we are in fullscreen mode, toggling will bring us back to
1991 empathy_call_window_fullscreen_toggle (window);
1999 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2000 GdkEventMotion *event, EmpathyCallWindow *window)
2002 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2004 if (priv->is_fullscreen)
2006 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2013 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2017 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2019 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2021 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2022 button, gtk_get_current_event_time ());
2023 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2027 empathy_call_window_status_message (EmpathyCallWindow *window,
2030 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2032 if (priv->context_id == 0)
2034 priv->context_id = gtk_statusbar_get_context_id (
2035 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2039 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2042 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2047 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2048 gdouble value, EmpathyCallWindow *window)
2050 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2052 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),