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