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