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