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