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 if (could_reset_pipeline)
1185 g_mutex_lock (priv->lock);
1187 g_timer_stop (priv->timer);
1189 if (priv->timer_id != 0)
1190 g_source_remove (priv->timer_id);
1193 g_mutex_unlock (priv->lock);
1195 empathy_call_window_status_message (self, _("Disconnected"));
1197 gtk_action_set_sensitive (priv->redial, TRUE);
1198 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1199 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1200 gtk_action_set_sensitive (priv->send_video, FALSE);
1201 priv->sending_video = FALSE;
1202 priv->connected = FALSE;
1203 priv->call_started = FALSE;
1205 could_disconnect = TRUE;
1208 return could_disconnect;
1213 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
1215 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1216 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1218 if (empathy_call_window_disconnected (self) && priv->redialing)
1220 empathy_call_window_restart_call (self);
1221 priv->redialing = FALSE;
1225 /* Called with global lock held */
1227 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1229 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1232 if (priv->funnel == NULL)
1236 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1237 (priv->video_output));
1239 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1241 gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1242 gst_bin_add (GST_BIN (priv->pipeline), output);
1244 gst_element_link (priv->funnel, output);
1246 gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1247 gst_element_set_state (output, GST_STATE_PLAYING);
1250 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1255 /* Called with global lock held */
1257 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1259 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1262 if (priv->liveadder == NULL)
1264 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1266 gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1267 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1269 gst_element_link (priv->liveadder, priv->audio_output);
1271 gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1272 gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1275 pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1281 empathy_call_window_update_timer (gpointer user_data)
1283 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1284 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1288 time = g_timer_elapsed (priv->timer, NULL);
1290 /* Translators: number of minutes:seconds the caller has been connected */
1291 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time / 60,
1293 empathy_call_window_status_message (self, str);
1300 empathy_call_window_connected (gpointer user_data)
1302 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1303 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1304 EmpathyTpCall *call;
1306 g_object_get (priv->handler, "tp-call", &call, NULL);
1308 g_signal_connect (call, "notify::video-stream",
1309 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1311 if (empathy_tp_call_has_dtmf (call))
1312 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1314 if (priv->video_input == NULL)
1315 empathy_call_window_set_send_video (self, FALSE);
1317 priv->sending_video = empathy_tp_call_is_sending_video (call);
1319 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1321 || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
1322 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1323 priv->sending_video && priv->video_input != NULL);
1324 gtk_toggle_tool_button_set_active (
1325 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1326 priv->sending_video && priv->video_input != NULL);
1327 gtk_widget_set_sensitive (priv->camera_button, priv->video_input != NULL);
1328 gtk_action_set_sensitive (priv->send_video, priv->video_input != NULL);
1330 gtk_action_set_sensitive (priv->redial, FALSE);
1331 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1333 empathy_call_window_update_avatars_visibility (call, self);
1335 g_object_unref (call);
1337 g_mutex_lock (priv->lock);
1339 priv->timer_id = g_timeout_add_seconds (1,
1340 empathy_call_window_update_timer, self);
1342 g_mutex_unlock (priv->lock);
1344 empathy_call_window_update_timer (self);
1350 /* Called from the streaming thread */
1352 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1353 GstPad *src, guint media_type, gpointer user_data)
1355 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1356 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1360 g_mutex_lock (priv->lock);
1362 if (priv->connected == FALSE)
1364 g_timer_start (priv->timer);
1365 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
1366 priv->connected = TRUE;
1371 case TP_MEDIA_STREAM_TYPE_AUDIO:
1372 pad = empathy_call_window_get_audio_sink_pad (self);
1374 case TP_MEDIA_STREAM_TYPE_VIDEO:
1375 gtk_widget_hide (priv->remote_user_avatar_widget);
1376 gtk_widget_show (priv->video_output);
1377 pad = empathy_call_window_get_video_sink_pad (self);
1380 g_assert_not_reached ();
1383 gst_pad_link (src, pad);
1384 gst_object_unref (pad);
1386 g_mutex_unlock (priv->lock);
1389 /* Called from the streaming thread */
1391 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1392 GstPad *sink, guint media_type, gpointer user_data)
1394 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1395 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1400 case TP_MEDIA_STREAM_TYPE_AUDIO:
1401 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1403 pad = gst_element_get_static_pad (priv->audio_input, "src");
1404 gst_pad_link (pad, sink);
1406 gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1408 case TP_MEDIA_STREAM_TYPE_VIDEO:
1409 if (priv->video_input != NULL)
1411 EmpathyTpCall *call;
1412 g_object_get (priv->handler, "tp-call", &call, NULL);
1414 if (empathy_tp_call_is_sending_video (call))
1416 empathy_call_window_setup_video_preview (self);
1418 gtk_toggle_action_set_active (
1419 GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1421 if (priv->video_preview != NULL)
1422 gtk_widget_show (priv->video_preview);
1423 gtk_widget_hide (priv->self_user_avatar_widget);
1426 g_object_unref (call);
1428 if (priv->video_tee != NULL)
1430 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1431 gst_pad_link (pad, sink);
1436 g_assert_not_reached ();
1442 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1444 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1445 GstElement *preview;
1447 preview = empathy_video_widget_get_element (
1448 EMPATHY_VIDEO_WIDGET (priv->video_preview));
1450 gst_element_set_state (priv->video_input, GST_STATE_NULL);
1451 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1452 gst_element_set_state (preview, GST_STATE_NULL);
1454 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1455 priv->video_tee, preview, NULL);
1457 g_object_unref (priv->video_input);
1458 priv->video_input = NULL;
1459 g_object_unref (priv->video_tee);
1460 priv->video_tee = NULL;
1461 gtk_widget_destroy (priv->video_preview);
1462 priv->video_preview = NULL;
1464 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1465 gtk_toggle_tool_button_set_active (
1466 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1467 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1468 gtk_action_set_sensitive (priv->send_video, FALSE);
1470 gtk_widget_show (priv->self_user_avatar_widget);
1475 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1478 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1479 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1482 empathy_call_handler_bus_message (priv->handler, bus, message);
1484 switch (GST_MESSAGE_TYPE (message))
1486 case GST_MESSAGE_STATE_CHANGED:
1487 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1489 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1490 if (newstate == GST_STATE_PAUSED)
1491 empathy_call_window_setup_video_input (self);
1493 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1494 !priv->call_started)
1496 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1497 if (newstate == GST_STATE_PAUSED)
1499 priv->call_started = TRUE;
1500 empathy_call_handler_start_call (priv->handler);
1501 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1505 case GST_MESSAGE_ERROR:
1507 GError *error = NULL;
1508 GstElement *gst_error;
1511 gst_message_parse_error (message, &error, &debug);
1512 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1514 g_message ("Element error: %s -- %s\n", error->message, debug);
1516 if (g_str_has_prefix (gst_element_get_name (gst_error),
1517 VIDEO_INPUT_ERROR_PREFIX))
1519 /* Remove the video input and continue */
1520 if (priv->video_input != NULL)
1521 empathy_call_window_remove_video_input (self);
1522 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1526 empathy_call_window_disconnected (self);
1528 g_error_free (error);
1539 empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
1541 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1543 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1545 if (priv->video_preview != NULL)
1547 gtk_widget_hide (priv->self_user_avatar_widget);
1548 gtk_widget_show (priv->video_preview);
1552 if (priv->video_preview != NULL)
1553 gtk_widget_hide (priv->video_preview);
1555 gtk_widget_show (priv->self_user_avatar_widget);
1561 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1562 EmpathyCallWindow *window)
1564 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1566 if (empathy_tp_call_is_receiving_video (call))
1568 gtk_widget_hide (priv->remote_user_avatar_widget);
1569 gtk_widget_show (priv->video_output);
1573 gtk_widget_hide (priv->video_output);
1574 gtk_widget_show (priv->remote_user_avatar_widget);
1577 empathy_call_window_update_self_avatar_visibility (window);
1581 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1583 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1585 g_signal_connect (priv->handler, "conference-added",
1586 G_CALLBACK (empathy_call_window_conference_added_cb), window);
1587 g_signal_connect (priv->handler, "request-resource",
1588 G_CALLBACK (empathy_call_window_request_resource_cb), window);
1589 g_signal_connect (priv->handler, "closed",
1590 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1591 g_signal_connect (priv->handler, "src-pad-added",
1592 G_CALLBACK (empathy_call_window_src_added_cb), window);
1593 g_signal_connect (priv->handler, "sink-pad-added",
1594 G_CALLBACK (empathy_call_window_sink_added_cb), window);
1596 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1600 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1601 EmpathyCallWindow *window)
1603 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1605 if (priv->pipeline != NULL)
1606 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1612 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1615 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1617 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1622 gtk_widget_hide (priv->sidebar);
1623 gtk_widget_hide (menu);
1624 gtk_widget_hide (priv->vbox);
1625 gtk_widget_hide (priv->statusbar);
1626 gtk_widget_hide (priv->toolbar);
1630 if (priv->sidebar_was_visible_before_fs)
1631 gtk_widget_show (priv->sidebar);
1633 gtk_widget_show (menu);
1634 gtk_widget_show (priv->vbox);
1635 gtk_widget_show (priv->statusbar);
1636 gtk_widget_show (priv->toolbar);
1638 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1639 priv->original_height_before_fs);
1644 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1646 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1648 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1649 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1650 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1651 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1652 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1653 priv->video_output, TRUE, TRUE,
1654 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1656 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1657 priv->vbox, TRUE, TRUE,
1658 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1663 empathy_call_window_state_event_cb (GtkWidget *widget,
1664 GdkEventWindowState *event, EmpathyCallWindow *window)
1666 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1668 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1669 gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1673 gboolean sidebar_was_visible;
1674 gint original_width = GTK_WIDGET (window)->allocation.width;
1675 gint original_height = GTK_WIDGET (window)->allocation.height;
1677 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1679 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1680 priv->original_width_before_fs = original_width;
1681 priv->original_height_before_fs = original_height;
1683 if (priv->video_output_motion_handler_id == 0 &&
1684 priv->video_output != NULL)
1686 priv->video_output_motion_handler_id = g_signal_connect (
1687 G_OBJECT (priv->video_output), "motion-notify-event",
1688 G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1693 if (priv->video_output_motion_handler_id != 0)
1695 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1696 priv->video_output_motion_handler_id);
1697 priv->video_output_motion_handler_id = 0;
1701 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1703 show_controls (window, set_fullscreen);
1704 show_borders (window, set_fullscreen);
1705 gtk_action_set_stock_id (priv->menu_fullscreen,
1706 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1707 priv->is_fullscreen = set_fullscreen;
1714 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1715 EmpathyCallWindow *window)
1717 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1719 int w,h, handle_size;
1721 w = GTK_WIDGET (window)->allocation.width;
1722 h = GTK_WIDGET (window)->allocation.height;
1724 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1726 if (gtk_toggle_button_get_active (toggle))
1728 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1729 gtk_widget_show (priv->sidebar);
1730 w += priv->sidebar->allocation.width + handle_size;
1734 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1735 w -= priv->sidebar->allocation.width + handle_size;
1736 gtk_widget_hide (priv->sidebar);
1739 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1742 gtk_window_resize (GTK_WINDOW (window), w, h);
1746 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1749 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1750 EmpathyTpCall *call;
1752 priv->sending_video = send;
1754 /* When we start sending video, we want to show the video preview by
1758 empathy_call_window_setup_video_preview (window);
1759 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1763 g_object_get (priv->handler, "tp-call", &call, NULL);
1764 empathy_tp_call_request_video_stream_direction (call, send);
1765 g_object_unref (call);
1769 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1770 EmpathyCallWindow *window)
1772 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1775 active = (gtk_toggle_tool_button_get_active (toggle));
1777 if (priv->sending_video == active)
1780 empathy_call_window_set_send_video (window, active);
1781 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1785 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1786 EmpathyCallWindow *window)
1788 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1791 active = (gtk_toggle_action_get_active (toggle));
1793 if (priv->sending_video == active)
1796 empathy_call_window_set_send_video (window, active);
1797 gtk_toggle_tool_button_set_active (
1798 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1802 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1803 EmpathyCallWindow *window)
1805 gboolean show_preview_toggled;
1806 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1808 show_preview_toggled = gtk_toggle_action_get_active (toggle);
1810 if (show_preview_toggled)
1812 empathy_call_window_setup_video_preview (window);
1813 gtk_widget_show (priv->self_user_output_frame);
1814 empathy_call_window_update_self_avatar_visibility (window);
1818 gtk_widget_hide (priv->self_user_output_frame);
1823 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1824 EmpathyCallWindow *window)
1826 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1829 active = (gtk_toggle_tool_button_get_active (toggle));
1833 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1835 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1839 /* TODO, Instead of setting the input volume to 0 we should probably
1840 * stop sending but this would cause the audio call to drop if both
1841 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1842 * in the future. GNOME #574574
1844 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1846 gtk_adjustment_set_value (priv->audio_input_adj, 0);
1851 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1852 EmpathyCallWindow *window)
1854 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1856 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1861 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1862 EmpathyCallWindow *window)
1864 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1866 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1871 empathy_call_window_hangup_cb (gpointer object,
1872 EmpathyCallWindow *window)
1874 if (empathy_call_window_disconnected (window))
1875 gtk_widget_destroy (GTK_WIDGET (window));
1879 empathy_call_window_restart_call (EmpathyCallWindow *window)
1882 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1884 gtk_widget_destroy (priv->remote_user_output_hbox);
1885 gtk_widget_destroy (priv->self_user_output_hbox);
1887 priv->pipeline = gst_pipeline_new (NULL);
1888 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
1889 gst_bus_add_watch (bus, empathy_call_window_bus_message, window);
1891 empathy_call_window_setup_remote_frame (bus, window);
1892 empathy_call_window_setup_self_frame (bus, window);
1894 g_object_unref (bus);
1896 gtk_widget_show_all (priv->content_hbox);
1898 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1899 gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
1901 empathy_call_window_status_message (window, CONNECTING_STATUS_TEXT);
1902 priv->call_started = TRUE;
1903 empathy_call_handler_start_call (priv->handler);
1904 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1906 gtk_action_set_sensitive (priv->redial, FALSE);
1907 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1911 empathy_call_window_redial_cb (gpointer object,
1912 EmpathyCallWindow *window)
1914 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1916 if (priv->connected)
1917 priv->redialing = TRUE;
1919 empathy_call_handler_stop_call (priv->handler);
1921 if (!priv->connected)
1922 empathy_call_window_restart_call (window);
1926 empathy_call_window_fullscreen_cb (gpointer object,
1927 EmpathyCallWindow *window)
1929 empathy_call_window_fullscreen_toggle (window);
1933 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
1935 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1937 if (priv->is_fullscreen)
1938 gtk_window_unfullscreen (GTK_WINDOW (window));
1940 gtk_window_fullscreen (GTK_WINDOW (window));
1944 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
1945 GdkEventButton *event, EmpathyCallWindow *window)
1947 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1949 empathy_call_window_video_menu_popup (window, event->button);
1957 empathy_call_window_key_press_cb (GtkWidget *video_output,
1958 GdkEventKey *event, EmpathyCallWindow *window)
1960 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1962 if (priv->is_fullscreen && event->keyval == GDK_Escape)
1964 /* Since we are in fullscreen mode, toggling will bring us back to
1966 empathy_call_window_fullscreen_toggle (window);
1974 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
1975 GdkEventMotion *event, EmpathyCallWindow *window)
1977 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1979 if (priv->is_fullscreen)
1981 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
1988 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
1992 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1994 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1996 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1997 button, gtk_get_current_event_time ());
1998 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2002 empathy_call_window_status_message (EmpathyCallWindow *window,
2005 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2007 if (priv->context_id == 0)
2009 priv->context_id = gtk_statusbar_get_context_id (
2010 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2014 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2017 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2022 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2023 gdouble value, EmpathyCallWindow *window)
2025 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2027 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),