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