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