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