]> 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   priv->dtmf_panel = empathy_call_window_create_dtmf (self);
1041   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
1042     priv->dtmf_panel);
1043
1044   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1045
1046   page = empathy_call_window_create_audio_input (self);
1047   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
1048     page);
1049
1050   page = empathy_call_window_create_video_input (self);
1051   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
1052     page);
1053
1054   gtk_widget_show_all (top_vbox);
1055
1056   gtk_widget_hide (priv->sidebar);
1057
1058   priv->fullscreen = empathy_call_window_fullscreen_new (self);
1059   empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1060       priv->video_output);
1061   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1062       "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1063
1064   g_signal_connect (G_OBJECT (self), "realize",
1065     G_CALLBACK (empathy_call_window_realized_cb), self);
1066
1067   g_signal_connect (G_OBJECT (self), "delete-event",
1068     G_CALLBACK (empathy_call_window_delete_cb), self);
1069
1070   g_signal_connect (G_OBJECT (self), "window-state-event",
1071     G_CALLBACK (empathy_call_window_state_event_cb), self);
1072
1073   g_signal_connect (G_OBJECT (self), "key-press-event",
1074       G_CALLBACK (empathy_call_window_key_press_cb), self);
1075
1076   priv->timer = g_timer_new ();
1077
1078   g_object_ref (priv->ui_manager);
1079   g_object_unref (gui);
1080
1081   empathy_geometry_bind (GTK_WINDOW (self), "call-window");
1082 }
1083
1084 /* Instead of specifying a width and a height, we specify only one size. That's
1085    because we want a square avatar icon.  */
1086 static void
1087 init_contact_avatar_with_size (EmpathyContact *contact,
1088     GtkWidget *image_widget,
1089     gint size)
1090 {
1091   GdkPixbuf *pixbuf_avatar = NULL;
1092
1093   if (contact != NULL)
1094     {
1095       pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1096         size, size);
1097     }
1098
1099   if (pixbuf_avatar == NULL)
1100     {
1101       pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
1102           size);
1103     }
1104
1105   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1106 }
1107
1108 static void
1109 set_window_title (EmpathyCallWindow *self)
1110 {
1111   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1112   gchar *tmp;
1113
1114   /* translators: Call is a noun and %s is the contact name. This string
1115    * is used in the window title */
1116   tmp = g_strdup_printf (_("Call with %s"),
1117       empathy_contact_get_name (priv->contact));
1118   gtk_window_set_title (GTK_WINDOW (self), tmp);
1119   g_free (tmp);
1120 }
1121
1122 static void
1123 contact_name_changed_cb (EmpathyContact *contact,
1124     GParamSpec *pspec, EmpathyCallWindow *self)
1125 {
1126   set_window_title (self);
1127 }
1128
1129 static void
1130 contact_avatar_changed_cb (EmpathyContact *contact,
1131     GParamSpec *pspec, GtkWidget *avatar_widget)
1132 {
1133   int size;
1134
1135   size = avatar_widget->allocation.height;
1136
1137   if (size == 0)
1138     {
1139       /* the widget is not allocated yet, set a default size */
1140       size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1141           REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1142     }
1143
1144   init_contact_avatar_with_size (contact, avatar_widget, size);
1145 }
1146
1147 static void
1148 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
1149     EmpathyContact *contact, const GError *error, gpointer user_data,
1150     GObject *weak_object)
1151 {
1152   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1153   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1154
1155   init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1156       MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1157
1158   g_signal_connect (contact, "notify::avatar",
1159       G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1160 }
1161
1162 static void
1163 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1164     EmpathyCallHandler *handler)
1165 {
1166   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1167
1168   g_object_get (handler, "contact", &(priv->contact), NULL);
1169
1170   if (priv->contact != NULL)
1171     {
1172       TpConnection *connection;
1173       EmpathyTpContactFactory *factory;
1174
1175       set_window_title (self);
1176
1177       g_signal_connect (priv->contact, "notify::name",
1178           G_CALLBACK (contact_name_changed_cb), self);
1179       g_signal_connect (priv->contact, "notify::avatar",
1180           G_CALLBACK (contact_avatar_changed_cb),
1181           priv->remote_user_avatar_widget);
1182
1183       /* Retreiving the self avatar */
1184       connection = empathy_contact_get_connection (priv->contact);
1185       factory = empathy_tp_contact_factory_dup_singleton (connection);
1186       empathy_tp_contact_factory_get_from_handle (factory,
1187           tp_connection_get_self_handle (connection),
1188           empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1189
1190       g_object_unref (factory);
1191     }
1192   else
1193     {
1194       g_warning ("call handler doesn't have a contact");
1195       /* translators: Call is a noun. This string is used in the window
1196        * title */
1197       gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1198
1199       /* Since we can't access the remote contact, we can't get a connection
1200          to it and can't get the self contact (and its avatar). This means
1201          that we have to manually set the self avatar. */
1202       init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1203           MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1204     }
1205
1206   init_contact_avatar_with_size (priv->contact,
1207       priv->remote_user_avatar_widget,
1208       MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1209           REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1210
1211   /* The remote avatar is shown by default and will be hidden when we receive
1212      video from the remote side. */
1213   gtk_widget_hide (priv->video_output);
1214   gtk_widget_show (priv->remote_user_avatar_widget);
1215 }
1216
1217 static void
1218 empathy_call_window_constructed (GObject *object)
1219 {
1220   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1221   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1222   EmpathyTpCall *call;
1223
1224   g_assert (priv->handler != NULL);
1225
1226   g_object_get (priv->handler, "tp-call", &call, NULL);
1227   priv->outgoing = (call == NULL);
1228   if (call != NULL)
1229     g_object_unref (call);
1230
1231   empathy_call_window_setup_avatars (self, priv->handler);
1232   empathy_call_window_set_state_connecting (self);
1233
1234   if (!empathy_call_handler_has_initial_video (priv->handler))
1235     {
1236       gtk_toggle_tool_button_set_active (
1237           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1238     }
1239   /* If call has InitialVideo, the preview will be started once the call has
1240    * been started (start_call()). */
1241 }
1242
1243 static void empathy_call_window_dispose (GObject *object);
1244 static void empathy_call_window_finalize (GObject *object);
1245
1246 static void
1247 empathy_call_window_set_property (GObject *object,
1248   guint property_id, const GValue *value, GParamSpec *pspec)
1249 {
1250   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1251
1252   switch (property_id)
1253     {
1254       case PROP_CALL_HANDLER:
1255         priv->handler = g_value_dup_object (value);
1256         break;
1257       default:
1258         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1259     }
1260 }
1261
1262 static void
1263 empathy_call_window_get_property (GObject *object,
1264   guint property_id, GValue *value, GParamSpec *pspec)
1265 {
1266   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1267
1268   switch (property_id)
1269     {
1270       case PROP_CALL_HANDLER:
1271         g_value_set_object (value, priv->handler);
1272         break;
1273       default:
1274         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1275     }
1276 }
1277
1278 static void
1279 empathy_call_window_class_init (
1280   EmpathyCallWindowClass *empathy_call_window_class)
1281 {
1282   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1283   GParamSpec *param_spec;
1284
1285   g_type_class_add_private (empathy_call_window_class,
1286     sizeof (EmpathyCallWindowPriv));
1287
1288   object_class->constructed = empathy_call_window_constructed;
1289   object_class->set_property = empathy_call_window_set_property;
1290   object_class->get_property = empathy_call_window_get_property;
1291
1292   object_class->dispose = empathy_call_window_dispose;
1293   object_class->finalize = empathy_call_window_finalize;
1294
1295   param_spec = g_param_spec_object ("handler",
1296     "handler", "The call handler",
1297     EMPATHY_TYPE_CALL_HANDLER,
1298     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1299   g_object_class_install_property (object_class,
1300     PROP_CALL_HANDLER, param_spec);
1301 }
1302
1303 static void
1304 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1305     GParamSpec *property, EmpathyCallWindow *self)
1306 {
1307   DEBUG ("video stream changed");
1308   empathy_call_window_update_avatars_visibility (call, self);
1309 }
1310
1311 void
1312 empathy_call_window_dispose (GObject *object)
1313 {
1314   EmpathyTpCall *call;
1315   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1316   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1317
1318   if (priv->dispose_has_run)
1319     return;
1320
1321   priv->dispose_has_run = TRUE;
1322
1323   g_object_get (priv->handler, "tp-call", &call, NULL);
1324
1325   if (call != NULL)
1326     {
1327       g_signal_handlers_disconnect_by_func (call,
1328         empathy_call_window_video_stream_changed_cb, object);
1329       g_object_unref (call);
1330     }
1331
1332   if (priv->handler != NULL)
1333     g_object_unref (priv->handler);
1334   priv->handler = NULL;
1335
1336   if (priv->pipeline != NULL)
1337     g_object_unref (priv->pipeline);
1338   priv->pipeline = NULL;
1339
1340   if (priv->video_input != NULL)
1341     g_object_unref (priv->video_input);
1342   priv->video_input = NULL;
1343
1344   if (priv->audio_input != NULL)
1345     g_object_unref (priv->audio_input);
1346   priv->audio_input = NULL;
1347
1348   if (priv->audio_output != NULL)
1349     g_object_unref (priv->audio_output);
1350   priv->audio_output = NULL;
1351
1352   if (priv->video_tee != NULL)
1353     g_object_unref (priv->video_tee);
1354   priv->video_tee = NULL;
1355
1356   if (priv->fsnotifier != NULL)
1357     g_object_unref (priv->fsnotifier);
1358   priv->fsnotifier = NULL;
1359
1360   if (priv->timer_id != 0)
1361     g_source_remove (priv->timer_id);
1362   priv->timer_id = 0;
1363
1364   if (priv->ui_manager != NULL)
1365     g_object_unref (priv->ui_manager);
1366   priv->ui_manager = NULL;
1367
1368   if (priv->contact != NULL)
1369     {
1370       g_signal_handlers_disconnect_by_func (priv->contact,
1371           contact_name_changed_cb, self);
1372       g_object_unref (priv->contact);
1373       priv->contact = NULL;
1374     }
1375
1376   /* release any references held by the object here */
1377   if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1378     G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1379 }
1380
1381 void
1382 empathy_call_window_finalize (GObject *object)
1383 {
1384   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1385   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1386
1387   if (priv->video_output_motion_handler_id != 0)
1388     {
1389       g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1390           priv->video_output_motion_handler_id);
1391       priv->video_output_motion_handler_id = 0;
1392     }
1393
1394   if (priv->bus_message_source_id != 0)
1395     {
1396       g_source_remove (priv->bus_message_source_id);
1397       priv->bus_message_source_id = 0;
1398     }
1399
1400   /* free any data held directly by the object here */
1401   g_mutex_free (priv->lock);
1402
1403   g_timer_destroy (priv->timer);
1404
1405   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1406 }
1407
1408
1409 EmpathyCallWindow *
1410 empathy_call_window_new (EmpathyCallHandler *handler)
1411 {
1412   return EMPATHY_CALL_WINDOW (
1413     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1414 }
1415
1416 static void
1417 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1418   GstElement *conference, gpointer user_data)
1419 {
1420   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1421   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1422
1423   gst_bin_add (GST_BIN (priv->pipeline), conference);
1424
1425   gst_element_set_state (conference, GST_STATE_PLAYING);
1426 }
1427
1428 static gboolean
1429 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1430   FsMediaType type, FsStreamDirection direction, gpointer user_data)
1431 {
1432   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1433   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1434
1435   if (type != FS_MEDIA_TYPE_VIDEO)
1436     return TRUE;
1437
1438   if (direction == FS_DIRECTION_RECV)
1439     return TRUE;
1440
1441   /* video and direction is send */
1442   return priv->video_input != NULL;
1443 }
1444
1445 static gboolean
1446 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1447 {
1448   GstStateChangeReturn state_change_return;
1449   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1450
1451   if (priv->pipeline == NULL)
1452     return TRUE;
1453
1454   if (priv->bus_message_source_id != 0)
1455     {
1456       g_source_remove (priv->bus_message_source_id);
1457       priv->bus_message_source_id = 0;
1458     }
1459
1460   state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1461
1462   if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1463         state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1464     {
1465       if (priv->pipeline != NULL)
1466         g_object_unref (priv->pipeline);
1467       priv->pipeline = NULL;
1468
1469       if (priv->video_input != NULL)
1470         g_object_unref (priv->video_input);
1471       priv->video_input = NULL;
1472
1473       if (priv->audio_input != NULL)
1474         g_object_unref (priv->audio_input);
1475       priv->audio_input = NULL;
1476
1477       g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1478           empathy_call_window_mic_volume_changed_cb, self);
1479
1480       if (priv->audio_output != NULL)
1481         g_object_unref (priv->audio_output);
1482       priv->audio_output = NULL;
1483
1484       if (priv->video_tee != NULL)
1485         g_object_unref (priv->video_tee);
1486       priv->video_tee = NULL;
1487
1488       if (priv->video_preview != NULL)
1489         gtk_widget_destroy (priv->video_preview);
1490       priv->video_preview = NULL;
1491
1492       priv->liveadder = NULL;
1493       priv->funnel = NULL;
1494
1495       return TRUE;
1496     }
1497   else
1498     {
1499       g_message ("Error: could not destroy pipeline. Closing call window");
1500       gtk_widget_destroy (GTK_WIDGET (self));
1501
1502       return FALSE;
1503     }
1504 }
1505
1506 static gboolean
1507 empathy_call_window_disconnected (EmpathyCallWindow *self)
1508 {
1509   gboolean could_disconnect = FALSE;
1510   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1511   gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1512
1513   if (priv->call_state == CONNECTING)
1514       empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1515
1516   if (priv->call_state != REDIALING)
1517     priv->call_state = DISCONNECTED;
1518
1519   if (could_reset_pipeline)
1520     {
1521       g_mutex_lock (priv->lock);
1522
1523       g_timer_stop (priv->timer);
1524
1525       if (priv->timer_id != 0)
1526         g_source_remove (priv->timer_id);
1527       priv->timer_id = 0;
1528
1529       g_mutex_unlock (priv->lock);
1530
1531       empathy_call_window_status_message (self, _("Disconnected"));
1532
1533       gtk_action_set_sensitive (priv->redial, TRUE);
1534       gtk_widget_set_sensitive (priv->redial_button, TRUE);
1535
1536       /* Reseting the send_video, camera_buton and mic_button to their
1537          initial state */
1538       gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1539       gtk_widget_set_sensitive (priv->mic_button, FALSE);
1540       gtk_toggle_tool_button_set_active (
1541           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1542       gtk_toggle_tool_button_set_active (
1543           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1544
1545       /* FIXME: This is to workaround the fact that the pipeline has been
1546        * destroyed and so we can't display preview until a new call (and so a
1547        * new pipeline) is created. We should fix this properly by refactoring
1548        * the code managing the pipeline. This is bug #602937 */
1549       gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
1550       gtk_action_set_sensitive (priv->action_camera_preview, FALSE);
1551
1552       gtk_progress_bar_set_fraction (
1553           GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1554
1555       gtk_widget_hide (priv->video_output);
1556       gtk_widget_show (priv->remote_user_avatar_widget);
1557
1558       priv->sending_video = FALSE;
1559       priv->call_started = FALSE;
1560
1561       could_disconnect = TRUE;
1562
1563       /* TODO: display the self avatar of the preview (depends if the "Always
1564        * Show Video Preview" is enabled or not) */
1565     }
1566
1567   return could_disconnect;
1568 }
1569
1570
1571 static void
1572 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1573     gpointer user_data)
1574 {
1575   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1576   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1577
1578   if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1579       empathy_call_window_restart_call (self);
1580 }
1581
1582
1583 static void
1584 empathy_call_window_channel_stream_closed_cb (EmpathyCallHandler *handler,
1585     TfStream *stream, gpointer user_data)
1586 {
1587   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1588   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1589   guint media_type;
1590
1591   g_object_get (stream, "media-type", &media_type, NULL);
1592
1593   /*
1594    * This assumes that there is only one video stream per channel...
1595    */
1596
1597   if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1598     {
1599       if (priv->funnel != NULL)
1600         {
1601           GstElement *output;
1602
1603           output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1604               (priv->video_output));
1605
1606           gst_element_set_state (output, GST_STATE_NULL);
1607           gst_element_set_state (priv->funnel, GST_STATE_NULL);
1608
1609           gst_bin_remove (GST_BIN (priv->pipeline), output);
1610           gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1611           priv->funnel = NULL;
1612         }
1613     }
1614   else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1615     {
1616       if (priv->liveadder != NULL)
1617         {
1618           gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1619           gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1620
1621           gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1622           gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1623           priv->liveadder = NULL;
1624         }
1625     }
1626 }
1627
1628 /* Called with global lock held */
1629 static GstPad *
1630 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1631 {
1632   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1633   GstPad *pad;
1634
1635   if (priv->funnel == NULL)
1636     {
1637       GstElement *output;
1638
1639       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1640         (priv->video_output));
1641
1642       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1643
1644       gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1645       gst_bin_add (GST_BIN (priv->pipeline), output);
1646
1647       gst_element_link (priv->funnel, output);
1648
1649       gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1650       gst_element_set_state (output, GST_STATE_PLAYING);
1651     }
1652
1653   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1654
1655   return pad;
1656 }
1657
1658 /* Called with global lock held */
1659 static GstPad *
1660 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1661 {
1662   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1663   GstPad *pad;
1664
1665   if (priv->liveadder == NULL)
1666     {
1667       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1668
1669       gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1670       gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1671
1672       gst_element_link (priv->liveadder, priv->audio_output);
1673
1674       gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1675       gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1676     }
1677
1678   pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1679
1680   return pad;
1681 }
1682
1683 static gboolean
1684 empathy_call_window_update_timer (gpointer user_data)
1685 {
1686   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1687   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1688   gchar *str;
1689   gdouble time_;
1690
1691   time_ = g_timer_elapsed (priv->timer, NULL);
1692
1693   /* Translators: number of minutes:seconds the caller has been connected */
1694   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time_ / 60,
1695     (int) time_ % 60);
1696   empathy_call_window_status_message (self, str);
1697   g_free (str);
1698
1699   return TRUE;
1700 }
1701
1702 static void
1703 display_error (EmpathyCallWindow *self,
1704     EmpathyTpCall *call,
1705     const gchar *img,
1706     const gchar *title,
1707     const gchar *desc,
1708     const gchar *details)
1709 {
1710   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1711   GtkWidget *info_bar;
1712   GtkWidget *content_area;
1713   GtkWidget *hbox;
1714   GtkWidget *vbox;
1715   GtkWidget *image;
1716   GtkWidget *label;
1717   gchar *txt;
1718
1719   /* Create info bar */
1720   info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1721       NULL);
1722
1723   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1724
1725   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1726
1727   /* hbox containing the image and the messages vbox */
1728   hbox = gtk_hbox_new (FALSE, 3);
1729   gtk_container_add (GTK_CONTAINER (content_area), hbox);
1730
1731   /* Add image */
1732   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1733   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1734
1735   /* vbox containing the main message and the details expander */
1736   vbox = gtk_vbox_new (FALSE, 3);
1737   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1738
1739   /* Add text */
1740   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
1741
1742   label = gtk_label_new (NULL);
1743   gtk_label_set_markup (GTK_LABEL (label), txt);
1744   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1745   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1746   g_free (txt);
1747
1748   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1749
1750   /* Add details */
1751   if (details != NULL)
1752     {
1753       GtkWidget *expander;
1754
1755       expander = gtk_expander_new (_("Technical Details"));
1756
1757       txt = g_strdup_printf ("<i>%s</i>", details);
1758
1759       label = gtk_label_new (NULL);
1760       gtk_label_set_markup (GTK_LABEL (label), txt);
1761       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1762       gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1763       g_free (txt);
1764
1765       gtk_container_add (GTK_CONTAINER (expander), label);
1766       gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1767     }
1768
1769   g_signal_connect (info_bar, "response",
1770       G_CALLBACK (gtk_widget_destroy), NULL);
1771
1772   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1773       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1774   gtk_widget_show_all (info_bar);
1775 }
1776
1777 static gchar *
1778 media_stream_error_to_txt (EmpathyCallWindow *self,
1779     EmpathyTpCall *call,
1780     gboolean audio,
1781     TpMediaStreamError error)
1782 {
1783   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1784   const gchar *cm;
1785   gchar *url;
1786   gchar *result;
1787
1788   switch (error)
1789     {
1790       case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1791         if (audio)
1792           return g_strdup_printf (
1793               _("%s's software does not understand any of the audio formats "
1794                 "supported by your computer"),
1795             empathy_contact_get_name (priv->contact));
1796         else
1797           return g_strdup_printf (
1798               _("%s's software does not understand any of the video formats "
1799                 "supported by your computer"),
1800             empathy_contact_get_name (priv->contact));
1801
1802       case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1803         return g_strdup_printf (
1804             _("Can't establish a connection to %s. "
1805               "One of you might be on a network that does not allow "
1806               "direct connections."),
1807           empathy_contact_get_name (priv->contact));
1808
1809       case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
1810           return g_strdup (_("There was a failure on the network"));
1811
1812       case TP_MEDIA_STREAM_ERROR_NO_CODECS:
1813         if (audio)
1814           return g_strdup (_("The audio formats necessary for this call "
1815                 "are not installed on your computer"));
1816         else
1817           return g_strdup (_("The video formats necessary for this call "
1818                 "are not installed on your computer"));
1819
1820       case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
1821         cm = empathy_tp_call_get_connection_manager (call);
1822
1823         url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
1824             "product=Telepathy&amp;component=%s", cm);
1825
1826         result = g_strdup_printf (
1827             _("Something unexpected happened in a Telepathy component. "
1828               "Please <a href=\"%s\">report this bug</a> and attach "
1829               "logs gathered from the 'Debug' window in the Help menu."), url);
1830
1831         g_free (url);
1832         return result;
1833
1834       case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
1835         return g_strdup (_("There was a failure in the call engine"));
1836
1837       default:
1838         return NULL;
1839     }
1840 }
1841
1842 static void
1843 empathy_call_window_stream_error (EmpathyCallWindow *self,
1844     EmpathyTpCall *call,
1845     gboolean audio,
1846     guint code,
1847     const gchar *msg,
1848     const gchar *icon,
1849     const gchar *title)
1850 {
1851   gchar *desc;
1852
1853   desc = media_stream_error_to_txt (self, call, audio, code);
1854   if (desc == NULL)
1855     {
1856       /* No description, use the error message. That's not great as it's not
1857        * localized but it's better than nothing. */
1858       display_error (self, call, icon, title, msg, NULL);
1859     }
1860   else
1861     {
1862       display_error (self, call, icon, title, desc, msg);
1863       g_free (desc);
1864     }
1865 }
1866
1867 static void
1868 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
1869     guint code,
1870     const gchar *msg,
1871     EmpathyCallWindow *self)
1872 {
1873   empathy_call_window_stream_error (self, call, TRUE, code, msg,
1874       "gnome-stock-mic", _("Can't establish audio stream"));
1875 }
1876
1877 static void
1878 empathy_call_window_video_stream_error (EmpathyTpCall *call,
1879     guint code,
1880     const gchar *msg,
1881     EmpathyCallWindow *self)
1882 {
1883   empathy_call_window_stream_error (self, call, FALSE, code, msg,
1884       "camera-web", _("Can't establish video stream"));
1885 }
1886
1887 static gboolean
1888 empathy_call_window_connected (gpointer user_data)
1889 {
1890   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1891   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1892   EmpathyTpCall *call;
1893   gboolean can_send_video;
1894
1895   empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1896
1897   can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1898     empathy_contact_can_voip_video (priv->contact);
1899
1900   g_object_get (priv->handler, "tp-call", &call, NULL);
1901
1902   g_signal_connect (call, "notify::video-stream",
1903     G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1904
1905   if (empathy_tp_call_has_dtmf (call))
1906     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1907
1908   if (priv->video_input == NULL)
1909     empathy_call_window_set_send_video (self, FALSE);
1910
1911   priv->sending_video = can_send_video ?
1912     empathy_tp_call_is_sending_video (call) : FALSE;
1913
1914   gtk_toggle_tool_button_set_active (
1915       GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
1916       priv->sending_video && priv->video_input != NULL);
1917   gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
1918
1919   gtk_action_set_sensitive (priv->redial, FALSE);
1920   gtk_widget_set_sensitive (priv->redial_button, FALSE);
1921
1922   gtk_widget_set_sensitive (priv->mic_button, TRUE);
1923
1924   /* FIXME: this should won't be needed once bug #602937 is fixed
1925    * (see empathy_call_window_disconnected for details) */
1926   gtk_widget_set_sensitive (priv->tool_button_camera_preview, TRUE);
1927   gtk_action_set_sensitive (priv->action_camera_preview, TRUE);
1928
1929   empathy_call_window_update_avatars_visibility (call, self);
1930
1931   g_object_unref (call);
1932
1933   g_mutex_lock (priv->lock);
1934
1935   priv->timer_id = g_timeout_add_seconds (1,
1936     empathy_call_window_update_timer, self);
1937
1938   g_mutex_unlock (priv->lock);
1939
1940   empathy_call_window_update_timer (self);
1941
1942   return FALSE;
1943 }
1944
1945
1946 /* Called from the streaming thread */
1947 static void
1948 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1949   GstPad *src, guint media_type, gpointer user_data)
1950 {
1951   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1952   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1953
1954   GstPad *pad;
1955
1956   g_mutex_lock (priv->lock);
1957
1958   if (priv->call_state != CONNECTED)
1959     {
1960       g_timer_start (priv->timer);
1961       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
1962       priv->call_state = CONNECTED;
1963     }
1964
1965   switch (media_type)
1966     {
1967       case TP_MEDIA_STREAM_TYPE_AUDIO:
1968         pad = empathy_call_window_get_audio_sink_pad (self);
1969         break;
1970       case TP_MEDIA_STREAM_TYPE_VIDEO:
1971         gtk_widget_hide (priv->remote_user_avatar_widget);
1972         gtk_widget_show (priv->video_output);
1973         pad = empathy_call_window_get_video_sink_pad (self);
1974         break;
1975       default:
1976         g_assert_not_reached ();
1977     }
1978
1979   gst_pad_link (src, pad);
1980   gst_object_unref (pad);
1981
1982   g_mutex_unlock (priv->lock);
1983 }
1984
1985 /* Called from the streaming thread */
1986 static void
1987 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1988   GstPad *sink, guint media_type, gpointer user_data)
1989 {
1990   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1991   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1992   GstPad *pad;
1993
1994   switch (media_type)
1995     {
1996       case TP_MEDIA_STREAM_TYPE_AUDIO:
1997         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1998
1999         pad = gst_element_get_static_pad (priv->audio_input, "src");
2000         gst_pad_link (pad, sink);
2001
2002         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
2003         break;
2004       case TP_MEDIA_STREAM_TYPE_VIDEO:
2005         if (priv->video_input != NULL)
2006           {
2007             if (priv->video_tee != NULL)
2008               {
2009                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2010                 gst_pad_link (pad, sink);
2011               }
2012           }
2013         break;
2014       default:
2015         g_assert_not_reached ();
2016     }
2017
2018 }
2019
2020 static void
2021 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2022 {
2023   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2024   GstElement *preview;
2025
2026   DEBUG ("remove video input");
2027   preview = empathy_video_widget_get_element (
2028     EMPATHY_VIDEO_WIDGET (priv->video_preview));
2029
2030   gst_element_set_state (priv->video_input, GST_STATE_NULL);
2031   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2032   gst_element_set_state (preview, GST_STATE_NULL);
2033
2034   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2035     priv->video_tee, preview, NULL);
2036
2037   g_object_unref (priv->video_input);
2038   priv->video_input = NULL;
2039   g_object_unref (priv->video_tee);
2040   priv->video_tee = NULL;
2041   gtk_widget_destroy (priv->video_preview);
2042   priv->video_preview = NULL;
2043
2044   gtk_toggle_tool_button_set_active (
2045       GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
2046   gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
2047
2048   gtk_widget_show (priv->self_user_avatar_widget);
2049 }
2050
2051 static void
2052 start_call (EmpathyCallWindow *self)
2053 {
2054   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2055
2056   priv->call_started = TRUE;
2057   empathy_call_handler_start_call (priv->handler);
2058   gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2059
2060   if (empathy_call_handler_has_initial_video (priv->handler))
2061     {
2062       /* Enable 'send video' buttons and display the preview */
2063       gtk_toggle_tool_button_set_active (
2064           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
2065     }
2066 }
2067
2068 static gboolean
2069 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2070   gpointer user_data)
2071 {
2072   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2073   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2074   GstState newstate;
2075
2076   empathy_call_handler_bus_message (priv->handler, bus, message);
2077
2078   switch (GST_MESSAGE_TYPE (message))
2079     {
2080       case GST_MESSAGE_STATE_CHANGED:
2081         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2082           {
2083             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2084             if (newstate == GST_STATE_PAUSED)
2085                 empathy_call_window_setup_video_input (self);
2086           }
2087         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2088             !priv->call_started)
2089           {
2090             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2091             if (newstate == GST_STATE_PAUSED)
2092               {
2093                 start_call (self);
2094               }
2095           }
2096         break;
2097       case GST_MESSAGE_ERROR:
2098         {
2099           GError *error = NULL;
2100           GstElement *gst_error;
2101           gchar *debug;
2102
2103           gst_message_parse_error (message, &error, &debug);
2104           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2105
2106           g_message ("Element error: %s -- %s\n", error->message, debug);
2107
2108           if (g_str_has_prefix (gst_element_get_name (gst_error),
2109                 VIDEO_INPUT_ERROR_PREFIX))
2110             {
2111               /* Remove the video input and continue */
2112               if (priv->video_input != NULL)
2113                 empathy_call_window_remove_video_input (self);
2114               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2115             }
2116           else
2117             {
2118               empathy_call_window_disconnected (self);
2119             }
2120           g_error_free (error);
2121           g_free (debug);
2122         }
2123       default:
2124         break;
2125     }
2126
2127   return TRUE;
2128 }
2129
2130 static void
2131 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
2132     EmpathyCallWindow *window)
2133 {
2134   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2135
2136   if (empathy_tp_call_is_receiving_video (call))
2137     {
2138       gtk_widget_hide (priv->remote_user_avatar_widget);
2139       gtk_widget_show (priv->video_output);
2140     }
2141   else
2142     {
2143       gtk_widget_hide (priv->video_output);
2144       gtk_widget_show (priv->remote_user_avatar_widget);
2145     }
2146 }
2147
2148 static void
2149 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
2150     GParamSpec *spec,
2151     EmpathyCallWindow *self)
2152 {
2153   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2154   EmpathyTpCall *call;
2155
2156   g_object_get (priv->handler, "tp-call", &call, NULL);
2157   if (call == NULL)
2158     return;
2159
2160   empathy_signal_connect_weak (call, "audio-stream-error",
2161       G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
2162   empathy_signal_connect_weak (call, "video-stream-error",
2163       G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
2164
2165   g_object_unref (call);
2166 }
2167
2168 static void
2169 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2170 {
2171   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2172   EmpathyTpCall *call;
2173
2174   g_signal_connect (priv->handler, "conference-added",
2175     G_CALLBACK (empathy_call_window_conference_added_cb), window);
2176   g_signal_connect (priv->handler, "request-resource",
2177     G_CALLBACK (empathy_call_window_request_resource_cb), window);
2178   g_signal_connect (priv->handler, "closed",
2179     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2180   g_signal_connect (priv->handler, "src-pad-added",
2181     G_CALLBACK (empathy_call_window_src_added_cb), window);
2182   g_signal_connect (priv->handler, "sink-pad-added",
2183     G_CALLBACK (empathy_call_window_sink_added_cb), window);
2184   g_signal_connect (priv->handler, "stream-closed",
2185     G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
2186
2187   g_object_get (priv->handler, "tp-call", &call, NULL);
2188   if (call != NULL)
2189     {
2190       empathy_signal_connect_weak (call, "audio-stream-error",
2191         G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
2192       empathy_signal_connect_weak (call, "video-stream-error",
2193         G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
2194
2195       g_object_unref (call);
2196     }
2197   else
2198     {
2199       /* tp-call doesn't exist yet, we'll connect signals once it has been
2200        * set */
2201       g_signal_connect (priv->handler, "notify::tp-call",
2202         G_CALLBACK (call_handler_notify_tp_call_cb), window);
2203     }
2204
2205   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2206 }
2207
2208 static gboolean
2209 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2210   EmpathyCallWindow *window)
2211 {
2212   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2213
2214   if (priv->pipeline != NULL)
2215     {
2216       if (priv->bus_message_source_id != 0)
2217         {
2218           g_source_remove (priv->bus_message_source_id);
2219           priv->bus_message_source_id = 0;
2220         }
2221
2222       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2223     }
2224
2225   if (priv->call_state == CONNECTING)
2226     empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2227
2228   return FALSE;
2229 }
2230
2231 static void
2232 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2233 {
2234   GtkWidget *menu;
2235   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2236
2237   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2238             "/menubar1");
2239
2240   if (set_fullscreen)
2241     {
2242       gtk_widget_hide (priv->sidebar);
2243       gtk_widget_hide (menu);
2244       gtk_widget_hide (priv->vbox);
2245       gtk_widget_hide (priv->statusbar);
2246       gtk_widget_hide (priv->toolbar);
2247     }
2248   else
2249     {
2250       if (priv->sidebar_was_visible_before_fs)
2251         gtk_widget_show (priv->sidebar);
2252
2253       gtk_widget_show (menu);
2254       gtk_widget_show (priv->vbox);
2255       gtk_widget_show (priv->statusbar);
2256       gtk_widget_show (priv->toolbar);
2257
2258       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2259           priv->original_height_before_fs);
2260     }
2261 }
2262
2263 static void
2264 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2265 {
2266   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2267
2268   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2269       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2270   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2271       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2272   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2273       priv->video_output, TRUE, TRUE,
2274       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2275       GTK_PACK_START);
2276   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2277       priv->vbox, TRUE, TRUE,
2278       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2279       GTK_PACK_START);
2280 }
2281
2282 static gboolean
2283 empathy_call_window_state_event_cb (GtkWidget *widget,
2284   GdkEventWindowState *event, EmpathyCallWindow *window)
2285 {
2286   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2287     {
2288       EmpathyCallWindowPriv *priv = GET_PRIV (window);
2289       gboolean set_fullscreen = event->new_window_state &
2290         GDK_WINDOW_STATE_FULLSCREEN;
2291
2292       if (set_fullscreen)
2293         {
2294           gboolean sidebar_was_visible;
2295           GtkAllocation allocation;
2296           gint original_width, original_height;
2297
2298           gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2299           original_width = allocation.width;
2300           original_height = allocation.height;
2301
2302           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2303
2304           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2305           priv->original_width_before_fs = original_width;
2306           priv->original_height_before_fs = original_height;
2307
2308           if (priv->video_output_motion_handler_id == 0 &&
2309                 priv->video_output != NULL)
2310             {
2311               priv->video_output_motion_handler_id = g_signal_connect (
2312                   G_OBJECT (priv->video_output), "motion-notify-event",
2313                   G_CALLBACK (empathy_call_window_video_output_motion_notify),
2314                   window);
2315             }
2316         }
2317       else
2318         {
2319           if (priv->video_output_motion_handler_id != 0)
2320             {
2321               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2322                   priv->video_output_motion_handler_id);
2323               priv->video_output_motion_handler_id = 0;
2324             }
2325         }
2326
2327       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2328           set_fullscreen);
2329       show_controls (window, set_fullscreen);
2330       show_borders (window, set_fullscreen);
2331       gtk_action_set_stock_id (priv->menu_fullscreen,
2332           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2333       priv->is_fullscreen = set_fullscreen;
2334   }
2335
2336   return FALSE;
2337 }
2338
2339 static void
2340 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2341   EmpathyCallWindow *window)
2342 {
2343   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2344   GtkWidget *arrow;
2345   int w, h, handle_size;
2346   GtkAllocation allocation, sidebar_allocation;
2347
2348   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2349   w = allocation.width;
2350   h = allocation.height;
2351
2352   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2353
2354   gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2355   if (gtk_toggle_button_get_active (toggle))
2356     {
2357       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2358       gtk_widget_show (priv->sidebar);
2359       w += sidebar_allocation.width + handle_size;
2360     }
2361   else
2362     {
2363       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2364       w -= sidebar_allocation.width + handle_size;
2365       gtk_widget_hide (priv->sidebar);
2366     }
2367
2368   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2369
2370   if (w > 0 && h > 0)
2371     gtk_window_resize (GTK_WINDOW (window), w, h);
2372 }
2373
2374 static void
2375 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2376   gboolean send)
2377 {
2378   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2379   EmpathyTpCall *call;
2380
2381   priv->sending_video = send;
2382
2383   /* When we start sending video, we want to show the video preview by
2384      default. */
2385   display_video_preview (window, send);
2386
2387   if (priv->call_state != CONNECTED)
2388     return;
2389
2390   g_object_get (priv->handler, "tp-call", &call, NULL);
2391   DEBUG ("%s sending video", send ? "start": "stop");
2392   empathy_tp_call_request_video_stream_direction (call, send);
2393   g_object_unref (call);
2394 }
2395
2396 static void
2397 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2398   EmpathyCallWindow *window)
2399 {
2400   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2401   gboolean active;
2402
2403   if (priv->audio_input == NULL)
2404     return;
2405
2406   active = (gtk_toggle_tool_button_get_active (toggle));
2407
2408   if (active)
2409     {
2410       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2411         priv->volume);
2412       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2413     }
2414   else
2415     {
2416       /* TODO, Instead of setting the input volume to 0 we should probably
2417        * stop sending but this would cause the audio call to drop if both
2418        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2419        * in the future. GNOME #574574
2420        */
2421       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2422         0);
2423       gtk_adjustment_set_value (priv->audio_input_adj, 0);
2424     }
2425 }
2426
2427 static void
2428 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2429   EmpathyCallWindow *window)
2430 {
2431   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2432
2433   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2434     FALSE);
2435 }
2436
2437 static void
2438 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2439   EmpathyCallWindow *window)
2440 {
2441   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2442
2443   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2444     TRUE);
2445 }
2446
2447 static void
2448 empathy_call_window_hangup_cb (gpointer object,
2449                                EmpathyCallWindow *window)
2450 {
2451   if (empathy_call_window_disconnected (window))
2452     gtk_widget_destroy (GTK_WIDGET (window));
2453 }
2454
2455 static void
2456 empathy_call_window_restart_call (EmpathyCallWindow *window)
2457 {
2458   GstBus *bus;
2459   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2460
2461   gtk_widget_destroy (priv->remote_user_output_hbox);
2462   gtk_widget_destroy (priv->self_user_output_hbox);
2463
2464   priv->pipeline = gst_pipeline_new (NULL);
2465   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2466   priv->bus_message_source_id = gst_bus_add_watch (bus,
2467       empathy_call_window_bus_message, window);
2468
2469   empathy_call_window_setup_remote_frame (bus, window);
2470   empathy_call_window_setup_self_frame (bus, window);
2471
2472   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2473       G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2474
2475   /* While the call was disconnected, the input volume might have changed.
2476    * However, since the audio_input source was destroyed, its volume has not
2477    * been updated during that time. That's why we manually update it here */
2478   empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2479
2480   g_object_unref (bus);
2481
2482   gtk_widget_show_all (priv->content_hbox);
2483
2484   priv->outgoing = TRUE;
2485   empathy_call_window_set_state_connecting (window);
2486
2487   start_call (window);
2488   empathy_call_window_setup_avatars (window, priv->handler);
2489
2490   gtk_action_set_sensitive (priv->redial, FALSE);
2491   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2492 }
2493
2494 static void
2495 empathy_call_window_redial_cb (gpointer object,
2496     EmpathyCallWindow *window)
2497 {
2498   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2499
2500   if (priv->call_state == CONNECTED)
2501     priv->call_state = REDIALING;
2502
2503   empathy_call_handler_stop_call (priv->handler);
2504
2505   if (priv->call_state != CONNECTED)
2506     empathy_call_window_restart_call (window);
2507 }
2508
2509 static void
2510 empathy_call_window_fullscreen_cb (gpointer object,
2511                                    EmpathyCallWindow *window)
2512 {
2513   empathy_call_window_fullscreen_toggle (window);
2514 }
2515
2516 static void
2517 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2518 {
2519   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2520
2521   if (priv->is_fullscreen)
2522     gtk_window_unfullscreen (GTK_WINDOW (window));
2523   else
2524     gtk_window_fullscreen (GTK_WINDOW (window));
2525 }
2526
2527 static gboolean
2528 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2529   GdkEventButton *event, EmpathyCallWindow *window)
2530 {
2531   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2532     {
2533       empathy_call_window_video_menu_popup (window, event->button);
2534       return TRUE;
2535     }
2536
2537   return FALSE;
2538 }
2539
2540 static gboolean
2541 empathy_call_window_key_press_cb (GtkWidget *video_output,
2542   GdkEventKey *event, EmpathyCallWindow *window)
2543 {
2544   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2545
2546   if (priv->is_fullscreen && event->keyval == GDK_Escape)
2547     {
2548       /* Since we are in fullscreen mode, toggling will bring us back to
2549          normal mode. */
2550       empathy_call_window_fullscreen_toggle (window);
2551       return TRUE;
2552     }
2553
2554   return FALSE;
2555 }
2556
2557 static gboolean
2558 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2559     GdkEventMotion *event, EmpathyCallWindow *window)
2560 {
2561   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2562
2563   if (priv->is_fullscreen)
2564     {
2565       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2566       return TRUE;
2567     }
2568   return FALSE;
2569 }
2570
2571 static void
2572 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2573   guint button)
2574 {
2575   GtkWidget *menu;
2576   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2577
2578   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2579             "/video-popup");
2580   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2581       button, gtk_get_current_event_time ());
2582   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2583 }
2584
2585 static void
2586 empathy_call_window_status_message (EmpathyCallWindow *window,
2587   gchar *message)
2588 {
2589   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2590
2591   if (priv->context_id == 0)
2592     {
2593       priv->context_id = gtk_statusbar_get_context_id (
2594         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2595     }
2596   else
2597     {
2598       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2599     }
2600
2601   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2602     message);
2603 }
2604
2605 static void
2606 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2607   gdouble value, EmpathyCallWindow *window)
2608 {
2609   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2610
2611   if (priv->audio_output == NULL)
2612     return;
2613
2614   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2615     value);
2616 }
2617
2618 /* block all the signals related to camera control widgets. This is useful
2619  * when we are manually updating the UI and so don't want to fire the
2620  * callbacks */
2621 static void
2622 block_camera_control_signals (EmpathyCallWindow *self)
2623 {
2624   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2625
2626   g_signal_handlers_block_by_func (priv->tool_button_camera_off,
2627       tool_button_camera_off_toggled_cb, self);
2628   g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
2629       tool_button_camera_preview_toggled_cb, self);
2630   g_signal_handlers_block_by_func (priv->tool_button_camera_on,
2631       tool_button_camera_on_toggled_cb, self);
2632   g_signal_handlers_block_by_func (priv->action_camera,
2633       action_camera_change_cb, self);
2634 }
2635
2636 static void
2637 unblock_camera_control_signals (EmpathyCallWindow *self)
2638 {
2639   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2640
2641   g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
2642       tool_button_camera_off_toggled_cb, self);
2643   g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
2644       tool_button_camera_preview_toggled_cb, self);
2645   g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
2646       tool_button_camera_on_toggled_cb, self);
2647   g_signal_handlers_unblock_by_func (priv->action_camera,
2648       action_camera_change_cb, self);
2649 }