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