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