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