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