]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
call-window: add a Microphone menu to change mic on the fly
[empathy.git] / src / empathy-call-window.c
1 /*
2  * empathy-call-window.c - Source for EmpathyCallWindow
3  * Copyright (C) 2008-2011 Collabora Ltd.
4  * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
5  *
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.
10  *
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.
15  *
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
19  */
20
21
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include <math.h>
26
27 #include <gdk/gdkkeysyms.h>
28 #include <gst/gst.h>
29 #include <gtk/gtk.h>
30 #include <glib/gi18n.h>
31
32 #include <clutter/clutter.h>
33 #include <clutter-gtk/clutter-gtk.h>
34 #include <clutter-gst/clutter-gst.h>
35
36 #include <telepathy-glib/util.h>
37 #include <telepathy-farstream/telepathy-farstream.h>
38 #include <telepathy-glib/util.h>
39
40 #include <gst/farsight/fs-element-added-notifier.h>
41 #include <gst/farsight/fs-utils.h>
42
43 #include <libempathy/empathy-camera-monitor.h>
44 #include <libempathy/empathy-tp-contact-factory.h>
45 #include <libempathy/empathy-utils.h>
46 #include <libempathy-gtk/empathy-avatar-image.h>
47 #include <libempathy-gtk/empathy-ui-utils.h>
48 #include <libempathy-gtk/empathy-sound-manager.h>
49 #include <libempathy-gtk/empathy-geometry.h>
50 #include <libempathy-gtk/empathy-images.h>
51
52 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
53 #include <libempathy/empathy-debug.h>
54
55 #include "empathy-call-window.h"
56 #include "empathy-call-window-fullscreen.h"
57 #include "empathy-call-factory.h"
58 #include "empathy-video-widget.h"
59 #include "empathy-audio-src.h"
60 #include "empathy-audio-sink.h"
61 #include "empathy-video-src.h"
62 #include "ev-sidebar.h"
63 #include "empathy-mic-menu.h"
64
65 #define CONTENT_HBOX_BORDER_WIDTH 6
66 #define CONTENT_HBOX_SPACING 3
67 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
68
69 #define SELF_VIDEO_SECTION_WIDTH 120
70 #define SELF_VIDEO_SECTION_HEIGTH 90
71
72 /* The avatar's default width and height are set to the same value because we
73    want a square icon. */
74 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
75 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
76   EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
77
78 #define SMALL_TOOLBAR_SIZE 36
79
80 /* If an video input error occurs, the error message will start with "v4l" */
81 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
82
83 /* The time interval in milliseconds between 2 outgoing rings */
84 #define MS_BETWEEN_RING 500
85
86 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
87
88 enum {
89   PROP_CALL_HANDLER = 1,
90 };
91
92 typedef enum {
93   CONNECTING,
94   CONNECTED,
95   HELD,
96   DISCONNECTED,
97   REDIALING
98 } CallState;
99
100 typedef enum {
101   CAMERA_STATE_OFF = 0,
102   CAMERA_STATE_ON,
103 } CameraState;
104
105 struct _EmpathyCallWindowPriv
106 {
107   gboolean dispose_has_run;
108   EmpathyCallHandler *handler;
109
110   EmpathyContact *contact;
111
112   EmpathyCameraMonitor *camera_monitor;
113
114   guint call_state;
115   gboolean outgoing;
116
117   GtkUIManager *ui_manager;
118   GtkWidget *errors_vbox;
119   /* widget displays the video received from the remote user. This widget is
120    * alive only during call. */
121   ClutterActor *video_output;
122   ClutterActor *video_preview;
123   ClutterActor *preview_hidden_button;
124   GtkWidget *video_container;
125   GtkWidget *remote_user_avatar_widget;
126   GtkWidget *remote_user_avatar_toolbar;
127   GtkWidget *remote_user_name_toolbar;
128   GtkWidget *sidebar;
129   GtkWidget *status_label;
130   GtkWidget *hangup_button;
131   GtkWidget *audio_call_button;
132   GtkWidget *video_call_button;
133   GtkWidget *mic_button;
134   GtkWidget *camera_button;
135   GtkWidget *dialpad_button;
136   GtkWidget *toolbar;
137   GtkWidget *bottom_toolbar;
138   GtkWidget *pane;
139   GtkAction *menu_sidebar;
140   GtkAction *menu_fullscreen;
141
142   /* The box that contains self and remote avatar and video
143      input/output. When we redial, we destroy and re-create the box */
144   ClutterActor *video_box;
145   ClutterLayoutManager *video_layout;
146
147   /* We keep a reference on the hbox which contains the main content so we can
148      easilly repack everything when toggling fullscreen */
149   GtkWidget *content_hbox;
150
151   gulong video_output_motion_handler_id;
152   guint bus_message_source_id;
153
154   gdouble volume;
155   GtkWidget *volume_scale;
156   GtkWidget *volume_progress_bar;
157   GtkAdjustment *audio_input_adj;
158
159   GtkWidget *dtmf_panel;
160
161   /* Details vbox */
162   GtkWidget *details_vbox;
163   GtkWidget *vcodec_encoding_label;
164   GtkWidget *acodec_encoding_label;
165   GtkWidget *vcodec_decoding_label;
166   GtkWidget *acodec_decoding_label;
167
168   GtkWidget *audio_remote_candidate_label;
169   GtkWidget *audio_local_candidate_label;
170   GtkWidget *video_remote_candidate_label;
171   GtkWidget *video_local_candidate_label;
172   GtkWidget *video_remote_candidate_info_img;
173   GtkWidget *video_local_candidate_info_img;
174   GtkWidget *audio_remote_candidate_info_img;
175   GtkWidget *audio_local_candidate_info_img;
176
177   GstElement *video_input;
178   GstElement *video_preview_sink;
179   GstElement *video_output_sink;
180   GstElement *audio_input;
181   GstElement *audio_output;
182   GstElement *pipeline;
183   GstElement *video_tee;
184
185   GstElement *funnel;
186
187   GList *notifiers;
188
189   GTimer *timer;
190   guint timer_id;
191
192   GtkWidget *video_contrast;
193   GtkWidget *video_brightness;
194   GtkWidget *video_gamma;
195
196   GMutex *lock;
197   gboolean call_started;
198   gboolean sending_video;
199   CameraState camera_state;
200
201   EmpathyCallWindowFullscreen *fullscreen;
202   gboolean is_fullscreen;
203
204   gboolean got_video;
205   guint got_video_src;
206
207   /* Those fields represent the state of the window before it actually was in
208      fullscreen mode. */
209   gboolean sidebar_was_visible_before_fs;
210   gint original_width_before_fs;
211   gint original_height_before_fs;
212
213   gint x, y, w, h, sidebar_width;
214   gboolean maximized;
215
216   /* TRUE if the call should be started when the pipeline is playing */
217   gboolean start_call_when_playing;
218   /* TRUE if we requested to set the pipeline in the playing state */
219   gboolean pipeline_playing;
220
221   EmpathySoundManager *sound_mgr;
222
223   EmpathyMicMenu *mic_menu;
224 };
225
226 #define GET_PRIV(o) (EMPATHY_CALL_WINDOW (o)->priv)
227
228 static void empathy_call_window_realized_cb (GtkWidget *widget,
229   EmpathyCallWindow *window);
230
231 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
232   GdkEvent *event, EmpathyCallWindow *window);
233
234 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
235   GdkEventWindowState *event, EmpathyCallWindow *window);
236
237 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
238   CameraState state);
239
240 static void empathy_call_window_mic_toggled_cb (
241   GtkToggleToolButton *toggle, EmpathyCallWindow *window);
242
243 static void empathy_call_window_sidebar_cb (GtkToggleAction *menu,
244   EmpathyCallWindow *self);
245
246 static void empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
247   EmpathyCallWindow *window);
248
249 static void empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
250   EmpathyCallWindow *window);
251
252 static void empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar,
253   const gchar *page,
254   EmpathyCallWindow *window);
255
256 static void empathy_call_window_hangup_cb (gpointer object,
257   EmpathyCallWindow *window);
258
259 static void empathy_call_window_fullscreen_cb (gpointer object,
260   EmpathyCallWindow *window);
261
262 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
263
264 static gboolean empathy_call_window_video_button_press_cb (
265   GtkWidget *video_output, GdkEventButton *event, EmpathyCallWindow *window);
266
267 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
268   GdkEventKey *event, EmpathyCallWindow *window);
269
270 static gboolean empathy_call_window_video_output_motion_notify (
271   GtkWidget *widget, GdkEventMotion *event, EmpathyCallWindow *window);
272
273 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
274   guint button);
275
276 static void empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
277   EmpathyCallWindow *window);
278
279 static void empathy_call_window_restart_call (EmpathyCallWindow *window);
280
281 static void empathy_call_window_status_message (EmpathyCallWindow *window,
282   gchar *message);
283
284 static gboolean empathy_call_window_bus_message (GstBus *bus,
285   GstMessage *message, gpointer user_data);
286
287 static void
288 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
289   gdouble value, EmpathyCallWindow *window);
290
291 static void
292 empathy_call_window_show_hangup_button (EmpathyCallWindow *self,
293     gboolean show)
294 {
295   gtk_widget_set_visible (self->priv->hangup_button, show);
296   gtk_widget_set_visible (self->priv->audio_call_button, !show);
297   gtk_widget_set_visible (self->priv->video_call_button, !show);
298 }
299
300 static void
301 empathy_call_window_audio_call_cb (GtkToggleToolButton *button,
302     EmpathyCallWindow *self)
303 {
304   g_object_set (self->priv->handler, "initial-video", FALSE, NULL);
305   empathy_call_window_restart_call (self);
306 }
307
308 static void
309 empathy_call_window_video_call_cb (GtkToggleToolButton *button,
310     EmpathyCallWindow *self)
311 {
312   empathy_call_window_set_send_video (self, CAMERA_STATE_ON);
313   g_object_set (self->priv->handler, "initial-video", TRUE, NULL);
314   empathy_call_window_restart_call (self);
315 }
316
317 static void
318 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
319 {
320   EmpathyCallWindowPriv *priv = GET_PRIV (window);
321   TpyCallChannel *call;
322   GQuark button_quark;
323   TpDTMFEvent event;
324
325   g_object_get (priv->handler, "call-channel", &call, NULL);
326
327   button_quark = g_quark_from_static_string (EMPATHY_DTMF_BUTTON_ID);
328   event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
329     button_quark));
330
331   tpy_call_channel_dtmf_start_tone (call, event);
332
333   g_object_unref (call);
334 }
335
336 static void
337 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
338 {
339   EmpathyCallWindowPriv *priv = GET_PRIV (window);
340   TpyCallChannel *call;
341
342   g_object_get (priv->handler, "call-channel", &call, NULL);
343
344   tpy_call_channel_dtmf_stop_tone (call);
345
346   g_object_unref (call);
347 }
348
349 static GtkWidget *
350 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
351   gchar *label_text, GtkWidget *bin)
352 {
353    GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
354    GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
355    GtkWidget *label = gtk_label_new (label_text);
356
357    gtk_widget_set_sensitive (scale, FALSE);
358
359    gtk_container_add (GTK_CONTAINER (bin), vbox);
360
361    gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
362    gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
363    gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
364
365    return scale;
366 }
367
368 static void
369 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
370   EmpathyCallWindow *self)
371
372 {
373   EmpathyCallWindowPriv *priv = GET_PRIV (self);
374
375   empathy_video_src_set_channel (priv->video_input,
376     EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
377 }
378
379 static void
380 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
381   EmpathyCallWindow *self)
382
383 {
384   EmpathyCallWindowPriv *priv = GET_PRIV (self);
385
386   empathy_video_src_set_channel (priv->video_input,
387     EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
388 }
389
390 static void
391 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
392   EmpathyCallWindow *self)
393
394 {
395   EmpathyCallWindowPriv *priv = GET_PRIV (self);
396
397   empathy_video_src_set_channel (priv->video_input,
398     EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
399 }
400
401
402 static GtkWidget *
403 empathy_call_window_create_video_input (EmpathyCallWindow *self)
404 {
405   EmpathyCallWindowPriv *priv = GET_PRIV (self);
406   GtkWidget *hbox;
407
408   hbox = gtk_hbox_new (TRUE, 3);
409
410   priv->video_contrast = empathy_call_window_create_video_input_add_slider (
411     self,  _("Contrast"), hbox);
412
413   priv->video_brightness = empathy_call_window_create_video_input_add_slider (
414     self,  _("Brightness"), hbox);
415
416   priv->video_gamma = empathy_call_window_create_video_input_add_slider (
417     self,  _("Gamma"), hbox);
418
419   return hbox;
420 }
421
422 static void
423 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
424 {
425   EmpathyCallWindowPriv *priv = GET_PRIV (self);
426   guint supported;
427   GtkAdjustment *adj;
428
429   supported = empathy_video_src_get_supported_channels (priv->video_input);
430
431   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
432     {
433       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
434
435       gtk_adjustment_set_value (adj,
436         empathy_video_src_get_channel (priv->video_input,
437           EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
438
439       g_signal_connect (G_OBJECT (adj), "value-changed",
440         G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
441
442       gtk_widget_set_sensitive (priv->video_contrast, TRUE);
443     }
444
445   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
446     {
447       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
448
449       gtk_adjustment_set_value (adj,
450         empathy_video_src_get_channel (priv->video_input,
451           EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
452
453       g_signal_connect (G_OBJECT (adj), "value-changed",
454         G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
455       gtk_widget_set_sensitive (priv->video_brightness, TRUE);
456     }
457
458   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
459     {
460       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
461
462       gtk_adjustment_set_value (adj,
463         empathy_video_src_get_channel (priv->video_input,
464           EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
465
466       g_signal_connect (G_OBJECT (adj), "value-changed",
467         G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
468       gtk_widget_set_sensitive (priv->video_gamma, TRUE);
469     }
470 }
471
472 static void
473 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
474   EmpathyCallWindow *self)
475 {
476   EmpathyCallWindowPriv *priv = GET_PRIV (self);
477   gdouble volume;
478
479   volume = gtk_adjustment_get_value (adj)/100.0;
480
481   /* Don't store the volume because of muting */
482   if (volume > 0 || gtk_toggle_tool_button_get_active (
483         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
484     priv->volume = volume;
485
486   /* Ensure that the toggle button is active if the volume is > 0 and inactive
487    * if it's smaller than 0 */
488   if ((volume > 0) != gtk_toggle_tool_button_get_active (
489         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
490     gtk_toggle_tool_button_set_active (
491       GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
492
493   empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
494     volume);
495 }
496
497 static void
498 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
499   gdouble level, EmpathyCallWindow *window)
500 {
501   gdouble value;
502   EmpathyCallWindowPriv *priv = GET_PRIV (window);
503
504   value = CLAMP (pow (10, level / 20), 0.0, 1.0);
505   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
506       value);
507 }
508
509 static GtkWidget *
510 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
511 {
512   EmpathyCallWindowPriv *priv = GET_PRIV (self);
513   GtkWidget *hbox, *vbox, *label;
514
515   hbox = gtk_hbox_new (TRUE, 3);
516
517   vbox = gtk_vbox_new (FALSE, 3);
518   gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
519
520   priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
521   gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
522   label = gtk_label_new (_("Volume"));
523
524   priv->audio_input_adj = gtk_range_get_adjustment (
525     GTK_RANGE (priv->volume_scale));
526   priv->volume =  empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
527     (priv->audio_input));
528   gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
529
530   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
531     G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
532
533   gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
534   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
535
536   priv->volume_progress_bar = gtk_progress_bar_new ();
537
538   gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->volume_progress_bar),
539       GTK_ORIENTATION_VERTICAL);
540
541   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
542       0);
543
544   gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
545       3);
546
547   return hbox;
548 }
549
550 static void
551 empathy_call_window_show_video_output (EmpathyCallWindow *self,
552     gboolean show)
553 {
554   if (self->priv->video_output != NULL)
555     g_object_set (self->priv->video_output, "visible", show, NULL);
556
557   gtk_widget_set_visible (self->priv->remote_user_avatar_widget, !show);
558 }
559
560 static void
561 create_video_output_widget (EmpathyCallWindow *self)
562 {
563   EmpathyCallWindowPriv *priv = GET_PRIV (self);
564
565   g_assert (priv->video_output == NULL);
566   g_assert (priv->pipeline != NULL);
567
568   priv->video_output = clutter_texture_new ();
569
570   clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (priv->video_output),
571       TRUE);
572
573   priv->video_output_sink = clutter_gst_video_sink_new (
574       CLUTTER_TEXTURE (priv->video_output));
575
576   clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_box),
577       priv->video_output);
578
579   gtk_widget_add_events (priv->video_container,
580       GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
581   g_signal_connect (G_OBJECT (priv->video_container), "button-press-event",
582       G_CALLBACK (empathy_call_window_video_button_press_cb), self);
583 }
584
585 static void
586 create_video_input (EmpathyCallWindow *self)
587 {
588   EmpathyCallWindowPriv *priv = GET_PRIV (self);
589
590   g_assert (priv->video_input == NULL);
591   priv->video_input = empathy_video_src_new ();
592   gst_object_ref (priv->video_input);
593   gst_object_sink (priv->video_input);
594 }
595
596 static void
597 create_audio_input (EmpathyCallWindow *self)
598 {
599   EmpathyCallWindowPriv *priv = GET_PRIV (self);
600
601   g_assert (priv->audio_input == NULL);
602   priv->audio_input = empathy_audio_src_new ();
603   gst_object_ref (priv->audio_input);
604   gst_object_sink (priv->audio_input);
605
606   tp_g_signal_connect_object (priv->audio_input, "peak-level-changed",
607     G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
608     self, 0);
609 }
610
611 static void
612 add_video_preview_to_pipeline (EmpathyCallWindow *self)
613 {
614   EmpathyCallWindowPriv *priv = GET_PRIV (self);
615   GstElement *preview;
616
617   g_assert (priv->video_preview != NULL);
618   g_assert (priv->pipeline != NULL);
619   g_assert (priv->video_input != NULL);
620   g_assert (priv->video_tee != NULL);
621
622   preview = priv->video_preview_sink;
623
624   if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_input))
625     {
626       g_warning ("Could not add video input to pipeline");
627       return;
628     }
629
630   if (!gst_bin_add (GST_BIN (priv->pipeline), preview))
631     {
632       g_warning ("Could not add video preview to pipeline");
633       return;
634     }
635
636   if (!gst_element_link (priv->video_input, priv->video_tee))
637     {
638       g_warning ("Could not link video input to video tee");
639       return;
640     }
641
642   if (!gst_element_link (priv->video_tee, preview))
643     {
644       g_warning ("Could not link video tee to video preview");
645       return;
646     }
647 }
648
649 static void
650 empathy_call_window_disable_camera_cb (GtkAction *action,
651     EmpathyCallWindow *self)
652 {
653   clutter_actor_destroy (self->priv->preview_hidden_button);
654
655   gtk_toggle_tool_button_set_active (
656       GTK_TOGGLE_TOOL_BUTTON (self->priv->camera_button), FALSE);
657 }
658
659 static void
660 empathy_call_window_minimise_camera_cb (GtkAction *action,
661     EmpathyCallWindow *self)
662 {
663   clutter_actor_hide (self->priv->video_preview);
664   clutter_actor_show (self->priv->preview_hidden_button);
665 }
666
667 static void
668 empathy_call_window_maximise_camera_cb (GtkAction *action,
669     EmpathyCallWindow *self)
670 {
671   clutter_actor_show (self->priv->video_preview);
672   clutter_actor_hide (self->priv->preview_hidden_button);
673 }
674
675 static void
676 empathy_call_window_preview_button_clicked_cb (GtkButton *button,
677     EmpathyCallWindow *self)
678 {
679   GtkWidget *menu;
680
681   menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
682       "/preview-menu");
683   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
684       0, gtk_get_current_event_time ());
685   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
686 }
687
688 static void
689 empathy_call_window_preview_hidden_button_clicked_cb (GtkButton *button,
690     EmpathyCallWindow *self)
691 {
692   GtkWidget *menu;
693
694   menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
695       "/preview-hidden-menu");
696   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
697       0, gtk_get_current_event_time ());
698   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
699 }
700
701 static void
702 create_video_preview (EmpathyCallWindow *self)
703 {
704   EmpathyCallWindowPriv *priv = GET_PRIV (self);
705   ClutterLayoutManager *layout, *layout_center;
706   ClutterActor *preview;
707   ClutterActor *box;
708   ClutterActor *b;
709   GtkWidget *button;
710
711   g_assert (priv->video_preview == NULL);
712
713   preview = clutter_texture_new ();
714   clutter_actor_set_size (preview,
715       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
716   priv->video_preview_sink = clutter_gst_video_sink_new (
717       CLUTTER_TEXTURE (preview));
718
719   /* Flip the video preview */
720   clutter_actor_set_rotation (preview,
721       CLUTTER_Y_AXIS,
722       180,
723       SELF_VIDEO_SECTION_WIDTH * 0.5,
724       0.0,
725       0.0);
726
727   /* Add a little offset to the video preview */
728   layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_END,
729       CLUTTER_BIN_ALIGNMENT_START);
730   priv->video_preview = clutter_box_new (layout);
731   clutter_actor_set_size (priv->video_preview,
732       SELF_VIDEO_SECTION_WIDTH + 10, SELF_VIDEO_SECTION_HEIGTH + 10);
733
734   layout_center = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
735       CLUTTER_BIN_ALIGNMENT_CENTER);
736   box = clutter_box_new (layout_center);
737   clutter_actor_set_size (box,
738       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
739
740   clutter_container_add_actor (CLUTTER_CONTAINER (box), preview);
741   clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_preview), box);
742
743   g_object_set (priv->video_preview_sink,
744       "sync", FALSE,
745       "async", TRUE,
746       NULL);
747
748   /* Translators: this is an "Info" label. It should be as short
749    * as possible. */
750   button = gtk_button_new_with_label (_("i"));
751   b = gtk_clutter_actor_new_with_contents (button);
752
753   clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout_center),
754       b,
755       CLUTTER_BIN_ALIGNMENT_END,
756       CLUTTER_BIN_ALIGNMENT_END);
757
758   g_signal_connect (button, "clicked",
759       G_CALLBACK (empathy_call_window_preview_button_clicked_cb),
760       self);
761
762   /* Translators: this is an "Info" label. It should be as short
763    * as possible. */
764   button = gtk_button_new_with_label (_("i"));
765   priv->preview_hidden_button =
766       gtk_clutter_actor_new_with_contents (button);
767
768   clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->video_layout),
769       priv->preview_hidden_button,
770       CLUTTER_BIN_ALIGNMENT_START,
771       CLUTTER_BIN_ALIGNMENT_END);
772
773   clutter_actor_hide (priv->preview_hidden_button);
774
775   g_signal_connect (button, "clicked",
776       G_CALLBACK (empathy_call_window_preview_hidden_button_clicked_cb),
777       self);
778
779   clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->video_layout),
780       priv->video_preview,
781       CLUTTER_BIN_ALIGNMENT_START,
782       CLUTTER_BIN_ALIGNMENT_END);
783 }
784
785 static void
786 play_camera (EmpathyCallWindow *window,
787     gboolean play)
788 {
789   EmpathyCallWindowPriv *priv = GET_PRIV (window);
790   GstElement *preview;
791   GstState state;
792
793   if (priv->video_preview == NULL)
794     {
795       create_video_preview (window);
796       add_video_preview_to_pipeline (window);
797     }
798
799   if (play)
800     state = GST_STATE_PLAYING;
801   else
802     state = GST_STATE_NULL;
803
804   preview = priv->video_preview_sink;
805
806   gst_element_set_state (preview, state);
807   gst_element_set_state (priv->video_input, state);
808   gst_element_set_state (priv->video_tee, state);
809 }
810
811 static void
812 display_video_preview (EmpathyCallWindow *self,
813     gboolean display)
814 {
815   EmpathyCallWindowPriv *priv = GET_PRIV (self);
816
817   if (display)
818     {
819       /* Display the video preview */
820       DEBUG ("Show video preview");
821
822       play_camera (self, TRUE);
823       clutter_actor_show (priv->video_preview);
824     }
825   else
826     {
827       /* Hide the video preview */
828       DEBUG ("Hide video preview");
829
830       if (priv->video_preview != NULL)
831         {
832           clutter_actor_hide (priv->video_preview);
833           play_camera (self, FALSE);
834         }
835     }
836 }
837
838 static void
839 empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
840 {
841   EmpathyCallWindowPriv *priv = GET_PRIV (window);
842
843   empathy_call_window_status_message (window, _("Connecting…"));
844   priv->call_state = CONNECTING;
845
846   if (priv->outgoing)
847     empathy_sound_manager_start_playing (priv->sound_mgr, GTK_WIDGET (window),
848         EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
849 }
850
851 static void
852 disable_camera (EmpathyCallWindow *self)
853 {
854   EmpathyCallWindowPriv *priv = GET_PRIV (self);
855
856   if (priv->camera_state == CAMERA_STATE_OFF)
857     return;
858
859   DEBUG ("Disable camera");
860
861   display_video_preview (self, FALSE);
862
863   if (priv->camera_state == CAMERA_STATE_ON)
864     empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
865
866   priv->camera_state = CAMERA_STATE_OFF;
867 }
868
869 static void
870 enable_camera (EmpathyCallWindow *self)
871 {
872   EmpathyCallWindowPriv *priv = GET_PRIV (self);
873
874   if (priv->camera_state == CAMERA_STATE_ON)
875     return;
876
877   if (priv->video_input == NULL)
878     {
879       DEBUG ("Can't enable camera, no input");
880       return;
881     }
882
883   DEBUG ("Enable camera");
884
885   empathy_call_window_set_send_video (self, CAMERA_STATE_ON);
886
887   priv->camera_state = CAMERA_STATE_ON;
888 }
889
890 static void
891 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
892   EmpathyCallWindow *self)
893 {
894   if (gtk_toggle_tool_button_get_active (toggle))
895     enable_camera (self);
896   else
897     disable_camera (self);
898 }
899
900 static void
901 create_pipeline (EmpathyCallWindow *self)
902 {
903   EmpathyCallWindowPriv *priv = GET_PRIV (self);
904   GstBus *bus;
905
906   g_assert (priv->pipeline == NULL);
907
908   priv->pipeline = gst_pipeline_new (NULL);
909   priv->pipeline_playing = FALSE;
910
911   priv->video_tee = gst_element_factory_make ("tee", NULL);
912   gst_object_ref (priv->video_tee);
913   gst_object_sink (priv->video_tee);
914
915   gst_bin_add (GST_BIN (priv->pipeline), priv->video_tee);
916
917   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
918   priv->bus_message_source_id = gst_bus_add_watch (bus,
919       empathy_call_window_bus_message, self);
920
921   g_object_unref (bus);
922 }
923
924 static gboolean
925 empathy_call_window_configure_event_cb (GtkWidget *widget,
926     GdkEvent  *event,
927     EmpathyCallWindow *self)
928 {
929   GdkWindow *gdk_window;
930   GdkWindowState window_state;
931
932   gtk_window_get_position (GTK_WINDOW (self), &self->priv->x, &self->priv->y);
933   gtk_window_get_size (GTK_WINDOW (self), &self->priv->w, &self->priv->h);
934
935   gtk_widget_get_preferred_width (self->priv->sidebar,
936       &self->priv->sidebar_width, NULL);
937
938   gdk_window = gtk_widget_get_window (widget);
939   window_state = gdk_window_get_state (gdk_window);
940   self->priv->maximized = (window_state & GDK_WINDOW_STATE_MAXIMIZED);
941
942   return FALSE;
943 }
944
945 static void
946 empathy_call_window_destroyed_cb (GtkWidget *object,
947     EmpathyCallWindow *self)
948 {
949   if (gtk_widget_get_visible (self->priv->sidebar))
950     {
951       /* Save the geometry as if the sidebar was hidden. */
952       empathy_geometry_save_values (GTK_WINDOW (self),
953           self->priv->x, self->priv->y,
954           self->priv->w - self->priv->sidebar_width, self->priv->h,
955           self->priv->maximized);
956     }
957 }
958
959 static void
960 empathy_call_window_init (EmpathyCallWindow *self)
961 {
962   EmpathyCallWindowPriv *priv;
963   GtkBuilder *gui;
964   GtkWidget *top_vbox;
965   GtkWidget *page;
966   gchar *filename;
967   GtkWidget *scroll;
968   ClutterConstraint *size_constraint;
969   ClutterActor *remote_avatar;
970   GtkStyleContext *context;
971   GdkRGBA rgba;
972   ClutterColor bg;
973
974   priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
975     EMPATHY_TYPE_CALL_WINDOW, EmpathyCallWindowPriv);
976
977   filename = empathy_file_lookup ("empathy-call-window.ui", "src");
978   gui = empathy_builder_get_file (filename,
979     "call_window_vbox", &top_vbox,
980     "errors_vbox", &priv->errors_vbox,
981     "pane", &priv->pane,
982     "remote_user_name_toolbar", &priv->remote_user_name_toolbar,
983     "remote_user_avatar_toolbar", &priv->remote_user_avatar_toolbar,
984     "status_label", &priv->status_label,
985     "audiocall", &priv->audio_call_button,
986     "videocall", &priv->video_call_button,
987     "microphone", &priv->mic_button,
988     "camera", &priv->camera_button,
989     "hangup", &priv->hangup_button,
990     "dialpad", &priv->dialpad_button,
991     "toolbar", &priv->toolbar,
992     "bottom_toolbar", &priv->bottom_toolbar,
993     "menusidebar", &priv->menu_sidebar,
994     "ui_manager", &priv->ui_manager,
995     "menufullscreen", &priv->menu_fullscreen,
996     "details_vbox",  &priv->details_vbox,
997     "vcodec_encoding_label", &priv->vcodec_encoding_label,
998     "acodec_encoding_label", &priv->acodec_encoding_label,
999     "acodec_decoding_label", &priv->acodec_decoding_label,
1000     "vcodec_decoding_label", &priv->vcodec_decoding_label,
1001     "audio_remote_candidate_label", &priv->audio_remote_candidate_label,
1002     "audio_local_candidate_label", &priv->audio_local_candidate_label,
1003     "video_remote_candidate_label", &priv->video_remote_candidate_label,
1004     "video_local_candidate_label", &priv->video_local_candidate_label,
1005     "video_remote_candidate_info_img", &priv->video_remote_candidate_info_img,
1006     "video_local_candidate_info_img", &priv->video_local_candidate_info_img,
1007     "audio_remote_candidate_info_img", &priv->audio_remote_candidate_info_img,
1008     "audio_local_candidate_info_img", &priv->audio_local_candidate_info_img,
1009     NULL);
1010   g_free (filename);
1011
1012   empathy_builder_connect (gui, self,
1013     "menuhangup", "activate", empathy_call_window_hangup_cb,
1014     "hangup", "clicked", empathy_call_window_hangup_cb,
1015     "audiocall", "clicked", empathy_call_window_audio_call_cb,
1016     "videocall", "clicked", empathy_call_window_video_call_cb,
1017     "menusidebar", "toggled", empathy_call_window_sidebar_cb,
1018     "volume", "value-changed", empathy_call_window_volume_changed_cb,
1019     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
1020     "camera", "toggled", empathy_call_window_camera_toggled_cb,
1021     "dialpad", "toggled", empathy_call_window_dialpad_cb,
1022     "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
1023     "menupreviewdisable", "activate", empathy_call_window_disable_camera_cb,
1024     "menupreviewminimise", "activate", empathy_call_window_minimise_camera_cb,
1025     "menupreviewmaximise", "activate", empathy_call_window_maximise_camera_cb,
1026     NULL);
1027
1028   gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1029
1030   priv->camera_monitor = empathy_camera_monitor_dup_singleton ();
1031
1032   g_object_bind_property (priv->camera_monitor, "available",
1033       priv->camera_button, "sensitive",
1034       G_BINDING_SYNC_CREATE);
1035
1036   priv->lock = g_mutex_new ();
1037
1038   gtk_container_add (GTK_CONTAINER (self), top_vbox);
1039
1040   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
1041   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1042                                   CONTENT_HBOX_BORDER_WIDTH);
1043   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
1044
1045   /* avatar/video box */
1046   priv->video_layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
1047       CLUTTER_BIN_ALIGNMENT_CENTER);
1048
1049   priv->video_box = clutter_box_new (priv->video_layout);
1050
1051   priv->video_container = gtk_clutter_embed_new ();
1052
1053   /* Set the background color to that of the rest of the window */
1054   context = gtk_widget_get_style_context (priv->content_hbox);
1055   gtk_style_context_get_background_color (context,
1056       GTK_STATE_FLAG_NORMAL, &rgba);
1057   bg.red = CLAMP (rgba.red * 255.0, 0, 255);
1058   bg.green = CLAMP (rgba.green * 255.0, 0, 255);
1059   bg.blue = CLAMP (rgba.blue * 255.0, 0, 255);
1060   bg.alpha = CLAMP (rgba.alpha * 255.0, 0, 255);
1061   clutter_stage_set_color (
1062       CLUTTER_STAGE (gtk_clutter_embed_get_stage (
1063           GTK_CLUTTER_EMBED (priv->video_container))),
1064       &bg);
1065
1066   clutter_container_add (
1067       CLUTTER_CONTAINER (gtk_clutter_embed_get_stage (
1068           GTK_CLUTTER_EMBED (priv->video_container))),
1069       priv->video_box,
1070       NULL);
1071
1072   size_constraint = clutter_bind_constraint_new (
1073       gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->video_container)),
1074       CLUTTER_BIND_SIZE, 0);
1075   clutter_actor_add_constraint (priv->video_box, size_constraint);
1076
1077   priv->remote_user_avatar_widget = gtk_image_new ();
1078   remote_avatar = gtk_clutter_actor_new_with_contents (
1079       priv->remote_user_avatar_widget);
1080
1081   clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_box),
1082       remote_avatar);
1083
1084   gtk_box_pack_start (GTK_BOX (priv->content_hbox),
1085       priv->video_container, TRUE, TRUE,
1086       CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1087
1088   create_pipeline (self);
1089   create_video_output_widget (self);
1090   create_audio_input (self);
1091   create_video_input (self);
1092
1093   /* The call will be started as soon the pipeline is playing */
1094   priv->start_call_when_playing = TRUE;
1095
1096   priv->sidebar = ev_sidebar_new ();
1097   g_signal_connect (G_OBJECT (priv->sidebar),
1098     "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
1099   g_signal_connect (G_OBJECT (priv->sidebar),
1100     "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
1101   g_signal_connect (G_OBJECT (priv->sidebar), "changed",
1102     G_CALLBACK (empathy_call_window_sidebar_changed_cb), self);
1103   gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
1104
1105   page = empathy_call_window_create_audio_input (self);
1106   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "audio-input",
1107       _("Audio input"), page);
1108
1109   page = empathy_call_window_create_video_input (self);
1110   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "video-input",
1111       _("Video input"), page);
1112
1113   priv->dtmf_panel = empathy_create_dtmf_dialpad (G_OBJECT (self),
1114       G_CALLBACK (dtmf_button_pressed_cb),
1115       G_CALLBACK (dtmf_button_released_cb));
1116   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "dialpad",
1117       _("Dialpad"), priv->dtmf_panel);
1118
1119   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1120
1121   /* Put the details vbox in a scroll window as it can require a lot of
1122    * horizontal space. */
1123   scroll = gtk_scrolled_window_new (NULL, NULL);
1124   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scroll),
1125       priv->details_vbox);
1126
1127   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "details", _("Details"),
1128     scroll);
1129
1130   gtk_widget_show_all (top_vbox);
1131
1132   gtk_widget_hide (priv->sidebar);
1133
1134   priv->fullscreen = empathy_call_window_fullscreen_new (self);
1135
1136   empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1137       priv->video_container);
1138
1139   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1140       "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1141
1142   g_signal_connect (G_OBJECT (self), "realize",
1143     G_CALLBACK (empathy_call_window_realized_cb), self);
1144
1145   g_signal_connect (G_OBJECT (self), "delete-event",
1146     G_CALLBACK (empathy_call_window_delete_cb), self);
1147
1148   g_signal_connect (G_OBJECT (self), "window-state-event",
1149     G_CALLBACK (empathy_call_window_state_event_cb), self);
1150
1151   g_signal_connect (G_OBJECT (self), "key-press-event",
1152       G_CALLBACK (empathy_call_window_key_press_cb), self);
1153
1154   priv->timer = g_timer_new ();
1155
1156   g_object_ref (priv->ui_manager);
1157   g_object_unref (gui);
1158
1159   priv->sound_mgr = empathy_sound_manager_dup_singleton ();
1160   priv->mic_menu = empathy_mic_menu_new (self);
1161
1162   empathy_call_window_show_hangup_button (self, TRUE);
1163
1164   empathy_geometry_bind (GTK_WINDOW (self), "call-window");
1165   /* These signals are used to track the window position and save it
1166    * when the window is destroyed. We need to do this as we don't want
1167    * the window geometry to be saved with the sidebar taken into account. */
1168   g_signal_connect (self, "destroy",
1169       G_CALLBACK (empathy_call_window_destroyed_cb), self);
1170   g_signal_connect (self, "configure-event",
1171       G_CALLBACK (empathy_call_window_configure_event_cb), self);
1172   g_signal_connect (self, "window-state-event",
1173       G_CALLBACK (empathy_call_window_configure_event_cb), self);
1174 }
1175
1176 /* Instead of specifying a width and a height, we specify only one size. That's
1177    because we want a square avatar icon.  */
1178 static void
1179 init_contact_avatar_with_size (EmpathyContact *contact,
1180     GtkWidget *image_widget,
1181     gint size)
1182 {
1183   GdkPixbuf *pixbuf_avatar = NULL;
1184
1185   if (contact != NULL)
1186     {
1187       pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1188         size, size);
1189     }
1190
1191   if (pixbuf_avatar == NULL)
1192     {
1193       pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
1194           EMPATHY_IMAGE_AVATAR_DEFAULT, size);
1195     }
1196
1197   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1198
1199   if (pixbuf_avatar != NULL)
1200     g_object_unref (pixbuf_avatar);
1201 }
1202
1203 static void
1204 set_window_title (EmpathyCallWindow *self)
1205 {
1206   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1207   gchar *tmp;
1208
1209   if (priv->contact != NULL)
1210     {
1211       /* translators: Call is a noun and %s is the contact name. This string
1212        * is used in the window title */
1213       tmp = g_strdup_printf (_("Call with %s"),
1214           empathy_contact_get_alias (priv->contact));
1215       gtk_window_set_title (GTK_WINDOW (self), tmp);
1216       g_free (tmp);
1217     }
1218   else
1219     {
1220       gtk_window_set_title (GTK_WINDOW (self), _("Call with %d participants"));
1221     }
1222 }
1223
1224 static void
1225 set_remote_user_name (EmpathyCallWindow *self,
1226   EmpathyContact *contact)
1227 {
1228   const gchar *alias = empathy_contact_get_alias (contact);
1229   const gchar *status = empathy_contact_get_status (contact);
1230   gchar *label;
1231
1232   label = g_strdup_printf ("%s\n<small>%s</small>", alias, status);
1233   gtk_label_set_markup (GTK_LABEL (self->priv->remote_user_name_toolbar),
1234       label);
1235   g_free (label);
1236 }
1237
1238 static void
1239 contact_name_changed_cb (EmpathyContact *contact,
1240     GParamSpec *pspec,
1241     EmpathyCallWindow *self)
1242 {
1243   set_window_title (self);
1244   set_remote_user_name (self, contact);
1245 }
1246
1247 static void
1248 contact_presence_changed_cb (EmpathyContact *contact,
1249     GParamSpec *pspec,
1250     EmpathyCallWindow *self)
1251 {
1252   set_remote_user_name (self, contact);
1253 }
1254
1255 static void
1256 contact_avatar_changed_cb (EmpathyContact *contact,
1257     GParamSpec *pspec,
1258     EmpathyCallWindow *self)
1259 {
1260   int size;
1261   GtkAllocation allocation;
1262   GtkWidget *avatar_widget;
1263
1264   avatar_widget = self->priv->remote_user_avatar_widget;
1265
1266   gtk_widget_get_allocation (avatar_widget, &allocation);
1267   size = allocation.height;
1268
1269   if (size == 0)
1270     {
1271       /* the widget is not allocated yet, set a default size */
1272       size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1273           REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1274     }
1275
1276   init_contact_avatar_with_size (contact, avatar_widget, size);
1277
1278   avatar_widget = self->priv->remote_user_avatar_toolbar;
1279
1280   gtk_widget_get_allocation (avatar_widget, &allocation);
1281   size = allocation.height;
1282
1283   if (size == 0)
1284     {
1285       /* the widget is not allocated yet, set a default size */
1286       size = SMALL_TOOLBAR_SIZE;
1287     }
1288
1289   init_contact_avatar_with_size (contact, avatar_widget, size);
1290 }
1291
1292 static void
1293 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1294     EmpathyCallHandler *handler)
1295 {
1296   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1297
1298   g_signal_connect (priv->contact, "notify::name",
1299       G_CALLBACK (contact_name_changed_cb), self);
1300   g_signal_connect (priv->contact, "notify::avatar",
1301     G_CALLBACK (contact_avatar_changed_cb), self);
1302   /* FIXME: There's no EmpathyContact::presence yet */
1303   g_signal_connect (priv->contact, "notify::presence",
1304       G_CALLBACK (contact_presence_changed_cb), self);
1305
1306   set_window_title (self);
1307   set_remote_user_name (self, priv->contact);
1308
1309   init_contact_avatar_with_size (priv->contact,
1310       priv->remote_user_avatar_widget,
1311       MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1312           REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1313
1314   init_contact_avatar_with_size (priv->contact,
1315       priv->remote_user_avatar_toolbar,
1316       SMALL_TOOLBAR_SIZE);
1317
1318   /* The remote avatar is shown by default and will be hidden when we receive
1319      video from the remote side. */
1320   clutter_actor_hide (priv->video_output);
1321   gtk_widget_show (priv->remote_user_avatar_widget);
1322 }
1323
1324 static void
1325 update_send_codec (EmpathyCallWindow *self,
1326     gboolean audio)
1327 {
1328   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1329   FsCodec *codec;
1330   GtkWidget *widget;
1331   gchar *tmp;
1332
1333   if (audio)
1334     {
1335       codec = empathy_call_handler_get_send_audio_codec (priv->handler);
1336       widget = priv->acodec_encoding_label;
1337     }
1338   else
1339     {
1340       codec = empathy_call_handler_get_send_video_codec (priv->handler);
1341       widget = priv->vcodec_encoding_label;
1342     }
1343
1344   if (codec == NULL)
1345     return;
1346
1347   tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
1348   gtk_label_set_text (GTK_LABEL (widget), tmp);
1349   g_free (tmp);
1350 }
1351
1352 static void
1353 send_audio_codec_notify_cb (GObject *object,
1354     GParamSpec *pspec,
1355     gpointer user_data)
1356 {
1357   EmpathyCallWindow *self = user_data;
1358
1359   update_send_codec (self, TRUE);
1360 }
1361
1362 static void
1363 send_video_codec_notify_cb (GObject *object,
1364     GParamSpec *pspec,
1365     gpointer user_data)
1366 {
1367   EmpathyCallWindow *self = user_data;
1368
1369   update_send_codec (self, FALSE);
1370 }
1371
1372 static void
1373 update_recv_codec (EmpathyCallWindow *self,
1374     gboolean audio)
1375 {
1376   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1377   GList *codecs, *l;
1378   GtkWidget *widget;
1379   GString *str = NULL;
1380
1381   if (audio)
1382     {
1383       codecs = empathy_call_handler_get_recv_audio_codecs (priv->handler);
1384       widget = priv->acodec_decoding_label;
1385     }
1386   else
1387     {
1388       codecs = empathy_call_handler_get_recv_video_codecs (priv->handler);
1389       widget = priv->vcodec_decoding_label;
1390     }
1391
1392   if (codecs == NULL)
1393     return;
1394
1395   for (l = codecs; l != NULL; l = g_list_next (l))
1396     {
1397       FsCodec *codec = l->data;
1398
1399       if (str == NULL)
1400         str = g_string_new (NULL);
1401       else
1402         g_string_append (str, ", ");
1403
1404       g_string_append_printf (str, "%s/%u", codec->encoding_name,
1405           codec->clock_rate);
1406     }
1407
1408   gtk_label_set_text (GTK_LABEL (widget), str->str);
1409   g_string_free (str, TRUE);
1410 }
1411
1412 static void
1413 recv_audio_codecs_notify_cb (GObject *object,
1414     GParamSpec *pspec,
1415     gpointer user_data)
1416 {
1417   EmpathyCallWindow *self = user_data;
1418
1419   update_recv_codec (self, TRUE);
1420 }
1421
1422 static void
1423 recv_video_codecs_notify_cb (GObject *object,
1424     GParamSpec *pspec,
1425     gpointer user_data)
1426 {
1427   EmpathyCallWindow *self = user_data;
1428
1429   update_recv_codec (self, FALSE);
1430 }
1431
1432 static const gchar *
1433 candidate_type_to_str (FsCandidate *candidate)
1434 {
1435   switch (candidate->type)
1436     {
1437       case FS_CANDIDATE_TYPE_HOST:
1438         return "host";
1439       case FS_CANDIDATE_TYPE_SRFLX:
1440         return "server reflexive";
1441       case FS_CANDIDATE_TYPE_PRFLX:
1442         return "peer reflexive";
1443       case FS_CANDIDATE_TYPE_RELAY:
1444         return "relay";
1445       case FS_CANDIDATE_TYPE_MULTICAST:
1446         return "multicast";
1447     }
1448
1449   return NULL;
1450 }
1451
1452 static const gchar *
1453 candidate_type_to_desc (FsCandidate *candidate)
1454 {
1455   switch (candidate->type)
1456     {
1457       case FS_CANDIDATE_TYPE_HOST:
1458         return _("The IP address as seen by the machine");
1459       case FS_CANDIDATE_TYPE_SRFLX:
1460         return _("The IP address as seen by a server on the Internet");
1461       case FS_CANDIDATE_TYPE_PRFLX:
1462         return _("The IP address of the peer as seen by the other side");
1463       case FS_CANDIDATE_TYPE_RELAY:
1464         return _("The IP address of a relay server");
1465       case FS_CANDIDATE_TYPE_MULTICAST:
1466         return _("The IP address of the multicast group");
1467     }
1468
1469   return NULL;
1470 }
1471
1472 static void
1473 update_candidat_widget (EmpathyCallWindow *self,
1474     GtkWidget *label,
1475     GtkWidget *img,
1476     FsCandidate *candidate)
1477 {
1478   gchar *str;
1479
1480   g_assert (candidate != NULL);
1481   str = g_strdup_printf ("%s %u (%s)", candidate->ip,
1482       candidate->port, candidate_type_to_str (candidate));
1483
1484   gtk_label_set_text (GTK_LABEL (label), str);
1485   gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
1486
1487   g_free (str);
1488 }
1489
1490 static void
1491 candidates_changed_cb (GObject *object,
1492     FsMediaType type,
1493     EmpathyCallWindow *self)
1494 {
1495   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1496   FsCandidate *candidate = NULL;
1497
1498   if (type == FS_MEDIA_TYPE_VIDEO)
1499     {
1500       /* Update remote candidate */
1501       candidate = empathy_call_handler_get_video_remote_candidate (
1502           priv->handler);
1503
1504       update_candidat_widget (self, priv->video_remote_candidate_label,
1505           priv->video_remote_candidate_info_img, candidate);
1506
1507       /* Update local candidate */
1508       candidate = empathy_call_handler_get_video_local_candidate (
1509           priv->handler);
1510
1511       update_candidat_widget (self, priv->video_local_candidate_label,
1512           priv->video_local_candidate_info_img, candidate);
1513     }
1514   else
1515     {
1516       /* Update remote candidate */
1517       candidate = empathy_call_handler_get_audio_remote_candidate (
1518           priv->handler);
1519
1520       update_candidat_widget (self, priv->audio_remote_candidate_label,
1521           priv->audio_remote_candidate_info_img, candidate);
1522
1523       /* Update local candidate */
1524       candidate = empathy_call_handler_get_audio_local_candidate (
1525           priv->handler);
1526
1527       update_candidat_widget (self, priv->audio_local_candidate_label,
1528           priv->audio_local_candidate_info_img, candidate);
1529     }
1530 }
1531
1532 static void
1533 empathy_call_window_constructed (GObject *object)
1534 {
1535   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1536   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1537   TpyCallChannel *call;
1538
1539   g_assert (priv->handler != NULL);
1540
1541   g_object_get (priv->handler, "call-channel", &call, NULL);
1542   priv->outgoing = (call == NULL);
1543   if (call != NULL)
1544     g_object_unref (call);
1545
1546   g_object_get (priv->handler, "target-contact", &priv->contact, NULL);
1547   g_assert (priv->contact != NULL);
1548
1549   empathy_call_window_setup_avatars (self, priv->handler);
1550   empathy_call_window_set_state_connecting (self);
1551
1552   if (!empathy_call_handler_has_initial_video (priv->handler))
1553     {
1554       gtk_toggle_tool_button_set_active (
1555           GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1556     }
1557   /* If call has InitialVideo, the preview will be started once the call has
1558    * been started (start_call()). */
1559
1560   update_send_codec (self, TRUE);
1561   update_send_codec (self, FALSE);
1562   update_recv_codec (self, TRUE);
1563   update_recv_codec (self, FALSE);
1564
1565   tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
1566       G_CALLBACK (send_audio_codec_notify_cb), self, 0);
1567   tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
1568       G_CALLBACK (send_video_codec_notify_cb), self, 0);
1569   tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
1570       G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
1571   tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
1572       G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
1573
1574   tp_g_signal_connect_object (priv->handler, "candidates-changed",
1575       G_CALLBACK (candidates_changed_cb), self, 0);
1576 }
1577
1578 static void empathy_call_window_dispose (GObject *object);
1579 static void empathy_call_window_finalize (GObject *object);
1580
1581 static void
1582 empathy_call_window_set_property (GObject *object,
1583   guint property_id, const GValue *value, GParamSpec *pspec)
1584 {
1585   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1586
1587   switch (property_id)
1588     {
1589       case PROP_CALL_HANDLER:
1590         priv->handler = g_value_dup_object (value);
1591         break;
1592       default:
1593         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1594     }
1595 }
1596
1597 static void
1598 empathy_call_window_get_property (GObject *object,
1599   guint property_id, GValue *value, GParamSpec *pspec)
1600 {
1601   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1602
1603   switch (property_id)
1604     {
1605       case PROP_CALL_HANDLER:
1606         g_value_set_object (value, priv->handler);
1607         break;
1608       default:
1609         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1610     }
1611 }
1612
1613 static void
1614 empathy_call_window_class_init (
1615   EmpathyCallWindowClass *empathy_call_window_class)
1616 {
1617   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1618   GParamSpec *param_spec;
1619
1620   g_type_class_add_private (empathy_call_window_class,
1621     sizeof (EmpathyCallWindowPriv));
1622
1623   object_class->constructed = empathy_call_window_constructed;
1624   object_class->set_property = empathy_call_window_set_property;
1625   object_class->get_property = empathy_call_window_get_property;
1626
1627   object_class->dispose = empathy_call_window_dispose;
1628   object_class->finalize = empathy_call_window_finalize;
1629
1630   param_spec = g_param_spec_object ("handler",
1631     "handler", "The call handler",
1632     EMPATHY_TYPE_CALL_HANDLER,
1633     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1634   g_object_class_install_property (object_class,
1635     PROP_CALL_HANDLER, param_spec);
1636 }
1637
1638 void
1639 empathy_call_window_dispose (GObject *object)
1640 {
1641   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1642   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1643
1644   if (priv->dispose_has_run)
1645     return;
1646
1647   priv->dispose_has_run = TRUE;
1648
1649   if (priv->handler != NULL)
1650     {
1651       empathy_call_handler_stop_call (priv->handler);
1652       tp_clear_object (&priv->handler);
1653     }
1654
1655   if (priv->bus_message_source_id != 0)
1656     {
1657       g_source_remove (priv->bus_message_source_id);
1658       priv->bus_message_source_id = 0;
1659     }
1660
1661   if (priv->got_video_src > 0)
1662     {
1663       g_source_remove (priv->got_video_src);
1664       priv->got_video_src = 0;
1665     }
1666
1667   tp_clear_object (&priv->pipeline);
1668   tp_clear_object (&priv->video_input);
1669   tp_clear_object (&priv->audio_input);
1670   tp_clear_object (&priv->video_tee);
1671   tp_clear_object (&priv->ui_manager);
1672   tp_clear_object (&priv->fullscreen);
1673   tp_clear_object (&priv->camera_monitor);
1674
1675   g_list_free_full (priv->notifiers, g_object_unref);
1676
1677   if (priv->timer_id != 0)
1678     g_source_remove (priv->timer_id);
1679   priv->timer_id = 0;
1680
1681   if (priv->contact != NULL)
1682     {
1683       g_signal_handlers_disconnect_by_func (priv->contact,
1684           contact_name_changed_cb, self);
1685       priv->contact = NULL;
1686     }
1687
1688
1689   tp_clear_object (&priv->sound_mgr);
1690
1691   tp_clear_object (&priv->mic_menu);
1692
1693   G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1694 }
1695
1696 static void
1697 disconnect_video_output_motion_handler (EmpathyCallWindow *self)
1698 {
1699   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1700
1701   if (priv->video_output_motion_handler_id != 0)
1702     {
1703       g_signal_handler_disconnect (G_OBJECT (priv->video_container),
1704           priv->video_output_motion_handler_id);
1705       priv->video_output_motion_handler_id = 0;
1706     }
1707 }
1708
1709 void
1710 empathy_call_window_finalize (GObject *object)
1711 {
1712   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1713   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1714
1715   disconnect_video_output_motion_handler (self);
1716
1717   /* free any data held directly by the object here */
1718   g_mutex_free (priv->lock);
1719
1720   g_timer_destroy (priv->timer);
1721
1722   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1723 }
1724
1725
1726 EmpathyCallWindow *
1727 empathy_call_window_new (EmpathyCallHandler *handler)
1728 {
1729   return EMPATHY_CALL_WINDOW (
1730     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1731 }
1732
1733 static void
1734 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1735   GstElement *conference, gpointer user_data)
1736 {
1737   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1738   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1739   FsElementAddedNotifier *notifier;
1740   GKeyFile *keyfile;
1741
1742   DEBUG ("Conference added");
1743
1744   /* Add notifier to set the various element properties as needed */
1745   notifier = fs_element_added_notifier_new ();
1746   keyfile = fs_utils_get_default_element_properties (conference);
1747
1748   if (keyfile != NULL)
1749     fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile);
1750
1751   fs_element_added_notifier_add (notifier, GST_BIN (priv->pipeline));
1752
1753   priv->notifiers = g_list_prepend (priv->notifiers, notifier);
1754
1755   gst_bin_add (GST_BIN (priv->pipeline), conference);
1756   gst_element_set_state (conference, GST_STATE_PLAYING);
1757 }
1758
1759 static void
1760 empathy_call_window_conference_removed_cb (EmpathyCallHandler *handler,
1761   GstElement *conference, gpointer user_data)
1762 {
1763   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1764   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1765
1766   gst_bin_remove (GST_BIN (priv->pipeline), conference);
1767   gst_element_set_state (conference, GST_STATE_NULL);
1768 }
1769
1770 static gboolean
1771 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1772 {
1773   GstStateChangeReturn state_change_return;
1774   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1775
1776   if (priv->pipeline == NULL)
1777     return TRUE;
1778
1779   if (priv->bus_message_source_id != 0)
1780     {
1781       g_source_remove (priv->bus_message_source_id);
1782       priv->bus_message_source_id = 0;
1783     }
1784
1785   state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1786
1787   if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1788         state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1789     {
1790       if (priv->pipeline != NULL)
1791         g_object_unref (priv->pipeline);
1792       priv->pipeline = NULL;
1793
1794       g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1795           empathy_call_window_mic_volume_changed_cb, self);
1796
1797       if (priv->audio_output != NULL)
1798         g_object_unref (priv->audio_output);
1799       priv->audio_output = NULL;
1800
1801       if (priv->video_tee != NULL)
1802         g_object_unref (priv->video_tee);
1803       priv->video_tee = NULL;
1804
1805       if (priv->video_preview != NULL)
1806         clutter_actor_destroy (priv->video_preview);
1807       priv->video_preview = NULL;
1808
1809       priv->funnel = NULL;
1810
1811       create_pipeline (self);
1812       /* Call will be started when user will hit the 'redial' button */
1813       priv->start_call_when_playing = FALSE;
1814       gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1815
1816       return TRUE;
1817     }
1818   else
1819     {
1820       g_message ("Error: could not destroy pipeline. Closing call window");
1821       gtk_widget_destroy (GTK_WIDGET (self));
1822
1823       return FALSE;
1824     }
1825 }
1826
1827 static void
1828 reset_details_pane (EmpathyCallWindow *self)
1829 {
1830   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1831
1832   gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label), _("Unknown"));
1833   gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label), _("Unknown"));
1834   gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label), _("Unknown"));
1835   gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label), _("Unknown"));
1836 }
1837
1838 static gboolean
1839 empathy_call_window_disconnected (EmpathyCallWindow *self,
1840     gboolean restart)
1841 {
1842   gboolean could_disconnect = FALSE;
1843   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1844   gboolean could_reset_pipeline;
1845
1846   /* Leave full screen mode if needed */
1847   gtk_window_unfullscreen (GTK_WINDOW (self));
1848
1849   gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1850   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1851
1852   could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1853
1854   if (priv->call_state == CONNECTING)
1855       empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
1856
1857   if (priv->call_state != REDIALING)
1858     priv->call_state = DISCONNECTED;
1859
1860   if (could_reset_pipeline)
1861     {
1862       g_mutex_lock (priv->lock);
1863
1864       g_timer_stop (priv->timer);
1865
1866       if (priv->timer_id != 0)
1867         g_source_remove (priv->timer_id);
1868       priv->timer_id = 0;
1869
1870       g_mutex_unlock (priv->lock);
1871
1872       if (!restart)
1873         /* We are about to destroy the window, no need to update it or create
1874          * a video preview */
1875         return TRUE;
1876
1877       empathy_call_window_status_message (self, _("Disconnected"));
1878
1879       empathy_call_window_show_hangup_button (self, FALSE);
1880
1881       /* Unsensitive the camera and mic button */
1882       gtk_widget_set_sensitive (priv->camera_button, FALSE);
1883       gtk_widget_set_sensitive (priv->mic_button, FALSE);
1884
1885       /* Be sure that the mic button is enabled */
1886       gtk_toggle_tool_button_set_active (
1887           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1888
1889       if (priv->camera_state == CAMERA_STATE_ON)
1890         {
1891           /* Restart the preview with the new pipeline. */
1892           display_video_preview (self, TRUE);
1893         }
1894
1895       gtk_progress_bar_set_fraction (
1896           GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1897
1898       /* destroy the video output; it will be recreated when we'll redial */
1899       disconnect_video_output_motion_handler (self);
1900       if (priv->video_output != NULL)
1901         clutter_actor_destroy (priv->video_output);
1902       priv->video_output = NULL;
1903       if (priv->got_video_src > 0)
1904         {
1905           g_source_remove (priv->got_video_src);
1906           priv->got_video_src = 0;
1907         }
1908
1909       gtk_widget_show (priv->remote_user_avatar_widget);
1910
1911       reset_details_pane (self);
1912
1913       priv->sending_video = FALSE;
1914       priv->call_started = FALSE;
1915
1916       could_disconnect = TRUE;
1917
1918       /* TODO: display the self avatar of the preview (depends if the "Always
1919        * Show Video Preview" is enabled or not) */
1920     }
1921
1922   return could_disconnect;
1923 }
1924
1925
1926 static void
1927 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1928     gpointer user_data)
1929 {
1930   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1931   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1932
1933   if (empathy_call_window_disconnected (self, TRUE) &&
1934       priv->call_state == REDIALING)
1935       empathy_call_window_restart_call (self);
1936 }
1937
1938 static gboolean
1939 empathy_call_window_sink_removed_cb (EmpathyCallHandler *handler,
1940     GstPad *sink,
1941     FsMediaType media_type,
1942     EmpathyCallWindow *self)
1943 {
1944   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1945
1946   DEBUG ("removing content");
1947
1948   /*
1949    * This assumes that there is only one video stream per channel...
1950    */
1951
1952   if ((guint) media_type == FS_MEDIA_TYPE_VIDEO)
1953     {
1954       if (priv->funnel != NULL)
1955         {
1956           GstElement *output;
1957
1958           output = priv->video_output_sink;
1959
1960           gst_element_set_state (output, GST_STATE_NULL);
1961           gst_element_set_state (priv->funnel, GST_STATE_NULL);
1962
1963           gst_bin_remove (GST_BIN (priv->pipeline), output);
1964           gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1965           priv->funnel = NULL;
1966           return TRUE;
1967         }
1968     }
1969   else if (media_type == FS_MEDIA_TYPE_AUDIO)
1970     {
1971       if (priv->audio_output != NULL)
1972         {
1973           gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1974
1975           gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1976           priv->audio_output = NULL;
1977           return TRUE;
1978         }
1979     }
1980
1981   return FALSE;
1982 }
1983
1984 /* Called with global lock held */
1985 static GstPad *
1986 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1987 {
1988   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1989   GstPad *pad;
1990   GstElement *output;
1991
1992   if (priv->funnel == NULL)
1993     {
1994       output = priv->video_output_sink;
1995
1996       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1997
1998       if (!priv->funnel)
1999         {
2000           g_warning ("Could not create fsfunnel");
2001           return NULL;
2002         }
2003
2004       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
2005         {
2006           gst_object_unref (priv->funnel);
2007           priv->funnel = NULL;
2008           g_warning ("Could  not add funnel to pipeline");
2009           return NULL;
2010         }
2011
2012       if (!gst_bin_add (GST_BIN (priv->pipeline), output))
2013         {
2014           g_warning ("Could not add the video output widget to the pipeline");
2015           goto error;
2016         }
2017
2018       if (!gst_element_link (priv->funnel, output))
2019         {
2020           g_warning ("Could not link output sink to funnel");
2021           goto error_output_added;
2022         }
2023
2024       if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2025         {
2026           g_warning ("Could not start video sink");
2027           goto error_output_added;
2028         }
2029
2030       if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2031         {
2032           g_warning ("Could not start funnel");
2033           goto error_output_added;
2034         }
2035     }
2036
2037   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
2038
2039   if (!pad)
2040     g_warning ("Could not get request pad from funnel");
2041
2042   return pad;
2043
2044
2045  error_output_added:
2046
2047   gst_element_set_locked_state (priv->funnel, TRUE);
2048   gst_element_set_locked_state (output, TRUE);
2049
2050   gst_element_set_state (priv->funnel, GST_STATE_NULL);
2051   gst_element_set_state (output, GST_STATE_NULL);
2052
2053   gst_bin_remove (GST_BIN (priv->pipeline), output);
2054   gst_element_set_locked_state (output, FALSE);
2055
2056  error:
2057
2058   gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
2059   priv->funnel = NULL;
2060
2061   return NULL;
2062 }
2063
2064 /* Called with global lock held */
2065 static GstPad *
2066 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
2067 {
2068   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2069   GstPad *pad;
2070   GstPadTemplate *template;
2071
2072   if (priv->audio_output == NULL)
2073     {
2074       priv->audio_output = empathy_audio_sink_new ();
2075       g_object_ref_sink (priv->audio_output);
2076
2077       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
2078         {
2079           g_warning ("Could not add audio sink to pipeline");
2080           g_object_unref (priv->audio_output);
2081           goto error_add_output;
2082         }
2083
2084       if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2085         {
2086           g_warning ("Could not start audio sink");
2087           goto error;
2088         }
2089     }
2090
2091   template = gst_element_class_get_pad_template (
2092     GST_ELEMENT_GET_CLASS (priv->audio_output), "sink%d");
2093
2094   pad = gst_element_request_pad (priv->audio_output,
2095     template, NULL, NULL);
2096
2097   if (pad == NULL)
2098     {
2099       g_warning ("Could not get sink pad from sink");
2100       return NULL;
2101     }
2102
2103   return pad;
2104
2105 error:
2106   gst_element_set_locked_state (priv->audio_output, TRUE);
2107   gst_element_set_state (priv->audio_output, GST_STATE_NULL);
2108   gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
2109   priv->audio_output = NULL;
2110
2111 error_add_output:
2112
2113   return NULL;
2114 }
2115
2116 static gboolean
2117 empathy_call_window_update_timer (gpointer user_data)
2118 {
2119   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2120   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2121   gchar *str;
2122   gdouble time_;
2123
2124   time_ = g_timer_elapsed (priv->timer, NULL);
2125
2126   /* Translators: 'status - minutes:seconds' the caller has been connected */
2127   str = g_strdup_printf (_("%s â€” %d:%02dm"),
2128       priv->call_state == HELD ? _("On hold") : _("Connected"),
2129       (int) time_ / 60, (int) time_ % 60);
2130   empathy_call_window_status_message (self, str);
2131   g_free (str);
2132
2133   return TRUE;
2134 }
2135
2136 #if 0
2137 static void
2138 display_error (EmpathyCallWindow *self,
2139     TpyCallChannel *call,
2140     const gchar *img,
2141     const gchar *title,
2142     const gchar *desc,
2143     const gchar *details)
2144 {
2145   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2146   GtkWidget *info_bar;
2147   GtkWidget *content_area;
2148   GtkWidget *hbox;
2149   GtkWidget *vbox;
2150   GtkWidget *image;
2151   GtkWidget *label;
2152   gchar *txt;
2153
2154   /* Create info bar */
2155   info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
2156       NULL);
2157
2158   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
2159
2160   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2161
2162   /* hbox containing the image and the messages vbox */
2163   hbox = gtk_hbox_new (FALSE, 3);
2164   gtk_container_add (GTK_CONTAINER (content_area), hbox);
2165
2166   /* Add image */
2167   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
2168   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2169
2170   /* vbox containing the main message and the details expander */
2171   vbox = gtk_vbox_new (FALSE, 3);
2172   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
2173
2174   /* Add text */
2175   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
2176
2177   label = gtk_label_new (NULL);
2178   gtk_label_set_markup (GTK_LABEL (label), txt);
2179   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2180   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2181   g_free (txt);
2182
2183   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
2184
2185   /* Add details */
2186   if (details != NULL)
2187     {
2188       GtkWidget *expander;
2189
2190       expander = gtk_expander_new (_("Technical Details"));
2191
2192       txt = g_strdup_printf ("<i>%s</i>", details);
2193
2194       label = gtk_label_new (NULL);
2195       gtk_label_set_markup (GTK_LABEL (label), txt);
2196       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2197       gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2198       g_free (txt);
2199
2200       gtk_container_add (GTK_CONTAINER (expander), label);
2201       gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
2202     }
2203
2204   g_signal_connect (info_bar, "response",
2205       G_CALLBACK (gtk_widget_destroy), NULL);
2206
2207   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
2208       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
2209   gtk_widget_show_all (info_bar);
2210 }
2211
2212 static gchar *
2213 media_stream_error_to_txt (EmpathyCallWindow *self,
2214     TpyCallChannel *call,
2215     gboolean audio,
2216     TpMediaStreamError error)
2217 {
2218   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2219   const gchar *cm = NULL;
2220   gchar *url;
2221   gchar *result;
2222
2223   switch (error)
2224     {
2225       case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
2226         if (audio)
2227           return g_strdup_printf (
2228               _("%s's software does not understand any of the audio formats "
2229                 "supported by your computer"),
2230             empathy_contact_get_alias (priv->contact));
2231         else
2232           return g_strdup_printf (
2233               _("%s's software does not understand any of the video formats "
2234                 "supported by your computer"),
2235             empathy_contact_get_alias (priv->contact));
2236
2237       case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
2238         return g_strdup_printf (
2239             _("Can't establish a connection to %s. "
2240               "One of you might be on a network that does not allow "
2241               "direct connections."),
2242           empathy_contact_get_alias (priv->contact));
2243
2244       case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
2245           return g_strdup (_("There was a failure on the network"));
2246
2247       case TP_MEDIA_STREAM_ERROR_NO_CODECS:
2248         if (audio)
2249           return g_strdup (_("The audio formats necessary for this call "
2250                 "are not installed on your computer"));
2251         else
2252           return g_strdup (_("The video formats necessary for this call "
2253                 "are not installed on your computer"));
2254
2255       case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
2256         tp_connection_parse_object_path (
2257             tp_channel_borrow_connection (TP_CHANNEL (call)),
2258             NULL, &cm);
2259
2260         url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
2261             "product=Telepathy&amp;component=%s", cm);
2262
2263         result = g_strdup_printf (
2264             _("Something unexpected happened in a Telepathy component. "
2265               "Please <a href=\"%s\">report this bug</a> and attach "
2266               "logs gathered from the 'Debug' window in the Help menu."), url);
2267
2268         g_free (url);
2269         g_free (cm);
2270         return result;
2271
2272       case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
2273         return g_strdup (_("There was a failure in the call engine"));
2274
2275       case TP_MEDIA_STREAM_ERROR_EOS:
2276         return g_strdup (_("The end of the stream was reached"));
2277
2278       case TP_MEDIA_STREAM_ERROR_UNKNOWN:
2279       default:
2280         return NULL;
2281     }
2282 }
2283
2284 static void
2285 empathy_call_window_stream_error (EmpathyCallWindow *self,
2286     TpyCallChannel *call,
2287     gboolean audio,
2288     guint code,
2289     const gchar *msg,
2290     const gchar *icon,
2291     const gchar *title)
2292 {
2293   gchar *desc;
2294
2295   desc = media_stream_error_to_txt (self, call, audio, code);
2296   if (desc == NULL)
2297     {
2298       /* No description, use the error message. That's not great as it's not
2299        * localized but it's better than nothing. */
2300       display_error (self, call, icon, title, msg, NULL);
2301     }
2302   else
2303     {
2304       display_error (self, call, icon, title, desc, msg);
2305       g_free (desc);
2306     }
2307 }
2308
2309 static void
2310 empathy_call_window_audio_stream_error (TpyCallChannel *call,
2311     guint code,
2312     const gchar *msg,
2313     EmpathyCallWindow *self)
2314 {
2315   empathy_call_window_stream_error (self, call, TRUE, code, msg,
2316       "gnome-stock-mic", _("Can't establish audio stream"));
2317 }
2318
2319 static void
2320 empathy_call_window_video_stream_error (TpyCallChannel *call,
2321     guint code,
2322     const gchar *msg,
2323     EmpathyCallWindow *self)
2324 {
2325   empathy_call_window_stream_error (self, call, FALSE, code, msg,
2326       "camera-web", _("Can't establish video stream"));
2327 }
2328 #endif
2329
2330 static void
2331 empathy_call_window_state_changed_cb (EmpathyCallHandler *handler,
2332     TpyCallState state,
2333     EmpathyCallWindow *self)
2334 {
2335   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2336   TpyCallChannel *call;
2337   gboolean can_send_video;
2338
2339   if (state != TPY_CALL_STATE_ACCEPTED)
2340     return;
2341
2342   if (priv->call_state == CONNECTED)
2343     return;
2344
2345   g_timer_start (priv->timer);
2346   priv->call_state = CONNECTED;
2347
2348   empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2349
2350   can_send_video = priv->video_input != NULL &&
2351     empathy_contact_can_voip_video (priv->contact) &&
2352     empathy_camera_monitor_get_available (priv->camera_monitor);
2353
2354   g_object_get (priv->handler, "call-channel", &call, NULL);
2355
2356   if (tpy_call_channel_has_dtmf (call))
2357     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2358
2359   if (priv->video_input == NULL)
2360     empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
2361
2362   gtk_widget_set_sensitive (priv->camera_button, can_send_video);
2363
2364   empathy_call_window_show_hangup_button (self, TRUE);
2365
2366   gtk_widget_set_sensitive (priv->mic_button, TRUE);
2367
2368   clutter_actor_hide (priv->video_output);
2369   gtk_widget_show (priv->remote_user_avatar_widget);
2370
2371   g_object_unref (call);
2372
2373   g_mutex_lock (priv->lock);
2374
2375   priv->timer_id = g_timeout_add_seconds (1,
2376     empathy_call_window_update_timer, self);
2377
2378   g_mutex_unlock (priv->lock);
2379
2380   empathy_call_window_update_timer (self);
2381
2382   gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
2383 }
2384
2385 static gboolean
2386 empathy_call_window_show_video_output_cb (gpointer user_data)
2387 {
2388   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2389
2390   if (self->priv->video_output != NULL)
2391     {
2392       gtk_widget_hide (self->priv->remote_user_avatar_widget);
2393       clutter_actor_show (self->priv->video_output);
2394     }
2395
2396   return FALSE;
2397 }
2398
2399 static gboolean
2400 empathy_call_window_check_video_cb (gpointer data)
2401 {
2402   EmpathyCallWindow *self = data;
2403
2404   if (self->priv->got_video)
2405     {
2406       self->priv->got_video = FALSE;
2407       return TRUE;
2408     }
2409
2410   /* No video in the last N seconds, display the remote avatar */
2411   empathy_call_window_show_video_output (self, FALSE);
2412
2413   return TRUE;
2414 }
2415
2416 /* Called from the streaming thread */
2417 static gboolean
2418 empathy_call_window_video_probe_cb (GstPad *pad,
2419     GstMiniObject *mini_obj,
2420     EmpathyCallWindow *self)
2421 {
2422   /* Ignore events */
2423   if (GST_IS_EVENT (mini_obj))
2424     return TRUE;
2425
2426   if (G_UNLIKELY (!self->priv->got_video))
2427     {
2428       /* show the remote video */
2429       g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
2430           empathy_call_window_show_video_output_cb,
2431           g_object_ref (self), g_object_unref);
2432
2433       self->priv->got_video = TRUE;
2434     }
2435
2436   return TRUE;
2437 }
2438
2439 /* Called from the streaming thread */
2440 static gboolean
2441 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
2442   GstPad *src, guint media_type, gpointer user_data)
2443 {
2444   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2445   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2446   gboolean retval = FALSE;
2447
2448   GstPad *pad;
2449
2450   g_mutex_lock (priv->lock);
2451
2452   switch (media_type)
2453     {
2454       case TP_MEDIA_STREAM_TYPE_AUDIO:
2455         pad = empathy_call_window_get_audio_sink_pad (self);
2456         break;
2457       case TP_MEDIA_STREAM_TYPE_VIDEO:
2458         g_idle_add (empathy_call_window_show_video_output_cb, self);
2459         pad = empathy_call_window_get_video_sink_pad (self);
2460
2461         gst_pad_add_data_probe (src,
2462             G_CALLBACK (empathy_call_window_video_probe_cb), self);
2463         if (priv->got_video_src > 0)
2464           g_source_remove (priv->got_video_src);
2465         priv->got_video_src = g_timeout_add_seconds (5,
2466             empathy_call_window_check_video_cb, self);
2467         break;
2468       default:
2469         g_assert_not_reached ();
2470     }
2471
2472   if (pad == NULL)
2473     goto out;
2474
2475   if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2476       g_warning ("Could not link %s sink pad",
2477           media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2478   else
2479       retval = TRUE;
2480
2481   gst_object_unref (pad);
2482
2483  out:
2484
2485   /* If no sink could be linked, try to add fakesink to prevent the whole call
2486    * aborting */
2487
2488   if (!retval)
2489     {
2490       GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2491
2492       if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2493         {
2494           GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2495           if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2496               GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2497             {
2498               gst_element_set_locked_state (fakesink, TRUE);
2499               gst_element_set_state (fakesink, GST_STATE_NULL);
2500               gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2501             }
2502           else
2503             {
2504               DEBUG ("Could not link real sink, linked fakesink instead");
2505             }
2506           gst_object_unref (sinkpad);
2507         }
2508       else
2509         {
2510           gst_object_unref (fakesink);
2511         }
2512     }
2513
2514
2515   g_mutex_unlock (priv->lock);
2516
2517   return TRUE;
2518 }
2519
2520 static gboolean
2521 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
2522   GstPad *sink, FsMediaType media_type, gpointer user_data)
2523 {
2524   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2525   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2526   GstPad *pad;
2527   gboolean retval = FALSE;
2528
2529   switch (media_type)
2530     {
2531       case FS_MEDIA_TYPE_AUDIO:
2532         if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2533           {
2534             g_warning ("Could not add audio source to pipeline");
2535             break;
2536           }
2537
2538         pad = gst_element_get_static_pad (priv->audio_input, "src");
2539         if (!pad)
2540           {
2541             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2542             g_warning ("Could not get source pad from audio source");
2543             break;
2544           }
2545
2546         if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2547           {
2548             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2549             g_warning ("Could not link audio source to farsight");
2550             break;
2551           }
2552
2553         if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2554           {
2555             g_warning ("Could not start audio source");
2556             gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2557             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2558             break;
2559           }
2560
2561         retval = TRUE;
2562         break;
2563       case FS_MEDIA_TYPE_VIDEO:
2564         if (priv->video_tee != NULL)
2565           {
2566             pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2567             if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2568               {
2569                 g_warning ("Could not link video source input pipeline");
2570                 break;
2571               }
2572             gst_object_unref (pad);
2573           }
2574
2575         retval = TRUE;
2576         break;
2577       default:
2578         g_assert_not_reached ();
2579     }
2580
2581   return retval;
2582 }
2583
2584 static void
2585 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2586 {
2587   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2588   GstElement *preview;
2589
2590   disable_camera (self);
2591
2592   DEBUG ("remove video input");
2593   preview = priv->video_preview_sink;
2594
2595   gst_element_set_state (priv->video_input, GST_STATE_NULL);
2596   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2597   gst_element_set_state (preview, GST_STATE_NULL);
2598
2599   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2600     preview, NULL);
2601
2602   g_object_unref (priv->video_input);
2603   priv->video_input = NULL;
2604   g_object_unref (priv->video_tee);
2605   priv->video_tee = NULL;
2606   clutter_actor_destroy (priv->video_preview);
2607   priv->video_preview = NULL;
2608
2609   gtk_widget_set_sensitive (priv->camera_button, FALSE);
2610 }
2611
2612 static void
2613 start_call (EmpathyCallWindow *self)
2614 {
2615   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2616
2617   priv->call_started = TRUE;
2618   empathy_call_handler_start_call (priv->handler,
2619       gtk_get_current_event_time ());
2620
2621   if (empathy_call_handler_has_initial_video (priv->handler))
2622     {
2623       TpyCallChannel *call;
2624       TpySendingState s;
2625
2626       g_object_get (priv->handler, "call-channel", &call, NULL);
2627       s = tpy_call_channel_get_video_state (call);
2628
2629       if (s == TPY_SENDING_STATE_PENDING_SEND ||
2630           s == TPY_SENDING_STATE_SENDING)
2631         {
2632           /* Enable 'send video' buttons and display the preview */
2633           gtk_toggle_tool_button_set_active (
2634             GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), TRUE);
2635         }
2636       else
2637         {
2638           gtk_toggle_tool_button_set_active (
2639             GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
2640
2641           if (priv->video_preview == NULL)
2642             {
2643               create_video_preview (self);
2644               add_video_preview_to_pipeline (self);
2645             }
2646         }
2647
2648       g_object_unref (call);
2649     }
2650 }
2651
2652 static gboolean
2653 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2654   gpointer user_data)
2655 {
2656   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2657   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2658   GstState newstate;
2659
2660   empathy_call_handler_bus_message (priv->handler, bus, message);
2661
2662   switch (GST_MESSAGE_TYPE (message))
2663     {
2664       case GST_MESSAGE_STATE_CHANGED:
2665         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2666           {
2667             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2668             if (newstate == GST_STATE_PAUSED)
2669                 empathy_call_window_setup_video_input (self);
2670           }
2671         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2672             !priv->call_started)
2673           {
2674             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2675             if (newstate == GST_STATE_PAUSED)
2676               {
2677                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2678                 priv->pipeline_playing = TRUE;
2679
2680                 if (priv->start_call_when_playing)
2681                   start_call (self);
2682               }
2683           }
2684         break;
2685       case GST_MESSAGE_ERROR:
2686         {
2687           GError *error = NULL;
2688           GstElement *gst_error;
2689           gchar *debug;
2690
2691           gst_message_parse_error (message, &error, &debug);
2692           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2693
2694           g_message ("Element error: %s -- %s\n", error->message, debug);
2695
2696           if (g_str_has_prefix (gst_element_get_name (gst_error),
2697                 VIDEO_INPUT_ERROR_PREFIX))
2698             {
2699               /* Remove the video input and continue */
2700               if (priv->video_input != NULL)
2701                 empathy_call_window_remove_video_input (self);
2702               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2703             }
2704           else
2705             {
2706               empathy_call_window_disconnected (self, TRUE);
2707             }
2708           g_error_free (error);
2709           g_free (debug);
2710         }
2711       case GST_MESSAGE_UNKNOWN:
2712       case GST_MESSAGE_EOS:
2713       case GST_MESSAGE_WARNING:
2714       case GST_MESSAGE_INFO:
2715       case GST_MESSAGE_TAG:
2716       case GST_MESSAGE_BUFFERING:
2717       case GST_MESSAGE_STATE_DIRTY:
2718       case GST_MESSAGE_STEP_DONE:
2719       case GST_MESSAGE_CLOCK_PROVIDE:
2720       case GST_MESSAGE_CLOCK_LOST:
2721       case GST_MESSAGE_NEW_CLOCK:
2722       case GST_MESSAGE_STRUCTURE_CHANGE:
2723       case GST_MESSAGE_STREAM_STATUS:
2724       case GST_MESSAGE_APPLICATION:
2725       case GST_MESSAGE_ELEMENT:
2726       case GST_MESSAGE_SEGMENT_START:
2727       case GST_MESSAGE_SEGMENT_DONE:
2728       case GST_MESSAGE_DURATION:
2729       case GST_MESSAGE_ANY:
2730       default:
2731         break;
2732     }
2733
2734   return TRUE;
2735 }
2736
2737 static void
2738 empathy_call_window_members_changed_cb (TpyCallChannel *call,
2739     GHashTable *members,
2740     EmpathyCallWindow *self)
2741 {
2742   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2743   GHashTableIter iter;
2744   gpointer key, value;
2745   gboolean held = FALSE;
2746
2747   g_hash_table_iter_init (&iter, members);
2748   while (g_hash_table_iter_next (&iter, &key, &value))
2749     {
2750       if (GPOINTER_TO_INT (value) & TPY_CALL_MEMBER_FLAG_HELD)
2751         {
2752           /* This assumes this is a 1-1 call, otherwise one participant
2753            * putting the call on hold wouldn't mean the call is on hold
2754            * for everyone. */
2755           held = TRUE;
2756           break;
2757         }
2758     }
2759
2760   if (held)
2761     priv->call_state = HELD;
2762   else if (priv->call_state == HELD)
2763     priv->call_state = CONNECTED;
2764 }
2765
2766 static void
2767 call_handler_notify_call_cb (EmpathyCallHandler *handler,
2768     GParamSpec *spec,
2769     EmpathyCallWindow *self)
2770 {
2771   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2772   TpyCallChannel *call;
2773
2774   g_object_get (priv->handler, "call-channel", &call, NULL);
2775   if (call == NULL)
2776     return;
2777
2778 /* FIXME
2779   tp_g_signal_connect_object (call, "audio-stream-error",
2780       G_CALLBACK (empathy_call_window_audio_stream_error), self, 0);
2781   tp_g_signal_connect_object (call, "video-stream-error",
2782       G_CALLBACK (empathy_call_window_video_stream_error), self, 0);
2783 */
2784
2785   tp_g_signal_connect_object (call, "members-changed",
2786       G_CALLBACK (empathy_call_window_members_changed_cb), self, 0);
2787
2788   g_object_unref (call);
2789 }
2790
2791 static void
2792 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2793 {
2794   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2795   TpyCallChannel *call;
2796   gint width;
2797
2798   /* Make the hangup button twice as wide */
2799   width = gtk_widget_get_allocated_width (priv->hangup_button);
2800   gtk_widget_set_size_request (priv->hangup_button, width * 2, -1);
2801
2802   g_signal_connect (priv->handler, "state-changed",
2803     G_CALLBACK (empathy_call_window_state_changed_cb), window);
2804   g_signal_connect (priv->handler, "conference-added",
2805     G_CALLBACK (empathy_call_window_conference_added_cb), window);
2806   g_signal_connect (priv->handler, "conference-removed",
2807     G_CALLBACK (empathy_call_window_conference_removed_cb), window);
2808   g_signal_connect (priv->handler, "closed",
2809     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2810   g_signal_connect (priv->handler, "src-pad-added",
2811     G_CALLBACK (empathy_call_window_src_added_cb), window);
2812   g_signal_connect (priv->handler, "sink-pad-added",
2813     G_CALLBACK (empathy_call_window_sink_added_cb), window);
2814   g_signal_connect (priv->handler, "sink-pad-removed",
2815     G_CALLBACK (empathy_call_window_sink_removed_cb), window);
2816
2817   g_object_get (priv->handler, "call-channel", &call, NULL);
2818   if (call != NULL)
2819     {
2820       call_handler_notify_call_cb (priv->handler, NULL, window);
2821       g_object_unref (call);
2822     }
2823   else
2824     {
2825       /* call-channel doesn't exist yet, we'll connect signals once it has been
2826        * set */
2827       g_signal_connect (priv->handler, "notify::call-channel",
2828         G_CALLBACK (call_handler_notify_call_cb), window);
2829     }
2830
2831   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2832 }
2833
2834 static gboolean
2835 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2836   EmpathyCallWindow *window)
2837 {
2838   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2839
2840   if (priv->pipeline != NULL)
2841     {
2842       if (priv->bus_message_source_id != 0)
2843         {
2844           g_source_remove (priv->bus_message_source_id);
2845           priv->bus_message_source_id = 0;
2846         }
2847
2848       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2849     }
2850
2851   if (priv->call_state == CONNECTING)
2852     empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2853
2854   return FALSE;
2855 }
2856
2857 static void
2858 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2859 {
2860   GtkWidget *menu;
2861   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2862
2863   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2864             "/menubar1");
2865
2866   if (set_fullscreen)
2867     {
2868       gtk_widget_hide (priv->sidebar);
2869       gtk_widget_hide (menu);
2870       gtk_widget_hide (priv->toolbar);
2871     }
2872   else
2873     {
2874       if (priv->sidebar_was_visible_before_fs)
2875         gtk_widget_show (priv->sidebar);
2876
2877       gtk_widget_show (menu);
2878       gtk_widget_show (priv->toolbar);
2879
2880       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2881           priv->original_height_before_fs);
2882     }
2883 }
2884
2885 static void
2886 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2887 {
2888   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2889
2890   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2891       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2892   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2893       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2894
2895   if (priv->video_output != NULL)
2896     {
2897 #if 0
2898       gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2899           priv->video_output, TRUE, TRUE,
2900           set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2901           GTK_PACK_START);
2902 #endif
2903     }
2904 }
2905
2906 static gboolean
2907 empathy_call_window_state_event_cb (GtkWidget *widget,
2908   GdkEventWindowState *event, EmpathyCallWindow *window)
2909 {
2910   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2911     {
2912       EmpathyCallWindowPriv *priv = GET_PRIV (window);
2913       gboolean set_fullscreen = event->new_window_state &
2914         GDK_WINDOW_STATE_FULLSCREEN;
2915
2916       if (set_fullscreen)
2917         {
2918           gboolean sidebar_was_visible;
2919           GtkAllocation allocation;
2920           gint original_width, original_height;
2921
2922           gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2923           original_width = allocation.width;
2924           original_height = allocation.height;
2925
2926           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2927
2928           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2929           priv->original_width_before_fs = original_width;
2930           priv->original_height_before_fs = original_height;
2931
2932           if (priv->video_output_motion_handler_id == 0 &&
2933                 priv->video_output != NULL)
2934             {
2935               priv->video_output_motion_handler_id = g_signal_connect (
2936                   G_OBJECT (priv->video_container), "motion-notify-event",
2937                   G_CALLBACK (empathy_call_window_video_output_motion_notify),
2938                   window);
2939             }
2940         }
2941       else
2942         {
2943           disconnect_video_output_motion_handler (window);
2944         }
2945
2946       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2947           set_fullscreen);
2948       show_controls (window, set_fullscreen);
2949       show_borders (window, set_fullscreen);
2950       gtk_action_set_stock_id (priv->menu_fullscreen,
2951           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2952       priv->is_fullscreen = set_fullscreen;
2953   }
2954
2955   return FALSE;
2956 }
2957
2958 static void
2959 empathy_call_window_update_sidebar_buttons (EmpathyCallWindow *window,
2960     gboolean toggled)
2961 {
2962   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2963
2964   /* Update dialpad button */
2965   g_signal_handlers_block_by_func (priv->dialpad_button,
2966       empathy_call_window_dialpad_cb, window);
2967   gtk_toggle_tool_button_set_active (
2968       GTK_TOGGLE_TOOL_BUTTON (priv->dialpad_button),
2969       toggled);
2970   g_signal_handlers_unblock_by_func (priv->dialpad_button,
2971       empathy_call_window_dialpad_cb, window);
2972
2973   /* Update sidebar menu */
2974   g_signal_handlers_block_by_func (priv->menu_sidebar,
2975       empathy_call_window_sidebar_cb, window);
2976   gtk_toggle_action_set_active (
2977       GTK_TOGGLE_ACTION (priv->menu_sidebar),
2978       gtk_widget_get_visible (priv->sidebar));
2979   g_signal_handlers_unblock_by_func (priv->menu_sidebar,
2980       empathy_call_window_sidebar_cb, window);
2981 }
2982
2983 static void
2984 empathy_call_window_show_sidebar (EmpathyCallWindow *window,
2985     gboolean active)
2986 {
2987   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2988   int w, h, sidebar_width, handle_size;
2989   GtkAllocation allocation;
2990   gchar *page;
2991   gboolean dialpad_shown;
2992
2993   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2994   w = allocation.width;
2995   h = allocation.height;
2996
2997   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2998
2999   gtk_widget_get_preferred_width (priv->sidebar, &sidebar_width, NULL);
3000
3001   if (active)
3002     {
3003       gtk_widget_show (priv->sidebar);
3004       w += sidebar_width + handle_size;
3005     }
3006   else
3007     {
3008       w -= sidebar_width + handle_size;
3009       gtk_widget_hide (priv->sidebar);
3010     }
3011
3012   if (w > 0 && h > 0)
3013     gtk_window_resize (GTK_WINDOW (window), w, h);
3014
3015   /* Update the 'Dialpad' menu */
3016   page = ev_sidebar_get_current_page (EV_SIDEBAR (priv->sidebar));
3017   dialpad_shown = active && !tp_strdiff (page, "dialpad");
3018   g_free (page);
3019
3020   empathy_call_window_update_sidebar_buttons (window, dialpad_shown);
3021 }
3022
3023 static void
3024 empathy_call_window_set_send_video (EmpathyCallWindow *window,
3025   CameraState state)
3026 {
3027   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3028   TpyCallChannel *call;
3029
3030   priv->sending_video = (state == CAMERA_STATE_ON);
3031
3032   if (state == CAMERA_STATE_ON)
3033     {
3034       /* When we start sending video, we want to show the video preview by
3035          default. */
3036       display_video_preview (window, TRUE);
3037     }
3038   else
3039     {
3040       display_video_preview (window, FALSE);
3041     }
3042
3043   if (priv->call_state != CONNECTED)
3044     return;
3045
3046   g_object_get (priv->handler, "call-channel", &call, NULL);
3047   DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
3048   tpy_call_channel_send_video (call, priv->sending_video);
3049   g_object_unref (call);
3050 }
3051
3052 static void
3053 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
3054   EmpathyCallWindow *window)
3055 {
3056   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3057   gboolean active;
3058
3059   active = (gtk_toggle_tool_button_get_active (toggle));
3060
3061   if (active)
3062     {
3063       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
3064         priv->volume);
3065       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
3066     }
3067   else
3068     {
3069       /* TODO, Instead of setting the input volume to 0 we should probably
3070        * stop sending but this would cause the audio call to drop if both
3071        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
3072        * in the future. GNOME #574574
3073        */
3074       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
3075         0);
3076       gtk_adjustment_set_value (priv->audio_input_adj, 0);
3077     }
3078 }
3079
3080 static void
3081 empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
3082   EmpathyCallWindow *window)
3083 {
3084   empathy_call_window_show_sidebar (window, FALSE);
3085 }
3086
3087 static void
3088 empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
3089   EmpathyCallWindow *window)
3090 {
3091   empathy_call_window_show_sidebar (window, TRUE);
3092 }
3093
3094 static void
3095 empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar,
3096   const gchar *page,
3097   EmpathyCallWindow *window)
3098 {
3099   empathy_call_window_update_sidebar_buttons (window,
3100       !tp_strdiff (page, "dialpad"));
3101 }
3102
3103 static void
3104 empathy_call_window_hangup_cb (gpointer object,
3105                                EmpathyCallWindow *window)
3106 {
3107   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3108
3109   empathy_call_handler_stop_call (priv->handler);
3110
3111   if (empathy_call_window_disconnected (window, FALSE))
3112     gtk_widget_destroy (GTK_WIDGET (window));
3113 }
3114
3115 static void
3116 empathy_call_window_restart_call (EmpathyCallWindow *window)
3117 {
3118   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3119
3120   /* Remove error info bars */
3121   gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
3122       (GtkCallback) gtk_widget_destroy, NULL);
3123
3124   create_video_output_widget (window);
3125
3126   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
3127       G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
3128
3129   /* While the call was disconnected, the input volume might have changed.
3130    * However, since the audio_input source was destroyed, its volume has not
3131    * been updated during that time. That's why we manually update it here */
3132   empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
3133
3134   priv->outgoing = TRUE;
3135   empathy_call_window_set_state_connecting (window);
3136
3137   if (priv->pipeline_playing)
3138     start_call (window);
3139   else
3140     /* call will be started when the pipeline is ready */
3141     priv->start_call_when_playing = TRUE;
3142
3143   empathy_call_window_setup_avatars (window, priv->handler);
3144
3145   empathy_call_window_show_hangup_button (window, TRUE);
3146 }
3147
3148 static void
3149 empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
3150     EmpathyCallWindow *window)
3151 {
3152   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3153   gboolean active;
3154
3155   active = gtk_toggle_tool_button_get_active (button);
3156
3157   if (active)
3158     ev_sidebar_set_current_page (EV_SIDEBAR (priv->sidebar), "dialpad");
3159
3160   empathy_call_window_show_sidebar (window, active);
3161 }
3162
3163 static void
3164 empathy_call_window_sidebar_cb (GtkToggleAction *menu,
3165     EmpathyCallWindow *self)
3166 {
3167   empathy_call_window_show_sidebar (self,
3168       gtk_toggle_action_get_active (menu));
3169 }
3170
3171 static void
3172 empathy_call_window_fullscreen_cb (gpointer object,
3173                                    EmpathyCallWindow *window)
3174 {
3175   empathy_call_window_fullscreen_toggle (window);
3176 }
3177
3178 static void
3179 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
3180 {
3181   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3182
3183   if (priv->is_fullscreen)
3184     gtk_window_unfullscreen (GTK_WINDOW (window));
3185   else
3186     gtk_window_fullscreen (GTK_WINDOW (window));
3187 }
3188
3189 static gboolean
3190 empathy_call_window_video_button_press_cb (GtkWidget *video_preview,
3191   GdkEventButton *event, EmpathyCallWindow *window)
3192 {
3193   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
3194     {
3195       empathy_call_window_video_menu_popup (window, event->button);
3196       return TRUE;
3197     }
3198
3199   return FALSE;
3200 }
3201
3202 static gboolean
3203 empathy_call_window_key_press_cb (GtkWidget *video_output,
3204   GdkEventKey *event, EmpathyCallWindow *window)
3205 {
3206   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3207
3208   if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
3209     {
3210       /* Since we are in fullscreen mode, toggling will bring us back to
3211          normal mode. */
3212       empathy_call_window_fullscreen_toggle (window);
3213       return TRUE;
3214     }
3215
3216   return FALSE;
3217 }
3218
3219 static gboolean
3220 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
3221     GdkEventMotion *event, EmpathyCallWindow *window)
3222 {
3223   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3224
3225   if (priv->is_fullscreen)
3226     {
3227       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
3228       return TRUE;
3229     }
3230   return FALSE;
3231 }
3232
3233 static void
3234 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
3235   guint button)
3236 {
3237   GtkWidget *menu;
3238   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3239
3240   menu = gtk_ui_manager_get_widget (priv->ui_manager,
3241             "/video-popup");
3242   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
3243       button, gtk_get_current_event_time ());
3244   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
3245 }
3246
3247 static void
3248 empathy_call_window_status_message (EmpathyCallWindow *self,
3249   gchar *message)
3250 {
3251   gtk_label_set_label (GTK_LABEL (self->priv->status_label), message);
3252 }
3253
3254 static void
3255 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
3256   gdouble value, EmpathyCallWindow *window)
3257 {
3258   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3259
3260   if (priv->audio_output == NULL)
3261     return;
3262
3263   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
3264     value);
3265 }
3266
3267 GtkUIManager *
3268 empathy_call_window_get_ui_manager (EmpathyCallWindow *window)
3269 {
3270   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3271
3272   return priv->ui_manager;
3273 }
3274
3275 EmpathyGstAudioSrc *
3276 empathy_call_window_get_audio_src (EmpathyCallWindow *window)
3277 {
3278   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3279
3280   return (EmpathyGstAudioSrc *) priv->audio_input;
3281 }