]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
Merge branch 'people-nearby-fake-group-613558'
[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   GstElement *output;
1636
1637   if (priv->funnel == NULL)
1638     {
1639       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1640         (priv->video_output));
1641
1642       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1643
1644       if (!priv->funnel)
1645         {
1646           g_warning ("Could not create fsfunnel");
1647           return NULL;
1648         }
1649
1650       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
1651         {
1652           gst_object_unref (priv->funnel);
1653           priv->funnel = NULL;
1654           g_warning ("Could  not add funnel to pipeline");
1655           return NULL;
1656         }
1657
1658       if (!gst_bin_add (GST_BIN (priv->pipeline), output))
1659         {
1660           g_warning ("Could not add the video output widget to the pipeline");
1661           goto error;
1662         }
1663
1664       if (!gst_element_link (priv->funnel, output))
1665         {
1666           g_warning ("Could not link output sink to funnel");
1667           goto error_output_added;
1668         }
1669
1670       if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1671         {
1672           g_warning ("Could not start video sink");
1673           goto error_output_added;
1674         }
1675
1676       if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1677         {
1678           g_warning ("Could not start funnel");
1679           goto error_output_added;
1680         }
1681     }
1682
1683   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1684
1685   if (!pad)
1686     g_warning ("Could not get request pad from funnel");
1687
1688   return pad;
1689
1690
1691  error_output_added:
1692
1693   gst_element_set_locked_state (priv->funnel, TRUE);
1694   gst_element_set_locked_state (output, TRUE);
1695
1696   gst_element_set_state (priv->funnel, GST_STATE_NULL);
1697   gst_element_set_state (output, GST_STATE_NULL);
1698
1699   gst_bin_remove (GST_BIN (priv->pipeline), output);
1700   gst_element_set_locked_state (output, FALSE);
1701
1702  error:
1703
1704   gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1705   priv->funnel = NULL;
1706
1707   return NULL;
1708 }
1709
1710 /* Called with global lock held */
1711 static GstPad *
1712 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1713 {
1714   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1715   GstPad *pad;
1716   GstElement *filter;
1717   GError *gerror = NULL;
1718
1719   if (priv->liveadder == NULL)
1720     {
1721       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1722
1723       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder))
1724         {
1725           g_warning ("Could not add liveadder to the pipeline");
1726           goto error_add_liveadder;
1727         }
1728       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
1729         {
1730           g_warning ("Could not add audio sink to pipeline");
1731           goto error_add_output;
1732         }
1733
1734       if (gst_element_set_state (priv->liveadder, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1735         {
1736           g_warning ("Could not start liveadder");
1737           goto error;
1738         }
1739
1740       if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1741         {
1742           g_warning ("Could not start audio sink");
1743           goto error;
1744         }
1745
1746       if (GST_PAD_LINK_FAILED (
1747               gst_element_link (priv->liveadder, priv->audio_output)))
1748         {
1749           g_warning ("Could not link liveadder to audio output");
1750           goto error;
1751         }
1752     }
1753
1754   filter = gst_parse_bin_from_description (
1755       "audioconvert ! audioresample ! audioconvert", TRUE, &gerror);
1756   if (filter == NULL)
1757     {
1758       g_warning ("Could not make audio conversion filter: %s", gerror->message);
1759       g_clear_error (&gerror);
1760       goto error;
1761     }
1762
1763   if (!gst_bin_add (GST_BIN (priv->pipeline), filter))
1764     {
1765       g_warning ("Could not add audio conversion filter to pipeline");
1766       gst_object_unref (filter);
1767       goto error;
1768     }
1769
1770   if (gst_element_set_state (filter, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1771     {
1772       g_warning ("Could not start audio conversion filter");
1773       goto error_filter;
1774     }
1775
1776   if (!gst_element_link (filter, priv->liveadder))
1777     {
1778       g_warning ("Could not link audio conversion filter to liveadder");
1779       goto error_filter;
1780     }
1781
1782   pad = gst_element_get_static_pad (filter, "sink");
1783
1784   if (pad == NULL)
1785     {
1786       g_warning ("Could not get sink pad from filter");
1787       goto error_filter;
1788     }
1789
1790   return pad;
1791
1792  error_filter:
1793
1794   gst_element_set_locked_state (filter, TRUE);
1795   gst_element_set_state (filter, GST_STATE_NULL);
1796   gst_bin_remove (GST_BIN (priv->pipeline), filter);
1797
1798  error:
1799
1800   gst_element_set_locked_state (priv->liveadder, TRUE);
1801   gst_element_set_locked_state (priv->audio_output, TRUE);
1802
1803   gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1804   gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1805
1806   gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1807
1808  error_add_output:
1809
1810   gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1811
1812   gst_element_set_locked_state (priv->liveadder, FALSE);
1813   gst_element_set_locked_state (priv->audio_output, FALSE);
1814
1815  error_add_liveadder:
1816
1817   if (priv->liveadder != NULL)
1818     {
1819       gst_object_unref (priv->liveadder);
1820       priv->liveadder = NULL;
1821     }
1822
1823   return NULL;
1824 }
1825
1826 static gboolean
1827 empathy_call_window_update_timer (gpointer user_data)
1828 {
1829   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1830   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1831   gchar *str;
1832   gdouble time_;
1833
1834   time_ = g_timer_elapsed (priv->timer, NULL);
1835
1836   /* Translators: number of minutes:seconds the caller has been connected */
1837   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time_ / 60,
1838     (int) time_ % 60);
1839   empathy_call_window_status_message (self, str);
1840   g_free (str);
1841
1842   return TRUE;
1843 }
1844
1845 static void
1846 display_error (EmpathyCallWindow *self,
1847     EmpathyTpCall *call,
1848     const gchar *img,
1849     const gchar *title,
1850     const gchar *desc,
1851     const gchar *details)
1852 {
1853   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1854   GtkWidget *info_bar;
1855   GtkWidget *content_area;
1856   GtkWidget *hbox;
1857   GtkWidget *vbox;
1858   GtkWidget *image;
1859   GtkWidget *label;
1860   gchar *txt;
1861
1862   /* Create info bar */
1863   info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1864       NULL);
1865
1866   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1867
1868   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1869
1870   /* hbox containing the image and the messages vbox */
1871   hbox = gtk_hbox_new (FALSE, 3);
1872   gtk_container_add (GTK_CONTAINER (content_area), hbox);
1873
1874   /* Add image */
1875   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1876   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1877
1878   /* vbox containing the main message and the details expander */
1879   vbox = gtk_vbox_new (FALSE, 3);
1880   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1881
1882   /* Add text */
1883   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
1884
1885   label = gtk_label_new (NULL);
1886   gtk_label_set_markup (GTK_LABEL (label), txt);
1887   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1888   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1889   g_free (txt);
1890
1891   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1892
1893   /* Add details */
1894   if (details != NULL)
1895     {
1896       GtkWidget *expander;
1897
1898       expander = gtk_expander_new (_("Technical Details"));
1899
1900       txt = g_strdup_printf ("<i>%s</i>", details);
1901
1902       label = gtk_label_new (NULL);
1903       gtk_label_set_markup (GTK_LABEL (label), txt);
1904       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1905       gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1906       g_free (txt);
1907
1908       gtk_container_add (GTK_CONTAINER (expander), label);
1909       gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1910     }
1911
1912   g_signal_connect (info_bar, "response",
1913       G_CALLBACK (gtk_widget_destroy), NULL);
1914
1915   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1916       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1917   gtk_widget_show_all (info_bar);
1918 }
1919
1920 static gchar *
1921 media_stream_error_to_txt (EmpathyCallWindow *self,
1922     EmpathyTpCall *call,
1923     gboolean audio,
1924     TpMediaStreamError error)
1925 {
1926   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1927   const gchar *cm;
1928   gchar *url;
1929   gchar *result;
1930
1931   switch (error)
1932     {
1933       case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1934         if (audio)
1935           return g_strdup_printf (
1936               _("%s's software does not understand any of the audio formats "
1937                 "supported by your computer"),
1938             empathy_contact_get_name (priv->contact));
1939         else
1940           return g_strdup_printf (
1941               _("%s's software does not understand any of the video formats "
1942                 "supported by your computer"),
1943             empathy_contact_get_name (priv->contact));
1944
1945       case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1946         return g_strdup_printf (
1947             _("Can't establish a connection to %s. "
1948               "One of you might be on a network that does not allow "
1949               "direct connections."),
1950           empathy_contact_get_name (priv->contact));
1951
1952       case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
1953           return g_strdup (_("There was a failure on the network"));
1954
1955       case TP_MEDIA_STREAM_ERROR_NO_CODECS:
1956         if (audio)
1957           return g_strdup (_("The audio formats necessary for this call "
1958                 "are not installed on your computer"));
1959         else
1960           return g_strdup (_("The video formats necessary for this call "
1961                 "are not installed on your computer"));
1962
1963       case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
1964         cm = empathy_tp_call_get_connection_manager (call);
1965
1966         url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
1967             "product=Telepathy&amp;component=%s", cm);
1968
1969         result = g_strdup_printf (
1970             _("Something unexpected happened in a Telepathy component. "
1971               "Please <a href=\"%s\">report this bug</a> and attach "
1972               "logs gathered from the 'Debug' window in the Help menu."), url);
1973
1974         g_free (url);
1975         return result;
1976
1977       case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
1978         return g_strdup (_("There was a failure in the call engine"));
1979
1980       default:
1981         return NULL;
1982     }
1983 }
1984
1985 static void
1986 empathy_call_window_stream_error (EmpathyCallWindow *self,
1987     EmpathyTpCall *call,
1988     gboolean audio,
1989     guint code,
1990     const gchar *msg,
1991     const gchar *icon,
1992     const gchar *title)
1993 {
1994   gchar *desc;
1995
1996   desc = media_stream_error_to_txt (self, call, audio, code);
1997   if (desc == NULL)
1998     {
1999       /* No description, use the error message. That's not great as it's not
2000        * localized but it's better than nothing. */
2001       display_error (self, call, icon, title, msg, NULL);
2002     }
2003   else
2004     {
2005       display_error (self, call, icon, title, desc, msg);
2006       g_free (desc);
2007     }
2008 }
2009
2010 static void
2011 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
2012     guint code,
2013     const gchar *msg,
2014     EmpathyCallWindow *self)
2015 {
2016   empathy_call_window_stream_error (self, call, TRUE, code, msg,
2017       "gnome-stock-mic", _("Can't establish audio stream"));
2018 }
2019
2020 static void
2021 empathy_call_window_video_stream_error (EmpathyTpCall *call,
2022     guint code,
2023     const gchar *msg,
2024     EmpathyCallWindow *self)
2025 {
2026   empathy_call_window_stream_error (self, call, FALSE, code, msg,
2027       "camera-web", _("Can't establish video stream"));
2028 }
2029
2030 static gboolean
2031 empathy_call_window_connected (gpointer user_data)
2032 {
2033   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2034   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2035   EmpathyTpCall *call;
2036   gboolean can_send_video;
2037
2038   empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2039
2040   can_send_video = priv->video_input != NULL && priv->contact != NULL &&
2041     empathy_contact_can_voip_video (priv->contact);
2042
2043   g_object_get (priv->handler, "tp-call", &call, NULL);
2044
2045   g_signal_connect (call, "notify::video-stream",
2046     G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
2047
2048   if (empathy_tp_call_has_dtmf (call))
2049     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2050
2051   if (priv->video_input == NULL)
2052     empathy_call_window_set_send_video (self, FALSE);
2053
2054   priv->sending_video = can_send_video ?
2055     empathy_tp_call_is_sending_video (call) : FALSE;
2056
2057   gtk_toggle_tool_button_set_active (
2058       GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
2059       priv->sending_video && priv->video_input != NULL);
2060   gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
2061
2062   gtk_action_set_sensitive (priv->redial, FALSE);
2063   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2064
2065   gtk_widget_set_sensitive (priv->mic_button, TRUE);
2066
2067   /* FIXME: this should won't be needed once bug #602937 is fixed
2068    * (see empathy_call_window_disconnected for details) */
2069   gtk_widget_set_sensitive (priv->tool_button_camera_preview, TRUE);
2070   gtk_action_set_sensitive (priv->action_camera_preview, TRUE);
2071
2072   empathy_call_window_update_avatars_visibility (call, self);
2073
2074   g_object_unref (call);
2075
2076   g_mutex_lock (priv->lock);
2077
2078   priv->timer_id = g_timeout_add_seconds (1,
2079     empathy_call_window_update_timer, self);
2080
2081   g_mutex_unlock (priv->lock);
2082
2083   empathy_call_window_update_timer (self);
2084
2085   return FALSE;
2086 }
2087
2088
2089 /* Called from the streaming thread */
2090 static gboolean
2091 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
2092   GstPad *src, guint media_type, gpointer user_data)
2093 {
2094   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2095   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2096   gboolean retval = FALSE;
2097
2098   GstPad *pad;
2099
2100   g_mutex_lock (priv->lock);
2101
2102   if (priv->call_state != CONNECTED)
2103     {
2104       g_timer_start (priv->timer);
2105       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
2106       priv->call_state = CONNECTED;
2107     }
2108
2109   switch (media_type)
2110     {
2111       case TP_MEDIA_STREAM_TYPE_AUDIO:
2112         pad = empathy_call_window_get_audio_sink_pad (self);
2113         break;
2114       case TP_MEDIA_STREAM_TYPE_VIDEO:
2115         gtk_widget_hide (priv->remote_user_avatar_widget);
2116         gtk_widget_show (priv->video_output);
2117         pad = empathy_call_window_get_video_sink_pad (self);
2118         break;
2119       default:
2120         g_assert_not_reached ();
2121     }
2122
2123   if (pad == NULL)
2124     goto out;
2125
2126   if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2127       g_warning ("Could not link %s sink pad",
2128           media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2129   else
2130       retval = TRUE;
2131
2132   gst_object_unref (pad);
2133
2134  out:
2135
2136   /* If no sink could be linked, try to add fakesink to prevent the whole call
2137    * aborting */
2138
2139   if (!retval)
2140     {
2141       GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2142
2143       if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2144         {
2145           GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2146           if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2147               GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2148             {
2149               gst_element_set_locked_state (fakesink, TRUE);
2150               gst_element_set_state (fakesink, GST_STATE_NULL);
2151               gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2152             }
2153           else
2154             {
2155               g_debug ("Could not link real sink, linked fakesink instead");
2156             }
2157           gst_object_unref (sinkpad);
2158         }
2159       else
2160         {
2161           gst_object_unref (fakesink);
2162         }
2163     }
2164
2165
2166   g_mutex_unlock (priv->lock);
2167
2168   return TRUE;
2169 }
2170
2171 static gboolean
2172 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
2173   GstPad *sink, guint media_type, gpointer user_data)
2174 {
2175   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2176   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2177   GstPad *pad;
2178   gboolean retval = FALSE;
2179
2180   switch (media_type)
2181     {
2182       case TP_MEDIA_STREAM_TYPE_AUDIO:
2183         if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2184           {
2185             g_warning ("Could not add audio source to pipeline");
2186             break;
2187           }
2188
2189         pad = gst_element_get_static_pad (priv->audio_input, "src");
2190         if (!pad)
2191           {
2192             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2193             g_warning ("Could not get source pad from audio source");
2194             break;
2195           }
2196
2197         if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2198           {
2199             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2200             g_warning ("Could not link audio source to farsight");
2201             break;
2202           }
2203
2204         if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2205           {
2206             g_warning ("Could not start audio source");
2207             gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2208             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2209             break;
2210           }
2211
2212         retval = TRUE;
2213         break;
2214       case TP_MEDIA_STREAM_TYPE_VIDEO:
2215         if (priv->video_input != NULL)
2216           {
2217             if (priv->video_tee != NULL)
2218               {
2219                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2220                 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2221                   {
2222                     g_warning ("Could not link videp soure input pipeline");
2223                     break;
2224                   }
2225               }
2226
2227             retval = TRUE;
2228           }
2229         break;
2230       default:
2231         g_assert_not_reached ();
2232     }
2233
2234   return retval;
2235 }
2236
2237 static void
2238 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2239 {
2240   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2241   GstElement *preview;
2242
2243   DEBUG ("remove video input");
2244   preview = empathy_video_widget_get_element (
2245     EMPATHY_VIDEO_WIDGET (priv->video_preview));
2246
2247   gst_element_set_state (priv->video_input, GST_STATE_NULL);
2248   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2249   gst_element_set_state (preview, GST_STATE_NULL);
2250
2251   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2252     priv->video_tee, preview, NULL);
2253
2254   g_object_unref (priv->video_input);
2255   priv->video_input = NULL;
2256   g_object_unref (priv->video_tee);
2257   priv->video_tee = NULL;
2258   gtk_widget_destroy (priv->video_preview);
2259   priv->video_preview = NULL;
2260
2261   gtk_toggle_tool_button_set_active (
2262       GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
2263   gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
2264
2265   gtk_widget_show (priv->self_user_avatar_widget);
2266 }
2267
2268 static void
2269 start_call (EmpathyCallWindow *self)
2270 {
2271   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2272
2273   priv->call_started = TRUE;
2274   empathy_call_handler_start_call (priv->handler);
2275   gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2276
2277   if (empathy_call_handler_has_initial_video (priv->handler))
2278     {
2279       /* Enable 'send video' buttons and display the preview */
2280       gtk_toggle_tool_button_set_active (
2281           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
2282     }
2283 }
2284
2285 static gboolean
2286 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2287   gpointer user_data)
2288 {
2289   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2290   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2291   GstState newstate;
2292
2293   empathy_call_handler_bus_message (priv->handler, bus, message);
2294
2295   switch (GST_MESSAGE_TYPE (message))
2296     {
2297       case GST_MESSAGE_STATE_CHANGED:
2298         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2299           {
2300             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2301             if (newstate == GST_STATE_PAUSED)
2302                 empathy_call_window_setup_video_input (self);
2303           }
2304         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2305             !priv->call_started)
2306           {
2307             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2308             if (newstate == GST_STATE_PAUSED)
2309               {
2310                 start_call (self);
2311               }
2312           }
2313         break;
2314       case GST_MESSAGE_ERROR:
2315         {
2316           GError *error = NULL;
2317           GstElement *gst_error;
2318           gchar *debug;
2319
2320           gst_message_parse_error (message, &error, &debug);
2321           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2322
2323           g_message ("Element error: %s -- %s\n", error->message, debug);
2324
2325           if (g_str_has_prefix (gst_element_get_name (gst_error),
2326                 VIDEO_INPUT_ERROR_PREFIX))
2327             {
2328               /* Remove the video input and continue */
2329               if (priv->video_input != NULL)
2330                 empathy_call_window_remove_video_input (self);
2331               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2332             }
2333           else
2334             {
2335               empathy_call_window_disconnected (self);
2336             }
2337           g_error_free (error);
2338           g_free (debug);
2339         }
2340       default:
2341         break;
2342     }
2343
2344   return TRUE;
2345 }
2346
2347 static void
2348 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
2349     EmpathyCallWindow *window)
2350 {
2351   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2352
2353   if (empathy_tp_call_is_receiving_video (call))
2354     {
2355       gtk_widget_hide (priv->remote_user_avatar_widget);
2356       gtk_widget_show (priv->video_output);
2357     }
2358   else
2359     {
2360       gtk_widget_hide (priv->video_output);
2361       gtk_widget_show (priv->remote_user_avatar_widget);
2362     }
2363 }
2364
2365 static void
2366 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
2367     GParamSpec *spec,
2368     EmpathyCallWindow *self)
2369 {
2370   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2371   EmpathyTpCall *call;
2372
2373   g_object_get (priv->handler, "tp-call", &call, NULL);
2374   if (call == NULL)
2375     return;
2376
2377   empathy_signal_connect_weak (call, "audio-stream-error",
2378       G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
2379   empathy_signal_connect_weak (call, "video-stream-error",
2380       G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
2381
2382   g_object_unref (call);
2383 }
2384
2385 static void
2386 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2387 {
2388   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2389   EmpathyTpCall *call;
2390
2391   g_signal_connect (priv->handler, "conference-added",
2392     G_CALLBACK (empathy_call_window_conference_added_cb), window);
2393   g_signal_connect (priv->handler, "request-resource",
2394     G_CALLBACK (empathy_call_window_request_resource_cb), window);
2395   g_signal_connect (priv->handler, "closed",
2396     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2397   g_signal_connect (priv->handler, "src-pad-added",
2398     G_CALLBACK (empathy_call_window_src_added_cb), window);
2399   g_signal_connect (priv->handler, "sink-pad-added",
2400     G_CALLBACK (empathy_call_window_sink_added_cb), window);
2401   g_signal_connect (priv->handler, "stream-closed",
2402     G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
2403
2404   g_object_get (priv->handler, "tp-call", &call, NULL);
2405   if (call != NULL)
2406     {
2407       empathy_signal_connect_weak (call, "audio-stream-error",
2408         G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
2409       empathy_signal_connect_weak (call, "video-stream-error",
2410         G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
2411
2412       g_object_unref (call);
2413     }
2414   else
2415     {
2416       /* tp-call doesn't exist yet, we'll connect signals once it has been
2417        * set */
2418       g_signal_connect (priv->handler, "notify::tp-call",
2419         G_CALLBACK (call_handler_notify_tp_call_cb), window);
2420     }
2421
2422   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2423 }
2424
2425 static gboolean
2426 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2427   EmpathyCallWindow *window)
2428 {
2429   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2430
2431   if (priv->pipeline != NULL)
2432     {
2433       if (priv->bus_message_source_id != 0)
2434         {
2435           g_source_remove (priv->bus_message_source_id);
2436           priv->bus_message_source_id = 0;
2437         }
2438
2439       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2440     }
2441
2442   if (priv->call_state == CONNECTING)
2443     empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2444
2445   return FALSE;
2446 }
2447
2448 static void
2449 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2450 {
2451   GtkWidget *menu;
2452   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2453
2454   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2455             "/menubar1");
2456
2457   if (set_fullscreen)
2458     {
2459       gtk_widget_hide (priv->sidebar);
2460       gtk_widget_hide (menu);
2461       gtk_widget_hide (priv->vbox);
2462       gtk_widget_hide (priv->statusbar);
2463       gtk_widget_hide (priv->toolbar);
2464     }
2465   else
2466     {
2467       if (priv->sidebar_was_visible_before_fs)
2468         gtk_widget_show (priv->sidebar);
2469
2470       gtk_widget_show (menu);
2471       gtk_widget_show (priv->vbox);
2472       gtk_widget_show (priv->statusbar);
2473       gtk_widget_show (priv->toolbar);
2474
2475       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2476           priv->original_height_before_fs);
2477     }
2478 }
2479
2480 static void
2481 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2482 {
2483   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2484
2485   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2486       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2487   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2488       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2489   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2490       priv->video_output, TRUE, TRUE,
2491       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2492       GTK_PACK_START);
2493   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2494       priv->vbox, TRUE, TRUE,
2495       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2496       GTK_PACK_START);
2497 }
2498
2499 static gboolean
2500 empathy_call_window_state_event_cb (GtkWidget *widget,
2501   GdkEventWindowState *event, EmpathyCallWindow *window)
2502 {
2503   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2504     {
2505       EmpathyCallWindowPriv *priv = GET_PRIV (window);
2506       gboolean set_fullscreen = event->new_window_state &
2507         GDK_WINDOW_STATE_FULLSCREEN;
2508
2509       if (set_fullscreen)
2510         {
2511           gboolean sidebar_was_visible;
2512           GtkAllocation allocation;
2513           gint original_width, original_height;
2514
2515           gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2516           original_width = allocation.width;
2517           original_height = allocation.height;
2518
2519           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2520
2521           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2522           priv->original_width_before_fs = original_width;
2523           priv->original_height_before_fs = original_height;
2524
2525           if (priv->video_output_motion_handler_id == 0 &&
2526                 priv->video_output != NULL)
2527             {
2528               priv->video_output_motion_handler_id = g_signal_connect (
2529                   G_OBJECT (priv->video_output), "motion-notify-event",
2530                   G_CALLBACK (empathy_call_window_video_output_motion_notify),
2531                   window);
2532             }
2533         }
2534       else
2535         {
2536           if (priv->video_output_motion_handler_id != 0)
2537             {
2538               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2539                   priv->video_output_motion_handler_id);
2540               priv->video_output_motion_handler_id = 0;
2541             }
2542         }
2543
2544       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2545           set_fullscreen);
2546       show_controls (window, set_fullscreen);
2547       show_borders (window, set_fullscreen);
2548       gtk_action_set_stock_id (priv->menu_fullscreen,
2549           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2550       priv->is_fullscreen = set_fullscreen;
2551   }
2552
2553   return FALSE;
2554 }
2555
2556 static void
2557 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2558   EmpathyCallWindow *window)
2559 {
2560   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2561   GtkWidget *arrow;
2562   int w, h, handle_size;
2563   GtkAllocation allocation, sidebar_allocation;
2564
2565   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2566   w = allocation.width;
2567   h = allocation.height;
2568
2569   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2570
2571   gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2572   if (gtk_toggle_button_get_active (toggle))
2573     {
2574       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2575       gtk_widget_show (priv->sidebar);
2576       w += sidebar_allocation.width + handle_size;
2577     }
2578   else
2579     {
2580       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2581       w -= sidebar_allocation.width + handle_size;
2582       gtk_widget_hide (priv->sidebar);
2583     }
2584
2585   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2586
2587   if (w > 0 && h > 0)
2588     gtk_window_resize (GTK_WINDOW (window), w, h);
2589 }
2590
2591 static void
2592 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2593   gboolean send)
2594 {
2595   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2596   EmpathyTpCall *call;
2597
2598   priv->sending_video = send;
2599
2600   /* When we start sending video, we want to show the video preview by
2601      default. */
2602   display_video_preview (window, send);
2603
2604   if (priv->call_state != CONNECTED)
2605     return;
2606
2607   g_object_get (priv->handler, "tp-call", &call, NULL);
2608   DEBUG ("%s sending video", send ? "start": "stop");
2609   empathy_tp_call_request_video_stream_direction (call, send);
2610   g_object_unref (call);
2611 }
2612
2613 static void
2614 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2615   EmpathyCallWindow *window)
2616 {
2617   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2618   gboolean active;
2619
2620   if (priv->audio_input == NULL)
2621     return;
2622
2623   active = (gtk_toggle_tool_button_get_active (toggle));
2624
2625   if (active)
2626     {
2627       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2628         priv->volume);
2629       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2630     }
2631   else
2632     {
2633       /* TODO, Instead of setting the input volume to 0 we should probably
2634        * stop sending but this would cause the audio call to drop if both
2635        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2636        * in the future. GNOME #574574
2637        */
2638       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2639         0);
2640       gtk_adjustment_set_value (priv->audio_input_adj, 0);
2641     }
2642 }
2643
2644 static void
2645 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2646   EmpathyCallWindow *window)
2647 {
2648   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2649
2650   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2651     FALSE);
2652 }
2653
2654 static void
2655 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2656   EmpathyCallWindow *window)
2657 {
2658   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2659
2660   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2661     TRUE);
2662 }
2663
2664 static void
2665 empathy_call_window_hangup_cb (gpointer object,
2666                                EmpathyCallWindow *window)
2667 {
2668   if (empathy_call_window_disconnected (window))
2669     gtk_widget_destroy (GTK_WIDGET (window));
2670 }
2671
2672 static void
2673 empathy_call_window_restart_call (EmpathyCallWindow *window)
2674 {
2675   GstBus *bus;
2676   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2677
2678   gtk_widget_destroy (priv->remote_user_output_hbox);
2679   gtk_widget_destroy (priv->self_user_output_hbox);
2680
2681   priv->pipeline = gst_pipeline_new (NULL);
2682   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2683   priv->bus_message_source_id = gst_bus_add_watch (bus,
2684       empathy_call_window_bus_message, window);
2685
2686   empathy_call_window_setup_remote_frame (bus, window);
2687   empathy_call_window_setup_self_frame (bus, window);
2688
2689   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2690       G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2691
2692   /* While the call was disconnected, the input volume might have changed.
2693    * However, since the audio_input source was destroyed, its volume has not
2694    * been updated during that time. That's why we manually update it here */
2695   empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2696
2697   g_object_unref (bus);
2698
2699   gtk_widget_show_all (priv->content_hbox);
2700
2701   priv->outgoing = TRUE;
2702   empathy_call_window_set_state_connecting (window);
2703
2704   start_call (window);
2705   empathy_call_window_setup_avatars (window, priv->handler);
2706
2707   gtk_action_set_sensitive (priv->redial, FALSE);
2708   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2709 }
2710
2711 static void
2712 empathy_call_window_redial_cb (gpointer object,
2713     EmpathyCallWindow *window)
2714 {
2715   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2716
2717   if (priv->call_state == CONNECTED)
2718     priv->call_state = REDIALING;
2719
2720   empathy_call_handler_stop_call (priv->handler);
2721
2722   if (priv->call_state != CONNECTED)
2723     empathy_call_window_restart_call (window);
2724 }
2725
2726 static void
2727 empathy_call_window_fullscreen_cb (gpointer object,
2728                                    EmpathyCallWindow *window)
2729 {
2730   empathy_call_window_fullscreen_toggle (window);
2731 }
2732
2733 static void
2734 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2735 {
2736   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2737
2738   if (priv->is_fullscreen)
2739     gtk_window_unfullscreen (GTK_WINDOW (window));
2740   else
2741     gtk_window_fullscreen (GTK_WINDOW (window));
2742 }
2743
2744 static gboolean
2745 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2746   GdkEventButton *event, EmpathyCallWindow *window)
2747 {
2748   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2749     {
2750       empathy_call_window_video_menu_popup (window, event->button);
2751       return TRUE;
2752     }
2753
2754   return FALSE;
2755 }
2756
2757 static gboolean
2758 empathy_call_window_key_press_cb (GtkWidget *video_output,
2759   GdkEventKey *event, EmpathyCallWindow *window)
2760 {
2761   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2762
2763   if (priv->is_fullscreen && event->keyval == GDK_Escape)
2764     {
2765       /* Since we are in fullscreen mode, toggling will bring us back to
2766          normal mode. */
2767       empathy_call_window_fullscreen_toggle (window);
2768       return TRUE;
2769     }
2770
2771   return FALSE;
2772 }
2773
2774 static gboolean
2775 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2776     GdkEventMotion *event, EmpathyCallWindow *window)
2777 {
2778   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2779
2780   if (priv->is_fullscreen)
2781     {
2782       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2783       return TRUE;
2784     }
2785   return FALSE;
2786 }
2787
2788 static void
2789 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2790   guint button)
2791 {
2792   GtkWidget *menu;
2793   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2794
2795   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2796             "/video-popup");
2797   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2798       button, gtk_get_current_event_time ());
2799   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2800 }
2801
2802 static void
2803 empathy_call_window_status_message (EmpathyCallWindow *window,
2804   gchar *message)
2805 {
2806   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2807
2808   if (priv->context_id == 0)
2809     {
2810       priv->context_id = gtk_statusbar_get_context_id (
2811         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2812     }
2813   else
2814     {
2815       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2816     }
2817
2818   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2819     message);
2820 }
2821
2822 static void
2823 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2824   gdouble value, EmpathyCallWindow *window)
2825 {
2826   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2827
2828   if (priv->audio_output == NULL)
2829     return;
2830
2831   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2832     value);
2833 }
2834
2835 /* block all the signals related to camera control widgets. This is useful
2836  * when we are manually updating the UI and so don't want to fire the
2837  * callbacks */
2838 static void
2839 block_camera_control_signals (EmpathyCallWindow *self)
2840 {
2841   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2842
2843   g_signal_handlers_block_by_func (priv->tool_button_camera_off,
2844       tool_button_camera_off_toggled_cb, self);
2845   g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
2846       tool_button_camera_preview_toggled_cb, self);
2847   g_signal_handlers_block_by_func (priv->tool_button_camera_on,
2848       tool_button_camera_on_toggled_cb, self);
2849   g_signal_handlers_block_by_func (priv->action_camera,
2850       action_camera_change_cb, self);
2851 }
2852
2853 static void
2854 unblock_camera_control_signals (EmpathyCallWindow *self)
2855 {
2856   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2857
2858   g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
2859       tool_button_camera_off_toggled_cb, self);
2860   g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
2861       tool_button_camera_preview_toggled_cb, self);
2862   g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
2863       tool_button_camera_on_toggled_cb, self);
2864   g_signal_handlers_unblock_by_func (priv->action_camera,
2865       action_camera_change_cb, self);
2866 }