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