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