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 if (priv->video_preview != NULL)
617 /* Since the video preview and the video tee are initialized and freed
618 at the same time, if one is initialized, then the other one should
620 g_assert (priv->video_tee != NULL);
625 GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
627 priv->video_tee = gst_element_factory_make ("tee", NULL);
628 gst_object_ref (priv->video_tee);
629 gst_object_sink (priv->video_tee);
631 priv->video_preview = empathy_video_widget_new_with_size (bus,
632 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
633 g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
634 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
635 priv->video_preview, TRUE, TRUE, 0);
637 preview = empathy_video_widget_get_element (
638 EMPATHY_VIDEO_WIDGET (priv->video_preview));
639 gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
640 priv->video_tee, preview, NULL);
641 gst_element_link_many (priv->video_input, priv->video_tee,
644 g_object_unref (bus);
646 gst_element_set_state (preview, GST_STATE_PLAYING);
647 gst_element_set_state (priv->video_input, GST_STATE_PLAYING);
648 gst_element_set_state (priv->video_tee, GST_STATE_PLAYING);
652 empathy_call_window_init (EmpathyCallWindow *self)
654 EmpathyCallWindowPriv *priv = GET_PRIV (self);
663 filename = empathy_file_lookup ("empathy-call-window.ui", "src");
664 gui = empathy_builder_get_file (filename,
665 "call_window_vbox", &top_vbox,
667 "statusbar", &priv->statusbar,
668 "redial", &priv->redial_button,
669 "microphone", &priv->mic_button,
670 "camera", &priv->camera_button,
671 "toolbar", &priv->toolbar,
672 "send_video", &priv->send_video,
673 "menuredial", &priv->redial,
674 "show_preview", &priv->show_preview,
675 "ui_manager", &priv->ui_manager,
676 "menufullscreen", &priv->menu_fullscreen,
679 empathy_builder_connect (gui, self,
680 "menuhangup", "activate", empathy_call_window_hangup_cb,
681 "hangup", "clicked", empathy_call_window_hangup_cb,
682 "menuredial", "activate", empathy_call_window_redial_cb,
683 "redial", "clicked", empathy_call_window_redial_cb,
684 "microphone", "toggled", empathy_call_window_mic_toggled_cb,
685 "camera", "toggled", empathy_call_window_camera_toggled_cb,
686 "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
687 "show_preview", "toggled", empathy_call_window_show_preview_toggled_cb,
688 "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
691 priv->lock = g_mutex_new ();
693 gtk_container_add (GTK_CONTAINER (self), top_vbox);
695 empathy_call_window_setup_toolbar (self);
697 priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
698 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
699 CONTENT_HBOX_BORDER_WIDTH);
700 gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
702 priv->pipeline = gst_pipeline_new (NULL);
703 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
704 gst_bus_add_watch (bus, empathy_call_window_bus_message, self);
706 priv->remote_user_output_frame = gtk_frame_new (NULL);
707 gtk_widget_set_size_request (priv->remote_user_output_frame,
708 EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
709 gtk_box_pack_start (GTK_BOX (priv->content_hbox),
710 priv->remote_user_output_frame, TRUE, TRUE,
711 CONTENT_HBOX_CHILDREN_PACKING_PADDING);
712 empathy_call_window_setup_remote_frame (bus, self);
714 priv->self_user_output_frame = gtk_frame_new (NULL);
715 gtk_widget_set_size_request (priv->self_user_output_frame,
716 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
718 priv->vbox = gtk_vbox_new (FALSE, 3);
719 gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
720 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
721 gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame, FALSE,
723 empathy_call_window_setup_self_frame (bus, self);
725 g_object_unref (bus);
727 priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
728 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
729 g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
730 G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
732 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
734 h = gtk_hbox_new (FALSE, 3);
735 gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
736 gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
738 priv->sidebar = empathy_sidebar_new ();
739 g_signal_connect (G_OBJECT (priv->sidebar),
740 "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
741 g_signal_connect (G_OBJECT (priv->sidebar),
742 "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
743 gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
745 priv->dtmf_panel = empathy_call_window_create_dtmf (self);
746 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
749 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
751 page = empathy_call_window_create_audio_input (self);
752 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
755 page = empathy_call_window_create_video_input (self);
756 empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
759 gtk_widget_show_all (top_vbox);
761 gtk_widget_hide (priv->sidebar);
763 priv->fullscreen = empathy_call_window_fullscreen_new (self);
764 empathy_call_window_fullscreen_set_video_widget (priv->fullscreen, priv->video_output);
765 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
766 "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
768 g_signal_connect (G_OBJECT (self), "realize",
769 G_CALLBACK (empathy_call_window_realized_cb), self);
771 g_signal_connect (G_OBJECT (self), "delete-event",
772 G_CALLBACK (empathy_call_window_delete_cb), self);
774 g_signal_connect (G_OBJECT (self), "window-state-event",
775 G_CALLBACK (empathy_call_window_state_event_cb), self);
777 g_signal_connect (G_OBJECT (self), "key-press-event",
778 G_CALLBACK (empathy_call_window_key_press_cb), self);
780 empathy_call_window_status_message (self, CONNECTING_STATUS_TEXT);
782 priv->timer = g_timer_new ();
784 g_object_ref (priv->ui_manager);
785 g_object_unref (gui);
789 /* Instead of specifying a width and a height, we specify only one size. That's
790 because we want a square avatar icon. */
792 init_contact_avatar_with_size (EmpathyContact *contact, GtkWidget *image_widget,
796 GdkPixbuf *pixbuf_avatar = NULL;
800 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
804 if (pixbuf_avatar == NULL)
806 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
810 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
814 set_window_title (EmpathyCallWindow *self)
816 EmpathyCallWindowPriv *priv = GET_PRIV (self);
819 tmp = g_strdup_printf (_("Call with %s"),
820 empathy_contact_get_name (priv->contact));
821 gtk_window_set_title (GTK_WINDOW (self), tmp);
826 contact_name_changed_cb (EmpathyContact *contact,
827 GParamSpec *pspec, EmpathyCallWindow *self)
829 set_window_title (self);
833 contact_avatar_changed_cb (EmpathyContact *contact,
834 GParamSpec *pspec, GtkWidget *avatar_widget)
836 init_contact_avatar_with_size (contact, avatar_widget,
837 avatar_widget->allocation.height);
841 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
842 EmpathyContact *contact, const GError *error, gpointer user_data,
843 GObject *weak_object)
845 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
846 EmpathyCallWindowPriv *priv = GET_PRIV (self);
848 init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
849 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
851 g_signal_connect (contact, "notify::avatar",
852 G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
856 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
857 EmpathyCallHandler *handler)
859 EmpathyCallWindowPriv *priv = GET_PRIV (self);
861 g_object_get (handler, "contact", &(priv->contact), NULL);
863 if (priv->contact != NULL)
865 TpConnection *connection;
866 EmpathyTpContactFactory *factory;
868 set_window_title (self);
870 g_signal_connect (priv->contact, "notify::name",
871 G_CALLBACK (contact_name_changed_cb), self);
872 g_signal_connect (priv->contact, "notify::avatar",
873 G_CALLBACK (contact_avatar_changed_cb),
874 priv->remote_user_avatar_widget);
876 /* Retreiving the self avatar */
877 connection = empathy_contact_get_connection (priv->contact);
878 factory = empathy_tp_contact_factory_dup_singleton (connection);
879 empathy_tp_contact_factory_get_from_handle (factory,
880 tp_connection_get_self_handle (connection),
881 empathy_call_window_got_self_contact_cb, self, NULL, NULL);
883 g_object_unref (factory);
887 g_warning ("call handler doesn't have a contact");
888 gtk_window_set_title (GTK_WINDOW (self), _("Call"));
890 /* Since we can't access the remote contact, we can't get a connection
891 to it and can't get the self contact (and its avatar). This means
892 that we have to manually set the self avatar. */
893 init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
894 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
897 init_contact_avatar_with_size (priv->contact,
898 priv->remote_user_avatar_widget, MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
899 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
901 /* The remote avatar is shown by default and will be hidden when we receive
902 video from the remote side. */
903 gtk_widget_hide (priv->video_output);
904 gtk_widget_show (priv->remote_user_avatar_widget);
908 empathy_call_window_setup_video_preview_visibility (EmpathyCallWindow *self,
909 EmpathyCallHandler *handler)
911 EmpathyCallWindowPriv *priv = GET_PRIV (self);
912 gboolean initial_video = empathy_call_handler_has_initial_video (priv->handler);
916 empathy_call_window_setup_video_preview (self);
917 gtk_widget_hide (priv->self_user_avatar_widget);
919 if (priv->video_preview != NULL)
920 gtk_widget_show (priv->video_preview);
924 gtk_widget_show (priv->self_user_avatar_widget);
927 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
932 empathy_call_window_constructed (GObject *object)
934 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
935 EmpathyCallWindowPriv *priv = GET_PRIV (self);
937 g_assert (priv->handler != NULL);
938 empathy_call_window_setup_avatars (self, priv->handler);
939 empathy_call_window_setup_video_preview_visibility (self, priv->handler);
942 static void empathy_call_window_dispose (GObject *object);
943 static void empathy_call_window_finalize (GObject *object);
946 empathy_call_window_set_property (GObject *object,
947 guint property_id, const GValue *value, GParamSpec *pspec)
949 EmpathyCallWindowPriv *priv = GET_PRIV (object);
953 case PROP_CALL_HANDLER:
954 priv->handler = g_value_dup_object (value);
957 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
962 empathy_call_window_get_property (GObject *object,
963 guint property_id, GValue *value, GParamSpec *pspec)
965 EmpathyCallWindowPriv *priv = GET_PRIV (object);
969 case PROP_CALL_HANDLER:
970 g_value_set_object (value, priv->handler);
973 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
978 empathy_call_window_class_init (
979 EmpathyCallWindowClass *empathy_call_window_class)
981 GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
982 GParamSpec *param_spec;
984 g_type_class_add_private (empathy_call_window_class,
985 sizeof (EmpathyCallWindowPriv));
987 object_class->constructed = empathy_call_window_constructed;
988 object_class->set_property = empathy_call_window_set_property;
989 object_class->get_property = empathy_call_window_get_property;
991 object_class->dispose = empathy_call_window_dispose;
992 object_class->finalize = empathy_call_window_finalize;
994 param_spec = g_param_spec_object ("handler",
995 "handler", "The call handler",
996 EMPATHY_TYPE_CALL_HANDLER,
997 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
998 g_object_class_install_property (object_class,
999 PROP_CALL_HANDLER, param_spec);
1004 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1005 GParamSpec *property, EmpathyCallWindow *self)
1007 empathy_call_window_update_avatars_visibility (call, self);
1011 empathy_call_window_dispose (GObject *object)
1013 EmpathyTpCall *call;
1014 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1015 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1017 if (priv->dispose_has_run)
1020 priv->dispose_has_run = TRUE;
1022 g_object_get (priv->handler, "tp-call", &call, NULL);
1026 g_signal_handlers_disconnect_by_func (call,
1027 empathy_call_window_video_stream_changed_cb, object);
1030 g_object_unref (call);
1032 if (priv->handler != NULL)
1033 g_object_unref (priv->handler);
1035 priv->handler = NULL;
1037 if (priv->pipeline != NULL)
1038 g_object_unref (priv->pipeline);
1039 priv->pipeline = NULL;
1041 if (priv->video_input != NULL)
1042 g_object_unref (priv->video_input);
1043 priv->video_input = NULL;
1045 if (priv->audio_input != NULL)
1046 g_object_unref (priv->audio_input);
1047 priv->audio_input = NULL;
1049 if (priv->audio_output != NULL)
1050 g_object_unref (priv->audio_output);
1051 priv->audio_output = NULL;
1053 if (priv->video_tee != NULL)
1054 g_object_unref (priv->video_tee);
1055 priv->video_tee = NULL;
1057 if (priv->timer_id != 0)
1058 g_source_remove (priv->timer_id);
1061 if (priv->ui_manager != NULL)
1062 g_object_unref (priv->ui_manager);
1063 priv->ui_manager = NULL;
1065 if (priv->contact != NULL)
1067 g_signal_handlers_disconnect_by_func (priv->contact,
1068 contact_name_changed_cb, self);
1069 g_object_unref (priv->contact);
1070 priv->contact = NULL;
1073 /* release any references held by the object here */
1074 if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1075 G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1079 empathy_call_window_finalize (GObject *object)
1081 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1082 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1084 if (priv->video_output_motion_handler_id != 0)
1086 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1087 priv->video_output_motion_handler_id);
1088 priv->video_output_motion_handler_id = 0;
1091 /* free any data held directly by the object here */
1092 g_mutex_free (priv->lock);
1094 g_timer_destroy (priv->timer);
1096 G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1101 empathy_call_window_new (EmpathyCallHandler *handler)
1103 return EMPATHY_CALL_WINDOW (
1104 g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1108 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1109 GstElement *conference, gpointer user_data)
1111 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1112 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1114 gst_bin_add (GST_BIN (priv->pipeline), conference);
1116 gst_element_set_state (conference, GST_STATE_PLAYING);
1120 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1121 FsMediaType type, FsStreamDirection direction, gpointer user_data)
1123 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1124 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1126 if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1129 if (direction == FS_DIRECTION_RECV)
1132 /* video and direction is send */
1133 return priv->video_input != NULL;
1137 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1139 GstStateChangeReturn state_change_return;
1140 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1142 if (priv->pipeline == NULL)
1145 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1147 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1148 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1150 if (priv->pipeline != NULL)
1151 g_object_unref (priv->pipeline);
1152 priv->pipeline = NULL;
1154 if (priv->video_input != NULL)
1155 g_object_unref (priv->video_input);
1156 priv->video_input = NULL;
1158 if (priv->audio_input != NULL)
1159 g_object_unref (priv->audio_input);
1160 priv->audio_input = NULL;
1162 if (priv->audio_output != NULL)
1163 g_object_unref (priv->audio_output);
1164 priv->audio_output = NULL;
1166 if (priv->video_tee != NULL)
1167 g_object_unref (priv->video_tee);
1168 priv->video_tee = NULL;
1170 if (priv->video_preview != NULL)
1171 gtk_widget_destroy (priv->video_preview);
1172 priv->video_preview = NULL;
1174 priv->liveadder = NULL;
1175 priv->funnel = NULL;
1181 g_message ("Error: could not destroy pipeline. Closing call window");
1182 gtk_widget_destroy (GTK_WIDGET (self));
1189 empathy_call_window_disconnected (EmpathyCallWindow *self)
1191 gboolean could_disconnect = FALSE;
1192 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1193 gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1195 if (could_reset_pipeline)
1197 g_mutex_lock (priv->lock);
1199 g_timer_stop (priv->timer);
1201 if (priv->timer_id != 0)
1202 g_source_remove (priv->timer_id);
1205 g_mutex_unlock (priv->lock);
1207 empathy_call_window_status_message (self, _("Disconnected"));
1209 gtk_action_set_sensitive (priv->redial, TRUE);
1210 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1211 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1212 gtk_action_set_sensitive (priv->send_video, FALSE);
1213 priv->sending_video = FALSE;
1214 priv->connected = FALSE;
1215 priv->call_started = FALSE;
1217 could_disconnect = TRUE;
1220 return could_disconnect;
1225 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
1227 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1228 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1230 if (empathy_call_window_disconnected (self) && priv->redialing)
1232 empathy_call_window_restart_call (self);
1233 priv->redialing = FALSE;
1237 /* Called with global lock held */
1239 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1241 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1244 if (priv->funnel == NULL)
1248 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1249 (priv->video_output));
1251 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1253 gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1254 gst_bin_add (GST_BIN (priv->pipeline), output);
1256 gst_element_link (priv->funnel, output);
1258 gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1259 gst_element_set_state (output, GST_STATE_PLAYING);
1262 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1267 /* Called with global lock held */
1269 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1271 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1274 if (priv->liveadder == NULL)
1276 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1278 gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1279 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1281 gst_element_link (priv->liveadder, priv->audio_output);
1283 gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1284 gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1287 pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1293 empathy_call_window_update_timer (gpointer user_data)
1295 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1296 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1300 time = g_timer_elapsed (priv->timer, NULL);
1302 /* Translators: number of minutes:seconds the caller has been connected */
1303 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time / 60,
1305 empathy_call_window_status_message (self, str);
1312 empathy_call_window_connected (gpointer user_data)
1314 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1315 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1316 EmpathyTpCall *call;
1318 g_object_get (priv->handler, "tp-call", &call, NULL);
1320 g_signal_connect (call, "notify::video-stream",
1321 G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1323 if (empathy_tp_call_has_dtmf (call))
1324 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1326 if (priv->video_input == NULL)
1327 empathy_call_window_set_send_video (self, FALSE);
1329 priv->sending_video = empathy_tp_call_is_sending_video (call);
1331 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1332 priv->sending_video);
1333 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1334 priv->sending_video && priv->video_input != NULL);
1335 gtk_toggle_tool_button_set_active (
1336 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1337 priv->sending_video && priv->video_input != NULL);
1338 gtk_widget_set_sensitive (priv->camera_button, priv->video_input != NULL);
1339 gtk_action_set_sensitive (priv->send_video, priv->video_input != NULL);
1341 gtk_action_set_sensitive (priv->redial, FALSE);
1342 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1344 empathy_call_window_update_avatars_visibility (call, self);
1346 g_object_unref (call);
1348 g_mutex_lock (priv->lock);
1350 priv->timer_id = g_timeout_add_seconds (1,
1351 empathy_call_window_update_timer, self);
1353 g_mutex_unlock (priv->lock);
1355 empathy_call_window_update_timer (self);
1361 /* Called from the streaming thread */
1363 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1364 GstPad *src, guint media_type, gpointer user_data)
1366 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1367 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1371 g_mutex_lock (priv->lock);
1373 if (priv->connected == FALSE)
1375 g_timer_start (priv->timer);
1376 priv->timer_id = g_idle_add (empathy_call_window_connected, self);
1377 priv->connected = TRUE;
1382 case TP_MEDIA_STREAM_TYPE_AUDIO:
1383 pad = empathy_call_window_get_audio_sink_pad (self);
1385 case TP_MEDIA_STREAM_TYPE_VIDEO:
1386 gtk_widget_hide (priv->remote_user_avatar_widget);
1387 gtk_widget_show (priv->video_output);
1388 pad = empathy_call_window_get_video_sink_pad (self);
1391 g_assert_not_reached ();
1394 gst_pad_link (src, pad);
1395 gst_object_unref (pad);
1397 g_mutex_unlock (priv->lock);
1400 /* Called from the streaming thread */
1402 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1403 GstPad *sink, guint media_type, gpointer user_data)
1405 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1406 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1411 case TP_MEDIA_STREAM_TYPE_AUDIO:
1412 gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1414 pad = gst_element_get_static_pad (priv->audio_input, "src");
1415 gst_pad_link (pad, sink);
1417 gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1419 case TP_MEDIA_STREAM_TYPE_VIDEO:
1420 if (priv->video_input != NULL)
1422 EmpathyTpCall *call;
1423 g_object_get (priv->handler, "tp-call", &call, NULL);
1425 if (empathy_tp_call_is_sending_video (call))
1427 empathy_call_window_setup_video_preview (self);
1429 gtk_toggle_action_set_active (
1430 GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1432 if (priv->video_preview != NULL)
1433 gtk_widget_show (priv->video_preview);
1434 gtk_widget_hide (priv->self_user_avatar_widget);
1437 g_object_unref (call);
1439 if (priv->video_tee != NULL)
1441 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1442 gst_pad_link (pad, sink);
1447 g_assert_not_reached ();
1453 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1455 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1456 GstElement *preview;
1458 preview = empathy_video_widget_get_element (
1459 EMPATHY_VIDEO_WIDGET (priv->video_preview));
1461 gst_element_set_state (priv->video_input, GST_STATE_NULL);
1462 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1463 gst_element_set_state (preview, GST_STATE_NULL);
1465 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1466 priv->video_tee, preview, NULL);
1468 g_object_unref (priv->video_input);
1469 priv->video_input = NULL;
1470 g_object_unref (priv->video_tee);
1471 priv->video_tee = NULL;
1472 gtk_widget_destroy (priv->video_preview);
1473 priv->video_preview = NULL;
1475 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1476 gtk_toggle_tool_button_set_active (
1477 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1478 gtk_widget_set_sensitive (priv->camera_button, FALSE);
1479 gtk_action_set_sensitive (priv->send_video, FALSE);
1481 gtk_widget_show (priv->self_user_avatar_widget);
1486 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1489 EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1490 EmpathyCallWindowPriv *priv = GET_PRIV (self);
1493 empathy_call_handler_bus_message (priv->handler, bus, message);
1495 switch (GST_MESSAGE_TYPE (message))
1497 case GST_MESSAGE_STATE_CHANGED:
1498 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1500 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1501 if (newstate == GST_STATE_PAUSED)
1502 empathy_call_window_setup_video_input (self);
1504 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1505 !priv->call_started)
1507 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1508 if (newstate == GST_STATE_PAUSED)
1510 priv->call_started = TRUE;
1511 empathy_call_handler_start_call (priv->handler);
1512 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1516 case GST_MESSAGE_ERROR:
1518 GError *error = NULL;
1519 GstElement *gst_error;
1522 gst_message_parse_error (message, &error, &debug);
1523 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1525 g_message ("Element error: %s -- %s\n", error->message, debug);
1527 if (g_str_has_prefix (gst_element_get_name (gst_error),
1528 VIDEO_INPUT_ERROR_PREFIX))
1530 /* Remove the video input and continue */
1531 if (priv->video_input != NULL)
1532 empathy_call_window_remove_video_input (self);
1533 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1537 empathy_call_window_disconnected (self);
1539 g_error_free (error);
1550 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1551 EmpathyCallWindow *window)
1553 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1555 if (empathy_tp_call_is_receiving_video (call))
1557 gtk_widget_hide (priv->remote_user_avatar_widget);
1558 gtk_widget_show (priv->video_output);
1562 gtk_widget_hide (priv->video_output);
1563 gtk_widget_show (priv->remote_user_avatar_widget);
1566 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1568 if (priv->video_preview != NULL
1569 && empathy_tp_call_is_sending_video (call))
1571 gtk_widget_hide (priv->self_user_avatar_widget);
1572 gtk_widget_show (priv->video_preview);
1576 if (priv->video_preview != NULL)
1577 gtk_widget_hide (priv->video_preview);
1579 gtk_widget_show (priv->self_user_avatar_widget);
1585 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1587 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1589 g_signal_connect (priv->handler, "conference-added",
1590 G_CALLBACK (empathy_call_window_conference_added_cb), window);
1591 g_signal_connect (priv->handler, "request-resource",
1592 G_CALLBACK (empathy_call_window_request_resource_cb), window);
1593 g_signal_connect (priv->handler, "closed",
1594 G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1595 g_signal_connect (priv->handler, "src-pad-added",
1596 G_CALLBACK (empathy_call_window_src_added_cb), window);
1597 g_signal_connect (priv->handler, "sink-pad-added",
1598 G_CALLBACK (empathy_call_window_sink_added_cb), window);
1600 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1604 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1605 EmpathyCallWindow *window)
1607 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1609 if (priv->pipeline != NULL)
1610 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1616 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1619 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1621 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1626 gtk_widget_hide (priv->sidebar);
1627 gtk_widget_hide (menu);
1628 gtk_widget_hide (priv->vbox);
1629 gtk_widget_hide (priv->statusbar);
1630 gtk_widget_hide (priv->toolbar);
1634 if (priv->sidebar_was_visible_before_fs)
1635 gtk_widget_show (priv->sidebar);
1637 gtk_widget_show (menu);
1638 gtk_widget_show (priv->vbox);
1639 gtk_widget_show (priv->statusbar);
1640 gtk_widget_show (priv->toolbar);
1642 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1643 priv->original_height_before_fs);
1648 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1650 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1652 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1653 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1654 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1655 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1656 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1657 priv->video_output, TRUE, TRUE,
1658 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1660 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1661 priv->vbox, TRUE, TRUE,
1662 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1667 empathy_call_window_state_event_cb (GtkWidget *widget,
1668 GdkEventWindowState *event, EmpathyCallWindow *window)
1670 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1672 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1673 gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1677 gboolean sidebar_was_visible;
1678 gint original_width = GTK_WIDGET (window)->allocation.width;
1679 gint original_height = GTK_WIDGET (window)->allocation.height;
1681 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1683 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1684 priv->original_width_before_fs = original_width;
1685 priv->original_height_before_fs = original_height;
1687 if (priv->video_output_motion_handler_id == 0 &&
1688 priv->video_output != NULL)
1690 priv->video_output_motion_handler_id = g_signal_connect (
1691 G_OBJECT (priv->video_output), "motion-notify-event",
1692 G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1697 if (priv->video_output_motion_handler_id != 0)
1699 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1700 priv->video_output_motion_handler_id);
1701 priv->video_output_motion_handler_id = 0;
1705 empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1707 show_controls (window, set_fullscreen);
1708 show_borders (window, set_fullscreen);
1709 gtk_action_set_stock_id (priv->menu_fullscreen,
1710 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1711 priv->is_fullscreen = set_fullscreen;
1718 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1719 EmpathyCallWindow *window)
1721 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1723 int w,h, handle_size;
1725 w = GTK_WIDGET (window)->allocation.width;
1726 h = GTK_WIDGET (window)->allocation.height;
1728 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1730 if (gtk_toggle_button_get_active (toggle))
1732 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1733 gtk_widget_show (priv->sidebar);
1734 w += priv->sidebar->allocation.width + handle_size;
1738 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1739 w -= priv->sidebar->allocation.width + handle_size;
1740 gtk_widget_hide (priv->sidebar);
1743 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1746 gtk_window_resize (GTK_WINDOW (window), w, h);
1750 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1753 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1754 EmpathyTpCall *call;
1756 priv->sending_video = send;
1758 /* When we start sending video, we want to show the video preview by
1762 empathy_call_window_setup_video_preview (window);
1763 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1767 g_object_get (priv->handler, "tp-call", &call, NULL);
1768 empathy_tp_call_request_video_stream_direction (call, send);
1769 g_object_unref (call);
1773 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1774 EmpathyCallWindow *window)
1776 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1779 active = (gtk_toggle_tool_button_get_active (toggle));
1781 if (priv->sending_video == active)
1784 empathy_call_window_set_send_video (window, active);
1785 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1789 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1790 EmpathyCallWindow *window)
1792 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1795 active = (gtk_toggle_action_get_active (toggle));
1797 if (priv->sending_video == active)
1800 empathy_call_window_set_send_video (window, active);
1801 gtk_toggle_tool_button_set_active (
1802 GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1806 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1807 EmpathyCallWindow *window)
1809 gboolean show_preview_toggled;
1810 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1812 show_preview_toggled = gtk_toggle_action_get_active (toggle);
1814 if (show_preview_toggled)
1815 gtk_widget_show (priv->self_user_output_frame);
1817 gtk_widget_hide (priv->self_user_output_frame);
1821 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1822 EmpathyCallWindow *window)
1824 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1827 active = (gtk_toggle_tool_button_get_active (toggle));
1831 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1833 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1837 /* TODO, Instead of setting the input volume to 0 we should probably
1838 * stop sending but this would cause the audio call to drop if both
1839 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1840 * in the future. GNOME #574574
1842 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1844 gtk_adjustment_set_value (priv->audio_input_adj, 0);
1849 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1850 EmpathyCallWindow *window)
1852 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1854 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1859 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1860 EmpathyCallWindow *window)
1862 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1864 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1869 empathy_call_window_hangup_cb (gpointer object,
1870 EmpathyCallWindow *window)
1872 if (empathy_call_window_disconnected (window))
1873 gtk_widget_destroy (GTK_WIDGET (window));
1877 empathy_call_window_restart_call (EmpathyCallWindow *window)
1880 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1882 gtk_widget_destroy (priv->remote_user_output_hbox);
1883 gtk_widget_destroy (priv->self_user_output_hbox);
1885 priv->pipeline = gst_pipeline_new (NULL);
1886 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
1887 gst_bus_add_watch (bus, empathy_call_window_bus_message, window);
1889 empathy_call_window_setup_remote_frame (bus, window);
1890 empathy_call_window_setup_self_frame (bus, window);
1892 g_object_unref (bus);
1894 gtk_widget_show_all (priv->content_hbox);
1896 empathy_call_window_status_message (window, CONNECTING_STATUS_TEXT);
1897 priv->call_started = TRUE;
1898 empathy_call_handler_start_call (priv->handler);
1899 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1901 gtk_action_set_sensitive (priv->redial, FALSE);
1902 gtk_widget_set_sensitive (priv->redial_button, FALSE);
1906 empathy_call_window_redial_cb (gpointer object,
1907 EmpathyCallWindow *window)
1909 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1911 if (priv->connected)
1912 priv->redialing = TRUE;
1914 empathy_call_handler_stop_call (priv->handler);
1916 if (!priv->connected)
1917 empathy_call_window_restart_call (window);
1921 empathy_call_window_fullscreen_cb (gpointer object,
1922 EmpathyCallWindow *window)
1924 empathy_call_window_fullscreen_toggle (window);
1928 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
1930 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1932 if (priv->is_fullscreen)
1933 gtk_window_unfullscreen (GTK_WINDOW (window));
1935 gtk_window_fullscreen (GTK_WINDOW (window));
1939 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
1940 GdkEventButton *event, EmpathyCallWindow *window)
1942 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1944 empathy_call_window_video_menu_popup (window, event->button);
1952 empathy_call_window_key_press_cb (GtkWidget *video_output,
1953 GdkEventKey *event, EmpathyCallWindow *window)
1955 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1957 if (priv->is_fullscreen && event->keyval == GDK_Escape)
1959 /* Since we are in fullscreen mode, toggling will bring us back to
1961 empathy_call_window_fullscreen_toggle (window);
1969 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
1970 GdkEventMotion *event, EmpathyCallWindow *window)
1972 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1974 if (priv->is_fullscreen)
1976 empathy_call_window_fullscreen_show_popup (priv->fullscreen);
1983 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
1987 EmpathyCallWindowPriv *priv = GET_PRIV (window);
1989 menu = gtk_ui_manager_get_widget (priv->ui_manager,
1991 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1992 button, gtk_get_current_event_time ());
1993 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
1997 empathy_call_window_status_message (EmpathyCallWindow *window,
2000 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2002 if (priv->context_id == 0)
2004 priv->context_id = gtk_statusbar_get_context_id (
2005 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2009 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2012 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2017 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2018 gdouble value, EmpathyCallWindow *window)
2020 EmpathyCallWindowPriv *priv = GET_PRIV (window);
2022 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),