]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
empathy-call-window: display_error: align the main message on the right
[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_widget_set_no_show_all (info_bar, TRUE);
1486   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1487
1488   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1489
1490   /* hbox containing the image and the messages vbox */
1491   hbox = gtk_hbox_new (FALSE, 3);
1492   gtk_container_add (GTK_CONTAINER (content_area), hbox);
1493
1494   /* Add image */
1495   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1496   gtk_widget_show (image);
1497   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1498
1499   /* vbox containing the main message and the details expander */
1500   vbox = gtk_vbox_new (FALSE, 3);
1501   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1502
1503   /* Add text */
1504   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
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_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1513
1514   /* Add details */
1515   if (details != NULL)
1516     {
1517       GtkWidget *expander;
1518
1519       expander = gtk_expander_new (_("Technical Details"));
1520
1521       txt = g_strdup_printf ("<i>%s</i>", details);
1522
1523       label = gtk_label_new (NULL);
1524       gtk_label_set_markup (GTK_LABEL (label), txt);
1525       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1526       gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1527       g_free (txt);
1528
1529       gtk_container_add (GTK_CONTAINER (expander), label);
1530       gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1531     }
1532
1533   g_signal_connect (info_bar, "response",
1534       G_CALLBACK (gtk_widget_destroy), NULL);
1535
1536   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1537       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1538   gtk_widget_show_all (hbox);
1539   gtk_widget_show (info_bar);
1540 }
1541
1542 static gchar *
1543 media_stream_error_to_txt (EmpathyCallWindow *self,
1544     gboolean audio,
1545     TpMediaStreamError error)
1546 {
1547   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1548
1549   switch (error)
1550     {
1551       case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1552         if (audio)
1553           return g_strdup_printf (
1554               _("%s's software does not understand any of the audio formats "
1555                 "supported by your computer"),
1556             empathy_contact_get_name (priv->contact));
1557         else
1558           return g_strdup_printf (
1559               _("%s's software does not understand any of the video formats "
1560                 "supported by your computer"),
1561             empathy_contact_get_name (priv->contact));
1562
1563       case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1564         return g_strdup_printf (
1565             _("Can't establish a connection to %s. "
1566               "One of you might be on a network that does not allow "
1567               "direct connections."),
1568           empathy_contact_get_name (priv->contact));
1569
1570       /* TODO: support more errors */
1571       default:
1572         return NULL;
1573     }
1574 }
1575
1576 static void
1577 empathy_call_window_stream_error (EmpathyCallWindow *self,
1578     gboolean audio,
1579     guint code,
1580     const gchar *msg,
1581     const gchar *icon,
1582     const gchar *title)
1583 {
1584   gchar *desc;
1585
1586   desc = media_stream_error_to_txt (self, audio, code);
1587   if (desc == NULL)
1588     {
1589       /* No description, use the error message. That's not great as it's not
1590        * localized but it's better than nothing. */
1591       display_error (self, icon, title, msg, NULL);
1592     }
1593   else
1594     {
1595       display_error (self, icon, title, desc, msg);
1596       g_free (desc);
1597     }
1598 }
1599
1600 static void
1601 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
1602     guint code,
1603     const gchar *msg,
1604     EmpathyCallWindow *self)
1605 {
1606   empathy_call_window_stream_error (self, TRUE, code, msg,
1607       "gnome-stock-mic", _("Can't establish audio stream"));
1608 }
1609
1610 static void
1611 empathy_call_window_video_stream_error (EmpathyTpCall *call,
1612     guint code,
1613     const gchar *msg,
1614     EmpathyCallWindow *self)
1615 {
1616   empathy_call_window_stream_error (self, FALSE, code, msg,
1617       "camera-web", _("Can't establish video stream"));
1618 }
1619
1620 static gboolean
1621 empathy_call_window_connected (gpointer user_data)
1622 {
1623   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1624   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1625   EmpathyTpCall *call;
1626   gboolean can_send_video;
1627
1628   empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1629
1630   can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1631     empathy_contact_can_voip_video (priv->contact);
1632
1633   g_object_get (priv->handler, "tp-call", &call, NULL);
1634
1635   g_signal_connect (call, "notify::video-stream",
1636     G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1637
1638   if (empathy_tp_call_has_dtmf (call))
1639     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1640
1641   if (priv->video_input == NULL)
1642     empathy_call_window_set_send_video (self, FALSE);
1643
1644   priv->sending_video = can_send_video ?
1645     empathy_tp_call_is_sending_video (call) : FALSE;
1646
1647   gtk_action_set_sensitive (priv->show_preview, TRUE);
1648   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1649       priv->sending_video
1650       || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (
1651               priv->show_preview)));
1652   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1653       priv->sending_video && priv->video_input != NULL);
1654   gtk_toggle_tool_button_set_active (
1655       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1656       priv->sending_video && priv->video_input != NULL);
1657   gtk_widget_set_sensitive (priv->camera_button, can_send_video);
1658   gtk_action_set_sensitive (priv->send_video, can_send_video);
1659
1660   gtk_action_set_sensitive (priv->redial, FALSE);
1661   gtk_widget_set_sensitive (priv->redial_button, FALSE);
1662
1663   gtk_widget_set_sensitive (priv->mic_button, TRUE);
1664
1665   empathy_call_window_update_avatars_visibility (call, self);
1666
1667   g_object_unref (call);
1668
1669   g_mutex_lock (priv->lock);
1670
1671   priv->timer_id = g_timeout_add_seconds (1,
1672     empathy_call_window_update_timer, self);
1673
1674   g_mutex_unlock (priv->lock);
1675
1676   empathy_call_window_update_timer (self);
1677
1678   return FALSE;
1679 }
1680
1681
1682 /* Called from the streaming thread */
1683 static void
1684 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1685   GstPad *src, guint media_type, gpointer user_data)
1686 {
1687   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1688   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1689
1690   GstPad *pad;
1691
1692   g_mutex_lock (priv->lock);
1693
1694   if (priv->call_state != CONNECTED)
1695     {
1696       g_timer_start (priv->timer);
1697       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
1698       priv->call_state = CONNECTED;
1699     }
1700
1701   switch (media_type)
1702     {
1703       case TP_MEDIA_STREAM_TYPE_AUDIO:
1704         pad = empathy_call_window_get_audio_sink_pad (self);
1705         break;
1706       case TP_MEDIA_STREAM_TYPE_VIDEO:
1707         gtk_widget_hide (priv->remote_user_avatar_widget);
1708         gtk_widget_show (priv->video_output);
1709         pad = empathy_call_window_get_video_sink_pad (self);
1710         break;
1711       default:
1712         g_assert_not_reached ();
1713     }
1714
1715   gst_pad_link (src, pad);
1716   gst_object_unref (pad);
1717
1718   g_mutex_unlock (priv->lock);
1719 }
1720
1721 /* Called from the streaming thread */
1722 static void
1723 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1724   GstPad *sink, guint media_type, gpointer user_data)
1725 {
1726   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1727   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1728   GstPad *pad;
1729
1730   switch (media_type)
1731     {
1732       case TP_MEDIA_STREAM_TYPE_AUDIO:
1733         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1734
1735         pad = gst_element_get_static_pad (priv->audio_input, "src");
1736         gst_pad_link (pad, sink);
1737
1738         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1739         break;
1740       case TP_MEDIA_STREAM_TYPE_VIDEO:
1741         if (priv->video_input != NULL)
1742           {
1743             EmpathyTpCall *call;
1744             g_object_get (priv->handler, "tp-call", &call, NULL);
1745
1746             if (empathy_tp_call_is_sending_video (call))
1747               {
1748                 empathy_call_window_setup_video_preview (self);
1749
1750                 gtk_toggle_action_set_active (
1751                     GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1752
1753                 if (priv->video_preview != NULL)
1754                   gtk_widget_show (priv->video_preview);
1755                 gtk_widget_hide (priv->self_user_avatar_widget);
1756               }
1757
1758             g_object_unref (call);
1759
1760             if (priv->video_tee != NULL)
1761               {
1762                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1763                 gst_pad_link (pad, sink);
1764               }
1765           }
1766         break;
1767       default:
1768         g_assert_not_reached ();
1769     }
1770
1771 }
1772
1773 static void
1774 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1775 {
1776   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1777   GstElement *preview;
1778
1779   preview = empathy_video_widget_get_element (
1780     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1781
1782   gst_element_set_state (priv->video_input, GST_STATE_NULL);
1783   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1784   gst_element_set_state (preview, GST_STATE_NULL);
1785
1786   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1787     priv->video_tee, preview, NULL);
1788
1789   g_object_unref (priv->video_input);
1790   priv->video_input = NULL;
1791   g_object_unref (priv->video_tee);
1792   priv->video_tee = NULL;
1793   gtk_widget_destroy (priv->video_preview);
1794   priv->video_preview = NULL;
1795
1796   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1797   gtk_toggle_tool_button_set_active (
1798       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1799   gtk_widget_set_sensitive (priv->camera_button, FALSE);
1800   gtk_action_set_sensitive (priv->send_video, FALSE);
1801
1802   gtk_widget_show (priv->self_user_avatar_widget);
1803 }
1804
1805
1806 static gboolean
1807 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1808   gpointer user_data)
1809 {
1810   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1811   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1812   GstState newstate;
1813
1814   empathy_call_handler_bus_message (priv->handler, bus, message);
1815
1816   switch (GST_MESSAGE_TYPE (message))
1817     {
1818       case GST_MESSAGE_STATE_CHANGED:
1819         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1820           {
1821             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1822             if (newstate == GST_STATE_PAUSED)
1823                 empathy_call_window_setup_video_input (self);
1824           }
1825         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1826             !priv->call_started)
1827           {
1828             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1829             if (newstate == GST_STATE_PAUSED)
1830               {
1831                 priv->call_started = TRUE;
1832                 empathy_call_handler_start_call (priv->handler);
1833                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1834               }
1835           }
1836         break;
1837       case GST_MESSAGE_ERROR:
1838         {
1839           GError *error = NULL;
1840           GstElement *gst_error;
1841           gchar *debug;
1842
1843           gst_message_parse_error (message, &error, &debug);
1844           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1845
1846           g_message ("Element error: %s -- %s\n", error->message, debug);
1847
1848           if (g_str_has_prefix (gst_element_get_name (gst_error),
1849                 VIDEO_INPUT_ERROR_PREFIX))
1850             {
1851               /* Remove the video input and continue */
1852               if (priv->video_input != NULL)
1853                 empathy_call_window_remove_video_input (self);
1854               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1855             }
1856           else
1857             {
1858               empathy_call_window_disconnected (self);
1859             }
1860           g_error_free (error);
1861           g_free (debug);
1862         }
1863       default:
1864         break;
1865     }
1866
1867   return TRUE;
1868 }
1869
1870 static void
1871 empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
1872 {
1873   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1874
1875   if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1876     {
1877       if (priv->video_preview != NULL)
1878         {
1879           gtk_widget_hide (priv->self_user_avatar_widget);
1880           gtk_widget_show (priv->video_preview);
1881         }
1882       else
1883         {
1884           if (priv->video_preview != NULL)
1885             gtk_widget_hide (priv->video_preview);
1886
1887           gtk_widget_show (priv->self_user_avatar_widget);
1888         }
1889     }
1890 }
1891
1892 static void
1893 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1894     EmpathyCallWindow *window)
1895 {
1896   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1897
1898   if (empathy_tp_call_is_receiving_video (call))
1899     {
1900       gtk_widget_hide (priv->remote_user_avatar_widget);
1901       gtk_widget_show (priv->video_output);
1902     }
1903   else
1904     {
1905       gtk_widget_hide (priv->video_output);
1906       gtk_widget_show (priv->remote_user_avatar_widget);
1907     }
1908
1909   empathy_call_window_update_self_avatar_visibility (window);
1910 }
1911
1912 static void
1913 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
1914     GParamSpec *spec,
1915     EmpathyCallWindow *self)
1916 {
1917   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1918   EmpathyTpCall *call;
1919
1920   g_object_get (priv->handler, "tp-call", &call, NULL);
1921   if (call == NULL)
1922     return;
1923
1924   empathy_signal_connect_weak (call, "audio-stream-error",
1925       G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
1926   empathy_signal_connect_weak (call, "video-stream-error",
1927       G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
1928
1929   g_object_unref (call);
1930 }
1931
1932 static void
1933 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1934 {
1935   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1936   EmpathyTpCall *call;
1937
1938   g_signal_connect (priv->handler, "conference-added",
1939     G_CALLBACK (empathy_call_window_conference_added_cb), window);
1940   g_signal_connect (priv->handler, "request-resource",
1941     G_CALLBACK (empathy_call_window_request_resource_cb), window);
1942   g_signal_connect (priv->handler, "closed",
1943     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1944   g_signal_connect (priv->handler, "src-pad-added",
1945     G_CALLBACK (empathy_call_window_src_added_cb), window);
1946   g_signal_connect (priv->handler, "sink-pad-added",
1947     G_CALLBACK (empathy_call_window_sink_added_cb), window);
1948   g_signal_connect (priv->handler, "stream-closed",
1949     G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
1950
1951   g_object_get (priv->handler, "tp-call", &call, NULL);
1952   if (call != NULL)
1953     {
1954       empathy_signal_connect_weak (call, "audio-stream-error",
1955         G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
1956       empathy_signal_connect_weak (call, "video-stream-error",
1957         G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
1958
1959       g_object_unref (call);
1960     }
1961   else
1962     {
1963       /* tp-call doesn't exist yet, we'll connect signals once it has been
1964        * set */
1965       g_signal_connect (priv->handler, "notify::tp-call",
1966         G_CALLBACK (call_handler_notify_tp_call_cb), window);
1967     }
1968
1969   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1970 }
1971
1972 static gboolean
1973 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1974   EmpathyCallWindow *window)
1975 {
1976   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1977
1978   if (priv->pipeline != NULL)
1979     {
1980       if (priv->bus_message_source_id != 0)
1981         {
1982           g_source_remove (priv->bus_message_source_id);
1983           priv->bus_message_source_id = 0;
1984         }
1985
1986       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1987     }
1988
1989   if (priv->call_state == CONNECTING)
1990     empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1991
1992   return FALSE;
1993 }
1994
1995 static void
1996 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1997 {
1998   GtkWidget *menu;
1999   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2000
2001   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2002             "/menubar1");
2003
2004   if (set_fullscreen)
2005     {
2006       gtk_widget_hide (priv->sidebar);
2007       gtk_widget_hide (menu);
2008       gtk_widget_hide (priv->vbox);
2009       gtk_widget_hide (priv->statusbar);
2010       gtk_widget_hide (priv->toolbar);
2011     }
2012   else
2013     {
2014       if (priv->sidebar_was_visible_before_fs)
2015         gtk_widget_show (priv->sidebar);
2016
2017       gtk_widget_show (menu);
2018       gtk_widget_show (priv->vbox);
2019       gtk_widget_show (priv->statusbar);
2020       gtk_widget_show (priv->toolbar);
2021
2022       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2023           priv->original_height_before_fs);
2024     }
2025 }
2026
2027 static void
2028 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2029 {
2030   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2031
2032   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2033       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2034   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2035       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2036   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2037       priv->video_output, TRUE, TRUE,
2038       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2039       GTK_PACK_START);
2040   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2041       priv->vbox, TRUE, TRUE,
2042       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2043       GTK_PACK_START);
2044 }
2045
2046 static gboolean
2047 empathy_call_window_state_event_cb (GtkWidget *widget,
2048   GdkEventWindowState *event, EmpathyCallWindow *window)
2049 {
2050   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2051     {
2052       EmpathyCallWindowPriv *priv = GET_PRIV (window);
2053       gboolean set_fullscreen = event->new_window_state &
2054         GDK_WINDOW_STATE_FULLSCREEN;
2055
2056       if (set_fullscreen)
2057         {
2058           gboolean sidebar_was_visible;
2059           gint original_width = GTK_WIDGET (window)->allocation.width;
2060           gint original_height = GTK_WIDGET (window)->allocation.height;
2061
2062           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2063
2064           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2065           priv->original_width_before_fs = original_width;
2066           priv->original_height_before_fs = original_height;
2067
2068           if (priv->video_output_motion_handler_id == 0 &&
2069                 priv->video_output != NULL)
2070             {
2071               priv->video_output_motion_handler_id = g_signal_connect (
2072                   G_OBJECT (priv->video_output), "motion-notify-event",
2073                   G_CALLBACK (empathy_call_window_video_output_motion_notify),
2074                   window);
2075             }
2076         }
2077       else
2078         {
2079           if (priv->video_output_motion_handler_id != 0)
2080             {
2081               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2082                   priv->video_output_motion_handler_id);
2083               priv->video_output_motion_handler_id = 0;
2084             }
2085         }
2086
2087       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2088           set_fullscreen);
2089       show_controls (window, set_fullscreen);
2090       show_borders (window, set_fullscreen);
2091       gtk_action_set_stock_id (priv->menu_fullscreen,
2092           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2093       priv->is_fullscreen = set_fullscreen;
2094   }
2095
2096   return FALSE;
2097 }
2098
2099 static void
2100 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2101   EmpathyCallWindow *window)
2102 {
2103   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2104   GtkWidget *arrow;
2105   int w,h, handle_size;
2106
2107   w = GTK_WIDGET (window)->allocation.width;
2108   h = GTK_WIDGET (window)->allocation.height;
2109
2110   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2111
2112   if (gtk_toggle_button_get_active (toggle))
2113     {
2114       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2115       gtk_widget_show (priv->sidebar);
2116       w += priv->sidebar->allocation.width + handle_size;
2117     }
2118   else
2119     {
2120       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2121       w -= priv->sidebar->allocation.width + handle_size;
2122       gtk_widget_hide (priv->sidebar);
2123     }
2124
2125   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2126
2127   if (w > 0 && h > 0)
2128     gtk_window_resize (GTK_WINDOW (window), w, h);
2129 }
2130
2131 static void
2132 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2133   gboolean send)
2134 {
2135   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2136   EmpathyTpCall *call;
2137
2138   priv->sending_video = send;
2139
2140   /* When we start sending video, we want to show the video preview by
2141      default. */
2142   if (send)
2143     {
2144       empathy_call_window_setup_video_preview (window);
2145       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
2146           TRUE);
2147     }
2148
2149   g_object_get (priv->handler, "tp-call", &call, NULL);
2150   empathy_tp_call_request_video_stream_direction (call, send);
2151   g_object_unref (call);
2152 }
2153
2154 static void
2155 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
2156   EmpathyCallWindow *window)
2157 {
2158   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2159   gboolean active;
2160
2161   if (priv->call_state != CONNECTED)
2162     return;
2163
2164   active = (gtk_toggle_tool_button_get_active (toggle));
2165
2166   if (priv->sending_video == active)
2167     return;
2168
2169   empathy_call_window_set_send_video (window, active);
2170   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
2171 }
2172
2173 static void
2174 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
2175   EmpathyCallWindow *window)
2176 {
2177   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2178   gboolean active;
2179
2180   if (priv->call_state != CONNECTED)
2181     return;
2182
2183   active = (gtk_toggle_action_get_active (toggle));
2184
2185   if (priv->sending_video == active)
2186     return;
2187
2188   empathy_call_window_set_send_video (window, active);
2189   gtk_toggle_tool_button_set_active (
2190       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
2191 }
2192
2193 static void
2194 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
2195   EmpathyCallWindow *window)
2196 {
2197   gboolean show_preview_toggled;
2198   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2199
2200   show_preview_toggled = gtk_toggle_action_get_active (toggle);
2201
2202   if (show_preview_toggled)
2203     {
2204       empathy_call_window_setup_video_preview (window);
2205       gtk_widget_show (priv->self_user_output_frame);
2206       empathy_call_window_update_self_avatar_visibility (window);
2207     }
2208   else
2209     {
2210       gtk_widget_hide (priv->self_user_output_frame);
2211     }
2212 }
2213
2214 static void
2215 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2216   EmpathyCallWindow *window)
2217 {
2218   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2219   gboolean active;
2220
2221   if (priv->audio_input == NULL)
2222     return;
2223
2224   active = (gtk_toggle_tool_button_get_active (toggle));
2225
2226   if (active)
2227     {
2228       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2229         priv->volume);
2230       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2231     }
2232   else
2233     {
2234       /* TODO, Instead of setting the input volume to 0 we should probably
2235        * stop sending but this would cause the audio call to drop if both
2236        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2237        * in the future. GNOME #574574
2238        */
2239       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2240         0);
2241       gtk_adjustment_set_value (priv->audio_input_adj, 0);
2242     }
2243 }
2244
2245 static void
2246 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2247   EmpathyCallWindow *window)
2248 {
2249   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2250
2251   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2252     FALSE);
2253 }
2254
2255 static void
2256 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2257   EmpathyCallWindow *window)
2258 {
2259   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2260
2261   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2262     TRUE);
2263 }
2264
2265 static void
2266 empathy_call_window_hangup_cb (gpointer object,
2267                                EmpathyCallWindow *window)
2268 {
2269   if (empathy_call_window_disconnected (window))
2270     gtk_widget_destroy (GTK_WIDGET (window));
2271 }
2272
2273 static void
2274 empathy_call_window_restart_call (EmpathyCallWindow *window)
2275 {
2276   GstBus *bus;
2277   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2278
2279   gtk_widget_destroy (priv->remote_user_output_hbox);
2280   gtk_widget_destroy (priv->self_user_output_hbox);
2281
2282   priv->pipeline = gst_pipeline_new (NULL);
2283   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2284   priv->bus_message_source_id = gst_bus_add_watch (bus,
2285       empathy_call_window_bus_message, window);
2286
2287   empathy_call_window_setup_remote_frame (bus, window);
2288   empathy_call_window_setup_self_frame (bus, window);
2289
2290   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2291       G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2292
2293   /* While the call was disconnected, the input volume might have changed.
2294    * However, since the audio_input source was destroyed, its volume has not
2295    * been updated during that time. That's why we manually update it here */
2296   empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2297
2298   g_object_unref (bus);
2299
2300   gtk_widget_show_all (priv->content_hbox);
2301
2302   if (!empathy_call_handler_has_initial_video (priv->handler))
2303     gtk_widget_hide (priv->self_user_output_frame);
2304
2305   priv->outgoing = TRUE;
2306   empathy_call_window_set_state_connecting (window);
2307
2308   priv->call_started = TRUE;
2309   empathy_call_handler_start_call (priv->handler);
2310   empathy_call_window_setup_avatars (window, priv->handler);
2311   gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2312
2313   gtk_action_set_sensitive (priv->redial, FALSE);
2314   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2315 }
2316
2317 static void
2318 empathy_call_window_redial_cb (gpointer object,
2319     EmpathyCallWindow *window)
2320 {
2321   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2322
2323   if (priv->call_state == CONNECTED)
2324     priv->call_state = REDIALING;
2325
2326   empathy_call_handler_stop_call (priv->handler);
2327
2328   if (priv->call_state != CONNECTED)
2329     empathy_call_window_restart_call (window);
2330 }
2331
2332 static void
2333 empathy_call_window_fullscreen_cb (gpointer object,
2334                                    EmpathyCallWindow *window)
2335 {
2336   empathy_call_window_fullscreen_toggle (window);
2337 }
2338
2339 static void
2340 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2341 {
2342   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2343
2344   if (priv->is_fullscreen)
2345     gtk_window_unfullscreen (GTK_WINDOW (window));
2346   else
2347     gtk_window_fullscreen (GTK_WINDOW (window));
2348 }
2349
2350 static gboolean
2351 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2352   GdkEventButton *event, EmpathyCallWindow *window)
2353 {
2354   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2355     {
2356       empathy_call_window_video_menu_popup (window, event->button);
2357       return TRUE;
2358     }
2359
2360   return FALSE;
2361 }
2362
2363 static gboolean
2364 empathy_call_window_key_press_cb (GtkWidget *video_output,
2365   GdkEventKey *event, EmpathyCallWindow *window)
2366 {
2367   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2368
2369   if (priv->is_fullscreen && event->keyval == GDK_Escape)
2370     {
2371       /* Since we are in fullscreen mode, toggling will bring us back to
2372          normal mode. */
2373       empathy_call_window_fullscreen_toggle (window);
2374       return TRUE;
2375     }
2376
2377   return FALSE;
2378 }
2379
2380 static gboolean
2381 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2382     GdkEventMotion *event, EmpathyCallWindow *window)
2383 {
2384   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2385
2386   if (priv->is_fullscreen)
2387     {
2388       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2389       return TRUE;
2390     }
2391   return FALSE;
2392 }
2393
2394 static void
2395 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2396   guint button)
2397 {
2398   GtkWidget *menu;
2399   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2400
2401   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2402             "/video-popup");
2403   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2404       button, gtk_get_current_event_time ());
2405   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2406 }
2407
2408 static void
2409 empathy_call_window_status_message (EmpathyCallWindow *window,
2410   gchar *message)
2411 {
2412   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2413
2414   if (priv->context_id == 0)
2415     {
2416       priv->context_id = gtk_statusbar_get_context_id (
2417         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2418     }
2419   else
2420     {
2421       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2422     }
2423
2424   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2425     message);
2426 }
2427
2428 static void
2429 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2430   gdouble value, EmpathyCallWindow *window)
2431 {
2432   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2433
2434   if (priv->audio_output == NULL)
2435     return;
2436
2437   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2438     value);
2439 }