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