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