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