]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
CallWindow: properly determine the sidebar width
[empathy.git] / src / empathy-call-window.c
1 /*
2  * empathy-call-window.c - Source for EmpathyCallWindow
3  * Copyright (C) 2008-2011 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 <clutter/clutter.h>
33 #include <clutter-gtk/clutter-gtk.h>
34 #include <clutter-gst/clutter-gst.h>
35
36 #include <telepathy-glib/util.h>
37 #include <telepathy-farstream/telepathy-farstream.h>
38 #include <telepathy-glib/util.h>
39
40 #include <gst/farsight/fs-element-added-notifier.h>
41 #include <gst/farsight/fs-utils.h>
42
43 #include <libempathy/empathy-tp-contact-factory.h>
44 #include <libempathy/empathy-utils.h>
45 #include <libempathy-gtk/empathy-avatar-image.h>
46 #include <libempathy-gtk/empathy-ui-utils.h>
47 #include <libempathy-gtk/empathy-sound-manager.h>
48 #include <libempathy-gtk/empathy-geometry.h>
49 #include <libempathy-gtk/empathy-images.h>
50
51 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
52 #include <libempathy/empathy-debug.h>
53
54 #include "empathy-call-window.h"
55 #include "empathy-call-window-fullscreen.h"
56 #include "empathy-call-factory.h"
57 #include "empathy-video-widget.h"
58 #include "empathy-audio-src.h"
59 #include "empathy-audio-sink.h"
60 #include "empathy-video-src.h"
61 #include "ev-sidebar.h"
62
63 #define BUTTON_ID "empathy-call-dtmf-button-id"
64
65 #define CONTENT_HBOX_BORDER_WIDTH 6
66 #define CONTENT_HBOX_SPACING 3
67 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
68
69 #define SELF_VIDEO_SECTION_WIDTH 120
70 #define SELF_VIDEO_SECTION_HEIGTH 90
71
72 /* The avatar's default width and height are set to the same value because we
73    want a square icon. */
74 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
75 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
76   EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
77
78 /* If an video input error occurs, the error message will start with "v4l" */
79 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
80
81 /* The time interval in milliseconds between 2 outgoing rings */
82 #define MS_BETWEEN_RING 500
83
84 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
85
86 enum {
87   PROP_CALL_HANDLER = 1,
88 };
89
90 typedef enum {
91   CONNECTING,
92   CONNECTED,
93   HELD,
94   DISCONNECTED,
95   REDIALING
96 } CallState;
97
98 typedef enum {
99   CAMERA_STATE_OFF = 0,
100   CAMERA_STATE_ON,
101 } CameraState;
102
103 struct _EmpathyCallWindowPriv
104 {
105   gboolean dispose_has_run;
106   EmpathyCallHandler *handler;
107
108   EmpathyContact *contact;
109
110   guint call_state;
111   gboolean outgoing;
112
113   GtkUIManager *ui_manager;
114   GtkWidget *errors_vbox;
115   /* widget displays the video received from the remote user. This widget is
116    * alive only during call. */
117   ClutterActor *video_output;
118   ClutterActor *video_preview;
119   ClutterActor *preview_hidden_button;
120   GtkWidget *video_container;
121   GtkWidget *remote_user_avatar_widget;
122   GtkWidget *sidebar;
123   GtkWidget *statusbar;
124   GtkWidget *volume_button;
125   GtkWidget *redial_button;
126   GtkWidget *mic_button;
127   GtkWidget *camera_button;
128   GtkWidget *dialpad_button;
129   GtkWidget *toolbar;
130   GtkWidget *pane;
131   GtkAction *redial;
132   GtkAction *menu_sidebar;
133   GtkAction *menu_fullscreen;
134
135   /* The box that contains self and remote avatar and video
136      input/output. When we redial, we destroy and re-create the box */
137   ClutterActor *video_box;
138   ClutterLayoutManager *video_layout;
139
140   /* We keep a reference on the hbox which contains the main content so we can
141      easilly repack everything when toggling fullscreen */
142   GtkWidget *content_hbox;
143
144   /* This vbox is contained in the content_hbox and it contains the
145      sidebar button. When toggling fullscreen,
146      it needs to be repacked. We keep a reference on it for easier access. */
147   GtkWidget *vbox;
148
149   gulong video_output_motion_handler_id;
150   guint bus_message_source_id;
151
152   gdouble volume;
153   GtkWidget *volume_scale;
154   GtkWidget *volume_progress_bar;
155   GtkAdjustment *audio_input_adj;
156
157   GtkWidget *dtmf_panel;
158
159   /* Details vbox */
160   GtkWidget *details_vbox;
161   GtkWidget *vcodec_encoding_label;
162   GtkWidget *acodec_encoding_label;
163   GtkWidget *vcodec_decoding_label;
164   GtkWidget *acodec_decoding_label;
165
166   GtkWidget *audio_remote_candidate_label;
167   GtkWidget *audio_local_candidate_label;
168   GtkWidget *video_remote_candidate_label;
169   GtkWidget *video_local_candidate_label;
170   GtkWidget *video_remote_candidate_info_img;
171   GtkWidget *video_local_candidate_info_img;
172   GtkWidget *audio_remote_candidate_info_img;
173   GtkWidget *audio_local_candidate_info_img;
174
175   GstElement *video_input;
176   GstElement *video_preview_sink;
177   GstElement *video_output_sink;
178   GstElement *audio_input;
179   GstElement *audio_output;
180   GstElement *pipeline;
181   GstElement *video_tee;
182
183   GstElement *funnel;
184
185   GList *notifiers;
186
187   guint context_id;
188
189   GTimer *timer;
190   guint timer_id;
191
192   GtkWidget *video_contrast;
193   GtkWidget *video_brightness;
194   GtkWidget *video_gamma;
195
196   GMutex *lock;
197   gboolean call_started;
198   gboolean sending_video;
199   CameraState camera_state;
200
201   EmpathyCallWindowFullscreen *fullscreen;
202   gboolean is_fullscreen;
203
204   /* Those fields represent the state of the window before it actually was in
205      fullscreen mode. */
206   gboolean sidebar_was_visible_before_fs;
207   gint original_width_before_fs;
208   gint original_height_before_fs;
209
210   /* TRUE if the call should be started when the pipeline is playing */
211   gboolean start_call_when_playing;
212   /* TRUE if we requested to set the pipeline in the playing state */
213   gboolean pipeline_playing;
214
215   EmpathySoundManager *sound_mgr;
216 };
217
218 #define GET_PRIV(o) (EMPATHY_CALL_WINDOW (o)->priv)
219
220 static void empathy_call_window_realized_cb (GtkWidget *widget,
221   EmpathyCallWindow *window);
222
223 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
224   GdkEvent *event, EmpathyCallWindow *window);
225
226 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
227   GdkEventWindowState *event, EmpathyCallWindow *window);
228
229 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
230   CameraState state);
231
232 static void empathy_call_window_mic_toggled_cb (
233   GtkToggleToolButton *toggle, EmpathyCallWindow *window);
234
235 static void empathy_call_window_sidebar_cb (GtkToggleAction *menu,
236   EmpathyCallWindow *self);
237
238 static void empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
239   EmpathyCallWindow *window);
240
241 static void empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
242   EmpathyCallWindow *window);
243
244 static void empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar,
245   const gchar *page,
246   EmpathyCallWindow *window);
247
248 static void empathy_call_window_hangup_cb (gpointer object,
249   EmpathyCallWindow *window);
250
251 static void empathy_call_window_fullscreen_cb (gpointer object,
252   EmpathyCallWindow *window);
253
254 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
255
256 static gboolean empathy_call_window_video_button_press_cb (
257   GtkWidget *video_output, GdkEventButton *event, EmpathyCallWindow *window);
258
259 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
260   GdkEventKey *event, EmpathyCallWindow *window);
261
262 static gboolean empathy_call_window_video_output_motion_notify (
263   GtkWidget *widget, GdkEventMotion *event, EmpathyCallWindow *window);
264
265 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
266   guint button);
267
268 static void empathy_call_window_redial_cb (gpointer object,
269   EmpathyCallWindow *window);
270
271 static void empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
272   EmpathyCallWindow *window);
273
274 static void empathy_call_window_restart_call (EmpathyCallWindow *window);
275
276 static void empathy_call_window_status_message (EmpathyCallWindow *window,
277   gchar *message);
278
279 static gboolean empathy_call_window_bus_message (GstBus *bus,
280   GstMessage *message, gpointer user_data);
281
282 static void
283 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
284   gdouble value, EmpathyCallWindow *window);
285
286 static void
287 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
288 {
289   EmpathyCallWindowPriv *priv = GET_PRIV (self);
290   GtkToolItem *tool_item;
291
292   /* Add an empty expanded GtkToolItem so the volume button is at the end of
293    * the toolbar. */
294   tool_item = gtk_tool_item_new ();
295   gtk_tool_item_set_expand (tool_item, TRUE);
296   gtk_widget_show (GTK_WIDGET (tool_item));
297   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
298
299   priv->volume_button = gtk_volume_button_new ();
300   /* FIXME listen to the audiosinks signals and update the button according to
301    * that, for now starting out at 1.0 and assuming only the app changes the
302    * volume will do */
303   gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
304   g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
305     G_CALLBACK (empathy_call_window_volume_changed_cb), self);
306
307   tool_item = gtk_tool_item_new ();
308   gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
309   gtk_widget_show_all (GTK_WIDGET (tool_item));
310   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
311 }
312
313 static void
314 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
315 {
316   EmpathyCallWindowPriv *priv = GET_PRIV (window);
317   TpyCallChannel *call;
318   GQuark button_quark;
319   TpDTMFEvent event;
320
321   g_object_get (priv->handler, "call-channel", &call, NULL);
322
323   button_quark = g_quark_from_static_string (BUTTON_ID);
324   event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
325     button_quark));
326
327   tpy_call_channel_dtmf_start_tone (call, event);
328
329   g_object_unref (call);
330 }
331
332 static void
333 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
334 {
335   EmpathyCallWindowPriv *priv = GET_PRIV (window);
336   TpyCallChannel *call;
337
338   g_object_get (priv->handler, "call-channel", &call, NULL);
339
340   tpy_call_channel_dtmf_stop_tone (call);
341
342   g_object_unref (call);
343 }
344
345 static GtkWidget *
346 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
347 {
348   GtkWidget *table;
349   int i;
350   GQuark button_quark;
351   struct {
352     const gchar *label;
353     TpDTMFEvent event;
354   } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
355                       { "2", TP_DTMF_EVENT_DIGIT_2 },
356                       { "3", TP_DTMF_EVENT_DIGIT_3 },
357                       { "4", TP_DTMF_EVENT_DIGIT_4 },
358                       { "5", TP_DTMF_EVENT_DIGIT_5 },
359                       { "6", TP_DTMF_EVENT_DIGIT_6 },
360                       { "7", TP_DTMF_EVENT_DIGIT_7 },
361                       { "8", TP_DTMF_EVENT_DIGIT_8 },
362                       { "9", TP_DTMF_EVENT_DIGIT_9 },
363                       { "#", TP_DTMF_EVENT_HASH },
364                       { "0", TP_DTMF_EVENT_DIGIT_0 },
365                       { "*", TP_DTMF_EVENT_ASTERISK },
366                       { NULL, } };
367
368   button_quark = g_quark_from_static_string (BUTTON_ID);
369
370   table = gtk_table_new (4, 3, TRUE);
371
372   for (i = 0; dtmfbuttons[i].label != NULL; i++)
373     {
374       GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
375       gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
376         i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
377
378       g_object_set_qdata (G_OBJECT (button), button_quark,
379         GUINT_TO_POINTER (dtmfbuttons[i].event));
380
381       g_signal_connect (G_OBJECT (button), "pressed",
382         G_CALLBACK (dtmf_button_pressed_cb), self);
383       g_signal_connect (G_OBJECT (button), "released",
384         G_CALLBACK (dtmf_button_released_cb), self);
385     }
386
387   return table;
388 }
389
390 static GtkWidget *
391 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
392   gchar *label_text, GtkWidget *bin)
393 {
394    GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
395    GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
396    GtkWidget *label = gtk_label_new (label_text);
397
398    gtk_widget_set_sensitive (scale, FALSE);
399
400    gtk_container_add (GTK_CONTAINER (bin), vbox);
401
402    gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
403    gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
404    gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
405
406    return scale;
407 }
408
409 static void
410 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
411   EmpathyCallWindow *self)
412
413 {
414   EmpathyCallWindowPriv *priv = GET_PRIV (self);
415
416   empathy_video_src_set_channel (priv->video_input,
417     EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
418 }
419
420 static void
421 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
422   EmpathyCallWindow *self)
423
424 {
425   EmpathyCallWindowPriv *priv = GET_PRIV (self);
426
427   empathy_video_src_set_channel (priv->video_input,
428     EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
429 }
430
431 static void
432 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
433   EmpathyCallWindow *self)
434
435 {
436   EmpathyCallWindowPriv *priv = GET_PRIV (self);
437
438   empathy_video_src_set_channel (priv->video_input,
439     EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
440 }
441
442
443 static GtkWidget *
444 empathy_call_window_create_video_input (EmpathyCallWindow *self)
445 {
446   EmpathyCallWindowPriv *priv = GET_PRIV (self);
447   GtkWidget *hbox;
448
449   hbox = gtk_hbox_new (TRUE, 3);
450
451   priv->video_contrast = empathy_call_window_create_video_input_add_slider (
452     self,  _("Contrast"), hbox);
453
454   priv->video_brightness = empathy_call_window_create_video_input_add_slider (
455     self,  _("Brightness"), hbox);
456
457   priv->video_gamma = empathy_call_window_create_video_input_add_slider (
458     self,  _("Gamma"), hbox);
459
460   return hbox;
461 }
462
463 static void
464 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
465 {
466   EmpathyCallWindowPriv *priv = GET_PRIV (self);
467   guint supported;
468   GtkAdjustment *adj;
469
470   supported = empathy_video_src_get_supported_channels (priv->video_input);
471
472   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
473     {
474       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
475
476       gtk_adjustment_set_value (adj,
477         empathy_video_src_get_channel (priv->video_input,
478           EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
479
480       g_signal_connect (G_OBJECT (adj), "value-changed",
481         G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
482
483       gtk_widget_set_sensitive (priv->video_contrast, TRUE);
484     }
485
486   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
487     {
488       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
489
490       gtk_adjustment_set_value (adj,
491         empathy_video_src_get_channel (priv->video_input,
492           EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
493
494       g_signal_connect (G_OBJECT (adj), "value-changed",
495         G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
496       gtk_widget_set_sensitive (priv->video_brightness, TRUE);
497     }
498
499   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
500     {
501       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
502
503       gtk_adjustment_set_value (adj,
504         empathy_video_src_get_channel (priv->video_input,
505           EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
506
507       g_signal_connect (G_OBJECT (adj), "value-changed",
508         G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
509       gtk_widget_set_sensitive (priv->video_gamma, TRUE);
510     }
511 }
512
513 static void
514 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
515   EmpathyCallWindow *self)
516 {
517   EmpathyCallWindowPriv *priv = GET_PRIV (self);
518   gdouble volume;
519
520   volume = gtk_adjustment_get_value (adj)/100.0;
521
522   /* Don't store the volume because of muting */
523   if (volume > 0 || gtk_toggle_tool_button_get_active (
524         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
525     priv->volume = volume;
526
527   /* Ensure that the toggle button is active if the volume is > 0 and inactive
528    * if it's smaller than 0 */
529   if ((volume > 0) != gtk_toggle_tool_button_get_active (
530         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
531     gtk_toggle_tool_button_set_active (
532       GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
533
534   empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
535     volume);
536 }
537
538 static void
539 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
540   gdouble level, EmpathyCallWindow *window)
541 {
542   gdouble value;
543   EmpathyCallWindowPriv *priv = GET_PRIV (window);
544
545   value = CLAMP (pow (10, level / 20), 0.0, 1.0);
546   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
547       value);
548 }
549
550 static GtkWidget *
551 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
552 {
553   EmpathyCallWindowPriv *priv = GET_PRIV (self);
554   GtkWidget *hbox, *vbox, *label;
555
556   hbox = gtk_hbox_new (TRUE, 3);
557
558   vbox = gtk_vbox_new (FALSE, 3);
559   gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
560
561   priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
562   gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
563   label = gtk_label_new (_("Volume"));
564
565   priv->audio_input_adj = gtk_range_get_adjustment (
566     GTK_RANGE (priv->volume_scale));
567   priv->volume =  empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
568     (priv->audio_input));
569   gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
570
571   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
572     G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
573
574   gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
575   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
576
577   priv->volume_progress_bar = gtk_progress_bar_new ();
578
579   gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->volume_progress_bar),
580       GTK_ORIENTATION_VERTICAL);
581
582   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
583       0);
584
585   gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
586       3);
587
588   return hbox;
589 }
590
591 static void
592 create_video_output_widget (EmpathyCallWindow *self)
593 {
594   EmpathyCallWindowPriv *priv = GET_PRIV (self);
595
596   g_assert (priv->video_output == NULL);
597   g_assert (priv->pipeline != NULL);
598
599   priv->video_output = clutter_texture_new ();
600
601   clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (priv->video_output),
602       TRUE);
603
604   priv->video_output_sink = clutter_gst_video_sink_new (
605       CLUTTER_TEXTURE (priv->video_output));
606
607   clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_box),
608       priv->video_output);
609
610   gtk_widget_add_events (priv->video_container,
611       GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
612   g_signal_connect (G_OBJECT (priv->video_container), "button-press-event",
613       G_CALLBACK (empathy_call_window_video_button_press_cb), self);
614 }
615
616 static void
617 create_video_input (EmpathyCallWindow *self)
618 {
619   EmpathyCallWindowPriv *priv = GET_PRIV (self);
620
621   g_assert (priv->video_input == NULL);
622   priv->video_input = empathy_video_src_new ();
623   gst_object_ref (priv->video_input);
624   gst_object_sink (priv->video_input);
625 }
626
627 static void
628 create_audio_input (EmpathyCallWindow *self)
629 {
630   EmpathyCallWindowPriv *priv = GET_PRIV (self);
631
632   g_assert (priv->audio_input == NULL);
633   priv->audio_input = empathy_audio_src_new ();
634   gst_object_ref (priv->audio_input);
635   gst_object_sink (priv->audio_input);
636
637   tp_g_signal_connect_object (priv->audio_input, "peak-level-changed",
638     G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
639     self, 0);
640 }
641
642 static void
643 add_video_preview_to_pipeline (EmpathyCallWindow *self)
644 {
645   EmpathyCallWindowPriv *priv = GET_PRIV (self);
646   GstElement *preview;
647
648   g_assert (priv->video_preview != NULL);
649   g_assert (priv->pipeline != NULL);
650   g_assert (priv->video_input != NULL);
651   g_assert (priv->video_tee != NULL);
652
653   preview = priv->video_preview_sink;
654
655   if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_input))
656     {
657       g_warning ("Could not add video input to pipeline");
658       return;
659     }
660
661   if (!gst_bin_add (GST_BIN (priv->pipeline), preview))
662     {
663       g_warning ("Could not add video preview to pipeline");
664       return;
665     }
666
667   if (!gst_element_link (priv->video_input, priv->video_tee))
668     {
669       g_warning ("Could not link video input to video tee");
670       return;
671     }
672
673   if (!gst_element_link (priv->video_tee, preview))
674     {
675       g_warning ("Could not link video tee to video preview");
676       return;
677     }
678 }
679
680 static void
681 empathy_call_window_disable_camera_cb (GtkAction *action,
682     EmpathyCallWindow *self)
683 {
684   clutter_actor_destroy (self->priv->preview_hidden_button);
685
686   gtk_toggle_tool_button_set_active (
687       GTK_TOGGLE_TOOL_BUTTON (self->priv->camera_button), FALSE);
688 }
689
690 static void
691 empathy_call_window_minimise_camera_cb (GtkAction *action,
692     EmpathyCallWindow *self)
693 {
694   clutter_actor_hide (self->priv->video_preview);
695   clutter_actor_show (self->priv->preview_hidden_button);
696 }
697
698 static void
699 empathy_call_window_maximise_camera_cb (GtkAction *action,
700     EmpathyCallWindow *self)
701 {
702   clutter_actor_show (self->priv->video_preview);
703   clutter_actor_hide (self->priv->preview_hidden_button);
704 }
705
706 static void
707 empathy_call_window_preview_button_clicked_cb (GtkButton *button,
708     EmpathyCallWindow *self)
709 {
710   GtkWidget *menu;
711
712   menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
713       "/preview-menu");
714   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
715       0, gtk_get_current_event_time ());
716   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
717 }
718
719 static void
720 empathy_call_window_preview_hidden_button_clicked_cb (GtkButton *button,
721     EmpathyCallWindow *self)
722 {
723   GtkWidget *menu;
724
725   menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
726       "/preview-hidden-menu");
727   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
728       0, gtk_get_current_event_time ());
729   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
730 }
731
732 static void
733 create_video_preview (EmpathyCallWindow *self)
734 {
735   EmpathyCallWindowPriv *priv = GET_PRIV (self);
736   ClutterLayoutManager *layout, *layout_center;
737   ClutterActor *preview;
738   ClutterActor *box;
739   ClutterActor *b;
740   GtkWidget *button;
741
742   g_assert (priv->video_preview == NULL);
743
744   preview = clutter_texture_new ();
745   clutter_actor_set_size (preview,
746       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
747   priv->video_preview_sink = clutter_gst_video_sink_new (
748       CLUTTER_TEXTURE (preview));
749
750   /* Flip the video preview */
751   clutter_actor_set_rotation (preview,
752       CLUTTER_Y_AXIS,
753       180,
754       SELF_VIDEO_SECTION_WIDTH * 0.5,
755       0.0,
756       0.0);
757
758   /* Add a little offset to the video preview */
759   layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_END,
760       CLUTTER_BIN_ALIGNMENT_START);
761   priv->video_preview = clutter_box_new (layout);
762   clutter_actor_set_size (priv->video_preview,
763       SELF_VIDEO_SECTION_WIDTH + 10, SELF_VIDEO_SECTION_HEIGTH + 10);
764
765   layout_center = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
766       CLUTTER_BIN_ALIGNMENT_CENTER);
767   box = clutter_box_new (layout_center);
768   clutter_actor_set_size (box,
769       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
770
771   clutter_container_add_actor (CLUTTER_CONTAINER (box), preview);
772   clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_preview), box);
773
774   g_object_set (priv->video_preview_sink,
775       "sync", FALSE,
776       "async", TRUE,
777       NULL);
778
779   /* Translators: this is an "Info" label. It should be as short
780    * as possible. */
781   button = gtk_button_new_with_label (_("i"));
782   b = gtk_clutter_actor_new_with_contents (button);
783
784   clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (layout_center),
785       b,
786       CLUTTER_BIN_ALIGNMENT_END,
787       CLUTTER_BIN_ALIGNMENT_END);
788
789   g_signal_connect (button, "clicked",
790       G_CALLBACK (empathy_call_window_preview_button_clicked_cb),
791       self);
792
793   /* Translators: this is an "Info" label. It should be as short
794    * as possible. */
795   button = gtk_button_new_with_label (_("i"));
796   priv->preview_hidden_button =
797       gtk_clutter_actor_new_with_contents (button);
798
799   clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->video_layout),
800       priv->preview_hidden_button,
801       CLUTTER_BIN_ALIGNMENT_START,
802       CLUTTER_BIN_ALIGNMENT_END);
803
804   clutter_actor_hide (priv->preview_hidden_button);
805
806   g_signal_connect (button, "clicked",
807       G_CALLBACK (empathy_call_window_preview_hidden_button_clicked_cb),
808       self);
809
810   clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (priv->video_layout),
811       priv->video_preview,
812       CLUTTER_BIN_ALIGNMENT_START,
813       CLUTTER_BIN_ALIGNMENT_END);
814 }
815
816 static void
817 play_camera (EmpathyCallWindow *window,
818     gboolean play)
819 {
820   EmpathyCallWindowPriv *priv = GET_PRIV (window);
821   GstElement *preview;
822   GstState state;
823
824   if (priv->video_preview == NULL)
825     {
826       create_video_preview (window);
827       add_video_preview_to_pipeline (window);
828     }
829
830   if (play)
831     state = GST_STATE_PLAYING;
832   else
833     state = GST_STATE_NULL;
834
835   preview = priv->video_preview_sink;
836
837   gst_element_set_state (preview, state);
838   gst_element_set_state (priv->video_input, state);
839   gst_element_set_state (priv->video_tee, state);
840 }
841
842 static void
843 display_video_preview (EmpathyCallWindow *self,
844     gboolean display)
845 {
846   EmpathyCallWindowPriv *priv = GET_PRIV (self);
847
848   if (display)
849     {
850       /* Display the preview and hide the self avatar */
851       DEBUG ("Show video preview");
852
853       play_camera (self, TRUE);
854       clutter_actor_show (priv->video_preview);
855     }
856   else
857     {
858       /* Display the self avatar and hide the preview */
859       DEBUG ("Show self avatar");
860
861       if (priv->video_preview != NULL)
862         {
863           clutter_actor_hide (priv->video_preview);
864           play_camera (self, FALSE);
865         }
866     }
867 }
868
869 static void
870 empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
871 {
872   EmpathyCallWindowPriv *priv = GET_PRIV (window);
873
874   empathy_call_window_status_message (window, _("Connecting…"));
875   priv->call_state = CONNECTING;
876
877   if (priv->outgoing)
878     empathy_sound_manager_start_playing (priv->sound_mgr, GTK_WIDGET (window),
879         EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
880 }
881
882 static void
883 disable_camera (EmpathyCallWindow *self)
884 {
885   EmpathyCallWindowPriv *priv = GET_PRIV (self);
886
887   if (priv->camera_state == CAMERA_STATE_OFF)
888     return;
889
890   DEBUG ("Disable camera");
891
892   display_video_preview (self, FALSE);
893
894   if (priv->camera_state == CAMERA_STATE_ON)
895     empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
896
897   priv->camera_state = CAMERA_STATE_OFF;
898 }
899
900 static void
901 enable_camera (EmpathyCallWindow *self)
902 {
903   EmpathyCallWindowPriv *priv = GET_PRIV (self);
904
905   if (priv->camera_state == CAMERA_STATE_ON)
906     return;
907
908   if (priv->video_input == NULL)
909     {
910       DEBUG ("Can't enable camera, no input");
911       return;
912     }
913
914   DEBUG ("Enable camera");
915
916   empathy_call_window_set_send_video (self, CAMERA_STATE_ON);
917
918   priv->camera_state = CAMERA_STATE_ON;
919 }
920
921 static void
922 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
923   EmpathyCallWindow *self)
924 {
925   if (gtk_toggle_tool_button_get_active (toggle))
926     enable_camera (self);
927   else
928     disable_camera (self);
929 }
930
931 static void
932 create_pipeline (EmpathyCallWindow *self)
933 {
934   EmpathyCallWindowPriv *priv = GET_PRIV (self);
935   GstBus *bus;
936
937   g_assert (priv->pipeline == NULL);
938
939   priv->pipeline = gst_pipeline_new (NULL);
940   priv->pipeline_playing = FALSE;
941
942   priv->video_tee = gst_element_factory_make ("tee", NULL);
943   gst_object_ref (priv->video_tee);
944   gst_object_sink (priv->video_tee);
945
946   gst_bin_add (GST_BIN (priv->pipeline), priv->video_tee);
947
948   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
949   priv->bus_message_source_id = gst_bus_add_watch (bus,
950       empathy_call_window_bus_message, self);
951
952   g_object_unref (bus);
953 }
954
955 static void
956 empathy_call_window_init (EmpathyCallWindow *self)
957 {
958   EmpathyCallWindowPriv *priv;
959   GtkBuilder *gui;
960   GtkWidget *top_vbox;
961   GtkWidget *h;
962   GtkWidget *page;
963   gchar *filename;
964   GtkWidget *scroll;
965   ClutterConstraint *size_constraint;
966   ClutterActor *remote_avatar;
967   GtkStyleContext *context;
968   GdkRGBA rgba;
969   ClutterColor bg;
970
971   priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
972     EMPATHY_TYPE_CALL_WINDOW, EmpathyCallWindowPriv);
973
974   filename = empathy_file_lookup ("empathy-call-window.ui", "src");
975   gui = empathy_builder_get_file (filename,
976     "call_window_vbox", &top_vbox,
977     "errors_vbox", &priv->errors_vbox,
978     "pane", &priv->pane,
979     "statusbar", &priv->statusbar,
980     "redial", &priv->redial_button,
981     "microphone", &priv->mic_button,
982     "camera", &priv->camera_button,
983     "dialpad", &priv->dialpad_button,
984     "toolbar", &priv->toolbar,
985     "menuredial", &priv->redial,
986     "menusidebar", &priv->menu_sidebar,
987     "ui_manager", &priv->ui_manager,
988     "menufullscreen", &priv->menu_fullscreen,
989     "details_vbox",  &priv->details_vbox,
990     "vcodec_encoding_label", &priv->vcodec_encoding_label,
991     "acodec_encoding_label", &priv->acodec_encoding_label,
992     "acodec_decoding_label", &priv->acodec_decoding_label,
993     "vcodec_decoding_label", &priv->vcodec_decoding_label,
994     "audio_remote_candidate_label", &priv->audio_remote_candidate_label,
995     "audio_local_candidate_label", &priv->audio_local_candidate_label,
996     "video_remote_candidate_label", &priv->video_remote_candidate_label,
997     "video_local_candidate_label", &priv->video_local_candidate_label,
998     "video_remote_candidate_info_img", &priv->video_remote_candidate_info_img,
999     "video_local_candidate_info_img", &priv->video_local_candidate_info_img,
1000     "audio_remote_candidate_info_img", &priv->audio_remote_candidate_info_img,
1001     "audio_local_candidate_info_img", &priv->audio_local_candidate_info_img,
1002     NULL);
1003   g_free (filename);
1004
1005   empathy_builder_connect (gui, self,
1006     "menuhangup", "activate", empathy_call_window_hangup_cb,
1007     "hangup", "clicked", empathy_call_window_hangup_cb,
1008     "menuredial", "activate", empathy_call_window_redial_cb,
1009     "redial", "clicked", empathy_call_window_redial_cb,
1010     "menusidebar", "toggled", empathy_call_window_sidebar_cb,
1011     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
1012     "camera", "toggled", empathy_call_window_camera_toggled_cb,
1013     "dialpad", "toggled", empathy_call_window_dialpad_cb,
1014     "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
1015     "menupreviewdisable", "activate", empathy_call_window_disable_camera_cb,
1016     "menupreviewminimise", "activate", empathy_call_window_minimise_camera_cb,
1017     "menupreviewmaximise", "activate", empathy_call_window_maximise_camera_cb,
1018     NULL);
1019
1020   gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1021
1022   priv->lock = g_mutex_new ();
1023
1024   gtk_container_add (GTK_CONTAINER (self), top_vbox);
1025
1026   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
1027   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1028                                   CONTENT_HBOX_BORDER_WIDTH);
1029   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
1030
1031   /* avatar/video box */
1032   priv->video_layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
1033       CLUTTER_BIN_ALIGNMENT_CENTER);
1034
1035   priv->video_box = clutter_box_new (priv->video_layout);
1036
1037   priv->video_container = gtk_clutter_embed_new ();
1038
1039   /* Set the background color to that of the rest of the window */
1040   context = gtk_widget_get_style_context (priv->content_hbox);
1041   gtk_style_context_get_background_color (context,
1042       GTK_STATE_FLAG_NORMAL, &rgba);
1043   bg.red = CLAMP (rgba.red * 255.0, 0, 255);
1044   bg.green = CLAMP (rgba.green * 255.0, 0, 255);
1045   bg.blue = CLAMP (rgba.blue * 255.0, 0, 255);
1046   bg.alpha = CLAMP (rgba.alpha * 255.0, 0, 255);
1047   clutter_stage_set_color (
1048       CLUTTER_STAGE (gtk_clutter_embed_get_stage (
1049           GTK_CLUTTER_EMBED (priv->video_container))),
1050       &bg);
1051
1052   clutter_container_add (
1053       CLUTTER_CONTAINER (gtk_clutter_embed_get_stage (
1054           GTK_CLUTTER_EMBED (priv->video_container))),
1055       priv->video_box,
1056       NULL);
1057
1058   size_constraint = clutter_bind_constraint_new (
1059       gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->video_container)),
1060       CLUTTER_BIND_SIZE, 0);
1061   clutter_actor_add_constraint (priv->video_box, size_constraint);
1062
1063   priv->remote_user_avatar_widget = gtk_image_new ();
1064   remote_avatar = gtk_clutter_actor_new_with_contents (
1065       priv->remote_user_avatar_widget);
1066
1067   clutter_container_add_actor (CLUTTER_CONTAINER (priv->video_box),
1068       remote_avatar);
1069
1070   gtk_box_pack_start (GTK_BOX (priv->content_hbox),
1071       priv->video_container, TRUE, TRUE,
1072       CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1073
1074   create_pipeline (self);
1075   create_video_output_widget (self);
1076   create_audio_input (self);
1077   create_video_input (self);
1078
1079   /* The call will be started as soon the pipeline is playing */
1080   priv->start_call_when_playing = TRUE;
1081
1082   priv->vbox = gtk_vbox_new (FALSE, 3);
1083   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
1084       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1085
1086   empathy_call_window_setup_toolbar (self);
1087
1088   h = gtk_hbox_new (FALSE, 3);
1089   gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
1090
1091   priv->sidebar = ev_sidebar_new ();
1092   g_signal_connect (G_OBJECT (priv->sidebar),
1093     "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
1094   g_signal_connect (G_OBJECT (priv->sidebar),
1095     "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
1096   g_signal_connect (G_OBJECT (priv->sidebar), "changed",
1097     G_CALLBACK (empathy_call_window_sidebar_changed_cb), self);
1098   gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
1099
1100   page = empathy_call_window_create_audio_input (self);
1101   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "audio-input",
1102       _("Audio input"), page);
1103
1104   page = empathy_call_window_create_video_input (self);
1105   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "video-input",
1106       _("Video input"), page);
1107
1108   priv->dtmf_panel = empathy_call_window_create_dtmf (self);
1109   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "dialpad",
1110       _("Dialpad"), priv->dtmf_panel);
1111
1112   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1113
1114   /* Put the details vbox in a scroll window as it can require a lot of
1115    * horizontal space. */
1116   scroll = gtk_scrolled_window_new (NULL, NULL);
1117   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scroll),
1118       priv->details_vbox);
1119
1120   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "details", _("Details"),
1121     scroll);
1122
1123   gtk_widget_show_all (top_vbox);
1124
1125   gtk_widget_hide (priv->sidebar);
1126
1127   priv->fullscreen = empathy_call_window_fullscreen_new (self);
1128 #if 0
1129   empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
1130       priv->video_output);
1131 #endif
1132   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1133       "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
1134
1135   g_signal_connect (G_OBJECT (self), "realize",
1136     G_CALLBACK (empathy_call_window_realized_cb), self);
1137
1138   g_signal_connect (G_OBJECT (self), "delete-event",
1139     G_CALLBACK (empathy_call_window_delete_cb), self);
1140
1141   g_signal_connect (G_OBJECT (self), "window-state-event",
1142     G_CALLBACK (empathy_call_window_state_event_cb), self);
1143
1144   g_signal_connect (G_OBJECT (self), "key-press-event",
1145       G_CALLBACK (empathy_call_window_key_press_cb), self);
1146
1147   priv->timer = g_timer_new ();
1148
1149   g_object_ref (priv->ui_manager);
1150   g_object_unref (gui);
1151
1152   priv->sound_mgr = empathy_sound_manager_dup_singleton ();
1153
1154   empathy_geometry_bind (GTK_WINDOW (self), "call-window");
1155 }
1156
1157 /* Instead of specifying a width and a height, we specify only one size. That's
1158    because we want a square avatar icon.  */
1159 static void
1160 init_contact_avatar_with_size (EmpathyContact *contact,
1161     GtkWidget *image_widget,
1162     gint size)
1163 {
1164   GdkPixbuf *pixbuf_avatar = NULL;
1165
1166   if (contact != NULL)
1167     {
1168       pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1169         size, size);
1170     }
1171
1172   if (pixbuf_avatar == NULL)
1173     {
1174       pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
1175           EMPATHY_IMAGE_AVATAR_DEFAULT, size);
1176     }
1177
1178   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1179
1180   if (pixbuf_avatar != NULL)
1181     g_object_unref (pixbuf_avatar);
1182 }
1183
1184 static void
1185 set_window_title (EmpathyCallWindow *self)
1186 {
1187   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1188   gchar *tmp;
1189
1190   if (priv->contact != NULL)
1191     {
1192       /* translators: Call is a noun and %s is the contact name. This string
1193        * is used in the window title */
1194       tmp = g_strdup_printf (_("Call with %s"),
1195           empathy_contact_get_alias (priv->contact));
1196       gtk_window_set_title (GTK_WINDOW (self), tmp);
1197       g_free (tmp);
1198     }
1199   else
1200     {
1201       gtk_window_set_title (GTK_WINDOW (self), _("Call with %d participants"));
1202     }
1203 }
1204
1205 static void
1206 contact_name_changed_cb (EmpathyContact *contact,
1207     GParamSpec *pspec, EmpathyCallWindow *self)
1208 {
1209   set_window_title (self);
1210 }
1211
1212 static void
1213 contact_avatar_changed_cb (EmpathyContact *contact,
1214     GParamSpec *pspec, GtkWidget *avatar_widget)
1215 {
1216   int size;
1217   GtkAllocation allocation;
1218
1219   gtk_widget_get_allocation (avatar_widget, &allocation);
1220   size = allocation.height;
1221
1222   if (size == 0)
1223     {
1224       /* the widget is not allocated yet, set a default size */
1225       size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1226           REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1227     }
1228
1229   init_contact_avatar_with_size (contact, avatar_widget, size);
1230 }
1231
1232 static void
1233 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1234     EmpathyCallHandler *handler)
1235 {
1236   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1237
1238   g_signal_connect (priv->contact, "notify::name",
1239       G_CALLBACK (contact_name_changed_cb), self);
1240   g_signal_connect (priv->contact, "notify::avatar",
1241     G_CALLBACK (contact_avatar_changed_cb),
1242     priv->remote_user_avatar_widget);
1243
1244   set_window_title (self);
1245
1246   init_contact_avatar_with_size (priv->contact,
1247       priv->remote_user_avatar_widget,
1248       MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1249           REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1250
1251   /* The remote avatar is shown by default and will be hidden when we receive
1252      video from the remote side. */
1253   clutter_actor_hide (priv->video_output);
1254   gtk_widget_show (priv->remote_user_avatar_widget);
1255 }
1256
1257 static void
1258 update_send_codec (EmpathyCallWindow *self,
1259     gboolean audio)
1260 {
1261   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1262   FsCodec *codec;
1263   GtkWidget *widget;
1264   gchar *tmp;
1265
1266   if (audio)
1267     {
1268       codec = empathy_call_handler_get_send_audio_codec (priv->handler);
1269       widget = priv->acodec_encoding_label;
1270     }
1271   else
1272     {
1273       codec = empathy_call_handler_get_send_video_codec (priv->handler);
1274       widget = priv->vcodec_encoding_label;
1275     }
1276
1277   if (codec == NULL)
1278     return;
1279
1280   tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
1281   gtk_label_set_text (GTK_LABEL (widget), tmp);
1282   g_free (tmp);
1283 }
1284
1285 static void
1286 send_audio_codec_notify_cb (GObject *object,
1287     GParamSpec *pspec,
1288     gpointer user_data)
1289 {
1290   EmpathyCallWindow *self = user_data;
1291
1292   update_send_codec (self, TRUE);
1293 }
1294
1295 static void
1296 send_video_codec_notify_cb (GObject *object,
1297     GParamSpec *pspec,
1298     gpointer user_data)
1299 {
1300   EmpathyCallWindow *self = user_data;
1301
1302   update_send_codec (self, FALSE);
1303 }
1304
1305 static void
1306 update_recv_codec (EmpathyCallWindow *self,
1307     gboolean audio)
1308 {
1309   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1310   GList *codecs, *l;
1311   GtkWidget *widget;
1312   GString *str = NULL;
1313
1314   if (audio)
1315     {
1316       codecs = empathy_call_handler_get_recv_audio_codecs (priv->handler);
1317       widget = priv->acodec_decoding_label;
1318     }
1319   else
1320     {
1321       codecs = empathy_call_handler_get_recv_video_codecs (priv->handler);
1322       widget = priv->vcodec_decoding_label;
1323     }
1324
1325   if (codecs == NULL)
1326     return;
1327
1328   for (l = codecs; l != NULL; l = g_list_next (l))
1329     {
1330       FsCodec *codec = l->data;
1331
1332       if (str == NULL)
1333         str = g_string_new (NULL);
1334       else
1335         g_string_append (str, ", ");
1336
1337       g_string_append_printf (str, "%s/%u", codec->encoding_name,
1338           codec->clock_rate);
1339     }
1340
1341   gtk_label_set_text (GTK_LABEL (widget), str->str);
1342   g_string_free (str, TRUE);
1343 }
1344
1345 static void
1346 recv_audio_codecs_notify_cb (GObject *object,
1347     GParamSpec *pspec,
1348     gpointer user_data)
1349 {
1350   EmpathyCallWindow *self = user_data;
1351
1352   update_recv_codec (self, TRUE);
1353 }
1354
1355 static void
1356 recv_video_codecs_notify_cb (GObject *object,
1357     GParamSpec *pspec,
1358     gpointer user_data)
1359 {
1360   EmpathyCallWindow *self = user_data;
1361
1362   update_recv_codec (self, FALSE);
1363 }
1364
1365 static const gchar *
1366 candidate_type_to_str (FsCandidate *candidate)
1367 {
1368   switch (candidate->type)
1369     {
1370       case FS_CANDIDATE_TYPE_HOST:
1371         return "host";
1372       case FS_CANDIDATE_TYPE_SRFLX:
1373         return "server reflexive";
1374       case FS_CANDIDATE_TYPE_PRFLX:
1375         return "peer reflexive";
1376       case FS_CANDIDATE_TYPE_RELAY:
1377         return "relay";
1378       case FS_CANDIDATE_TYPE_MULTICAST:
1379         return "multicast";
1380     }
1381
1382   return NULL;
1383 }
1384
1385 static const gchar *
1386 candidate_type_to_desc (FsCandidate *candidate)
1387 {
1388   switch (candidate->type)
1389     {
1390       case FS_CANDIDATE_TYPE_HOST:
1391         return _("The IP address as seen by the machine");
1392       case FS_CANDIDATE_TYPE_SRFLX:
1393         return _("The IP address as seen by a server on the Internet");
1394       case FS_CANDIDATE_TYPE_PRFLX:
1395         return _("The IP address of the peer as seen by the other side");
1396       case FS_CANDIDATE_TYPE_RELAY:
1397         return _("The IP address of a relay server");
1398       case FS_CANDIDATE_TYPE_MULTICAST:
1399         return _("The IP address of the multicast group");
1400     }
1401
1402   return NULL;
1403 }
1404
1405 static void
1406 update_candidat_widget (EmpathyCallWindow *self,
1407     GtkWidget *label,
1408     GtkWidget *img,
1409     FsCandidate *candidate)
1410 {
1411   gchar *str;
1412
1413   g_assert (candidate != NULL);
1414   str = g_strdup_printf ("%s %u (%s)", candidate->ip,
1415       candidate->port, candidate_type_to_str (candidate));
1416
1417   gtk_label_set_text (GTK_LABEL (label), str);
1418   gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
1419
1420   g_free (str);
1421 }
1422
1423 static void
1424 candidates_changed_cb (GObject *object,
1425     FsMediaType type,
1426     EmpathyCallWindow *self)
1427 {
1428   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1429   FsCandidate *candidate = NULL;
1430
1431   if (type == FS_MEDIA_TYPE_VIDEO)
1432     {
1433       /* Update remote candidate */
1434       candidate = empathy_call_handler_get_video_remote_candidate (
1435           priv->handler);
1436
1437       update_candidat_widget (self, priv->video_remote_candidate_label,
1438           priv->video_remote_candidate_info_img, candidate);
1439
1440       /* Update local candidate */
1441       candidate = empathy_call_handler_get_video_local_candidate (
1442           priv->handler);
1443
1444       update_candidat_widget (self, priv->video_local_candidate_label,
1445           priv->video_local_candidate_info_img, candidate);
1446     }
1447   else
1448     {
1449       /* Update remote candidate */
1450       candidate = empathy_call_handler_get_audio_remote_candidate (
1451           priv->handler);
1452
1453       update_candidat_widget (self, priv->audio_remote_candidate_label,
1454           priv->audio_remote_candidate_info_img, candidate);
1455
1456       /* Update local candidate */
1457       candidate = empathy_call_handler_get_audio_local_candidate (
1458           priv->handler);
1459
1460       update_candidat_widget (self, priv->audio_local_candidate_label,
1461           priv->audio_local_candidate_info_img, candidate);
1462     }
1463 }
1464
1465 static void
1466 empathy_call_window_constructed (GObject *object)
1467 {
1468   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1469   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1470   TpyCallChannel *call;
1471
1472   g_assert (priv->handler != NULL);
1473
1474   g_object_get (priv->handler, "call-channel", &call, NULL);
1475   priv->outgoing = (call == NULL);
1476   if (call != NULL)
1477     g_object_unref (call);
1478
1479   g_object_get (priv->handler, "target-contact", &priv->contact, NULL);
1480   g_assert (priv->contact != NULL);
1481
1482   empathy_call_window_setup_avatars (self, priv->handler);
1483   empathy_call_window_set_state_connecting (self);
1484
1485   if (!empathy_call_handler_has_initial_video (priv->handler))
1486     {
1487       gtk_toggle_tool_button_set_active (
1488           GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1489     }
1490   /* If call has InitialVideo, the preview will be started once the call has
1491    * been started (start_call()). */
1492
1493   update_send_codec (self, TRUE);
1494   update_send_codec (self, FALSE);
1495   update_recv_codec (self, TRUE);
1496   update_recv_codec (self, FALSE);
1497
1498   tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
1499       G_CALLBACK (send_audio_codec_notify_cb), self, 0);
1500   tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
1501       G_CALLBACK (send_video_codec_notify_cb), self, 0);
1502   tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
1503       G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
1504   tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
1505       G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
1506
1507   tp_g_signal_connect_object (priv->handler, "candidates-changed",
1508       G_CALLBACK (candidates_changed_cb), self, 0);
1509 }
1510
1511 static void empathy_call_window_dispose (GObject *object);
1512 static void empathy_call_window_finalize (GObject *object);
1513
1514 static void
1515 empathy_call_window_set_property (GObject *object,
1516   guint property_id, const GValue *value, GParamSpec *pspec)
1517 {
1518   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1519
1520   switch (property_id)
1521     {
1522       case PROP_CALL_HANDLER:
1523         priv->handler = g_value_dup_object (value);
1524         break;
1525       default:
1526         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1527     }
1528 }
1529
1530 static void
1531 empathy_call_window_get_property (GObject *object,
1532   guint property_id, GValue *value, GParamSpec *pspec)
1533 {
1534   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1535
1536   switch (property_id)
1537     {
1538       case PROP_CALL_HANDLER:
1539         g_value_set_object (value, priv->handler);
1540         break;
1541       default:
1542         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1543     }
1544 }
1545
1546 static void
1547 empathy_call_window_class_init (
1548   EmpathyCallWindowClass *empathy_call_window_class)
1549 {
1550   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1551   GParamSpec *param_spec;
1552
1553   g_type_class_add_private (empathy_call_window_class,
1554     sizeof (EmpathyCallWindowPriv));
1555
1556   object_class->constructed = empathy_call_window_constructed;
1557   object_class->set_property = empathy_call_window_set_property;
1558   object_class->get_property = empathy_call_window_get_property;
1559
1560   object_class->dispose = empathy_call_window_dispose;
1561   object_class->finalize = empathy_call_window_finalize;
1562
1563   param_spec = g_param_spec_object ("handler",
1564     "handler", "The call handler",
1565     EMPATHY_TYPE_CALL_HANDLER,
1566     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1567   g_object_class_install_property (object_class,
1568     PROP_CALL_HANDLER, param_spec);
1569 }
1570
1571 void
1572 empathy_call_window_dispose (GObject *object)
1573 {
1574   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1575   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1576
1577   if (priv->dispose_has_run)
1578     return;
1579
1580   priv->dispose_has_run = TRUE;
1581
1582   if (priv->handler != NULL)
1583     {
1584       empathy_call_handler_stop_call (priv->handler);
1585       tp_clear_object (&priv->handler);
1586     }
1587
1588   if (priv->bus_message_source_id != 0)
1589     {
1590       g_source_remove (priv->bus_message_source_id);
1591       priv->bus_message_source_id = 0;
1592     }
1593
1594   tp_clear_object (&priv->pipeline);
1595   tp_clear_object (&priv->video_input);
1596   tp_clear_object (&priv->audio_input);
1597   tp_clear_object (&priv->video_tee);
1598   tp_clear_object (&priv->ui_manager);
1599   tp_clear_object (&priv->fullscreen);
1600
1601   g_list_free_full (priv->notifiers, g_object_unref);
1602
1603   if (priv->timer_id != 0)
1604     g_source_remove (priv->timer_id);
1605   priv->timer_id = 0;
1606
1607   if (priv->contact != NULL)
1608     {
1609       g_signal_handlers_disconnect_by_func (priv->contact,
1610           contact_name_changed_cb, self);
1611       priv->contact = NULL;
1612     }
1613
1614
1615   tp_clear_object (&priv->sound_mgr);
1616
1617   G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1618 }
1619
1620 static void
1621 disconnect_video_output_motion_handler (EmpathyCallWindow *self)
1622 {
1623   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1624
1625   if (priv->video_output_motion_handler_id != 0)
1626     {
1627       g_signal_handler_disconnect (G_OBJECT (priv->video_container),
1628           priv->video_output_motion_handler_id);
1629       priv->video_output_motion_handler_id = 0;
1630     }
1631 }
1632
1633 void
1634 empathy_call_window_finalize (GObject *object)
1635 {
1636   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1637   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1638
1639   disconnect_video_output_motion_handler (self);
1640
1641   /* free any data held directly by the object here */
1642   g_mutex_free (priv->lock);
1643
1644   g_timer_destroy (priv->timer);
1645
1646   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1647 }
1648
1649
1650 EmpathyCallWindow *
1651 empathy_call_window_new (EmpathyCallHandler *handler)
1652 {
1653   return EMPATHY_CALL_WINDOW (
1654     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1655 }
1656
1657 static void
1658 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1659   GstElement *conference, gpointer user_data)
1660 {
1661   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1662   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1663   FsElementAddedNotifier *notifier;
1664   GKeyFile *keyfile;
1665
1666   DEBUG ("Conference added");
1667
1668   /* Add notifier to set the various element properties as needed */
1669   notifier = fs_element_added_notifier_new ();
1670   keyfile = fs_utils_get_default_element_properties (conference);
1671
1672   if (keyfile != NULL)
1673     fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile);
1674
1675   fs_element_added_notifier_add (notifier, GST_BIN (priv->pipeline));
1676
1677   priv->notifiers = g_list_prepend (priv->notifiers, notifier);
1678
1679   gst_bin_add (GST_BIN (priv->pipeline), conference);
1680   gst_element_set_state (conference, GST_STATE_PLAYING);
1681 }
1682
1683 static void
1684 empathy_call_window_conference_removed_cb (EmpathyCallHandler *handler,
1685   GstElement *conference, gpointer user_data)
1686 {
1687   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1688   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1689
1690   gst_bin_remove (GST_BIN (priv->pipeline), conference);
1691   gst_element_set_state (conference, GST_STATE_NULL);
1692 }
1693
1694 static gboolean
1695 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1696 {
1697   GstStateChangeReturn state_change_return;
1698   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1699
1700   if (priv->pipeline == NULL)
1701     return TRUE;
1702
1703   if (priv->bus_message_source_id != 0)
1704     {
1705       g_source_remove (priv->bus_message_source_id);
1706       priv->bus_message_source_id = 0;
1707     }
1708
1709   state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1710
1711   if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1712         state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1713     {
1714       if (priv->pipeline != NULL)
1715         g_object_unref (priv->pipeline);
1716       priv->pipeline = NULL;
1717
1718       g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1719           empathy_call_window_mic_volume_changed_cb, self);
1720
1721       if (priv->audio_output != NULL)
1722         g_object_unref (priv->audio_output);
1723       priv->audio_output = NULL;
1724
1725       if (priv->video_tee != NULL)
1726         g_object_unref (priv->video_tee);
1727       priv->video_tee = NULL;
1728
1729       if (priv->video_preview != NULL)
1730         clutter_actor_destroy (priv->video_preview);
1731       priv->video_preview = NULL;
1732
1733       priv->funnel = NULL;
1734
1735       create_pipeline (self);
1736       /* Call will be started when user will hit the 'redial' button */
1737       priv->start_call_when_playing = FALSE;
1738       gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1739
1740       return TRUE;
1741     }
1742   else
1743     {
1744       g_message ("Error: could not destroy pipeline. Closing call window");
1745       gtk_widget_destroy (GTK_WIDGET (self));
1746
1747       return FALSE;
1748     }
1749 }
1750
1751 static void
1752 reset_details_pane (EmpathyCallWindow *self)
1753 {
1754   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1755
1756   gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label), _("Unknown"));
1757   gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label), _("Unknown"));
1758   gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label), _("Unknown"));
1759   gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label), _("Unknown"));
1760 }
1761
1762 static gboolean
1763 empathy_call_window_disconnected (EmpathyCallWindow *self,
1764     gboolean restart)
1765 {
1766   gboolean could_disconnect = FALSE;
1767   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1768   gboolean could_reset_pipeline;
1769
1770   /* Leave full screen mode if needed */
1771   gtk_window_unfullscreen (GTK_WINDOW (self));
1772
1773   gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1774   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1775
1776   could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1777
1778   if (priv->call_state == CONNECTING)
1779       empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
1780
1781   if (priv->call_state != REDIALING)
1782     priv->call_state = DISCONNECTED;
1783
1784   if (could_reset_pipeline)
1785     {
1786       g_mutex_lock (priv->lock);
1787
1788       g_timer_stop (priv->timer);
1789
1790       if (priv->timer_id != 0)
1791         g_source_remove (priv->timer_id);
1792       priv->timer_id = 0;
1793
1794       g_mutex_unlock (priv->lock);
1795
1796       if (!restart)
1797         /* We are about to destroy the window, no need to update it or create
1798          * a video preview */
1799         return TRUE;
1800
1801       empathy_call_window_status_message (self, _("Disconnected"));
1802
1803       gtk_action_set_sensitive (priv->redial, TRUE);
1804       gtk_widget_set_sensitive (priv->redial_button, TRUE);
1805
1806       /* Unsensitive the camera and mic button */
1807       gtk_widget_set_sensitive (priv->camera_button, FALSE);
1808       gtk_widget_set_sensitive (priv->mic_button, FALSE);
1809
1810       /* Be sure that the mic button is enabled */
1811       gtk_toggle_tool_button_set_active (
1812           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1813
1814       if (priv->camera_state == CAMERA_STATE_ON)
1815         {
1816           /* Restart the preview with the new pipeline. */
1817           display_video_preview (self, TRUE);
1818         }
1819
1820       gtk_progress_bar_set_fraction (
1821           GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1822
1823       /* destroy the video output; it will be recreated when we'll redial */
1824       disconnect_video_output_motion_handler (self);
1825       if (priv->video_output != NULL)
1826         clutter_actor_destroy (priv->video_output);
1827       priv->video_output = NULL;
1828
1829       gtk_widget_show (priv->remote_user_avatar_widget);
1830
1831       reset_details_pane (self);
1832
1833       priv->sending_video = FALSE;
1834       priv->call_started = FALSE;
1835
1836       could_disconnect = TRUE;
1837
1838       /* TODO: display the self avatar of the preview (depends if the "Always
1839        * Show Video Preview" is enabled or not) */
1840     }
1841
1842   return could_disconnect;
1843 }
1844
1845
1846 static void
1847 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1848     gpointer user_data)
1849 {
1850   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1851   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1852
1853   if (empathy_call_window_disconnected (self, TRUE) &&
1854       priv->call_state == REDIALING)
1855       empathy_call_window_restart_call (self);
1856 }
1857
1858 static gboolean
1859 empathy_call_window_sink_removed_cb (EmpathyCallHandler *handler,
1860     GstPad *sink,
1861     FsMediaType media_type,
1862     EmpathyCallWindow *self)
1863 {
1864   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1865
1866   DEBUG ("removing content");
1867
1868   /*
1869    * This assumes that there is only one video stream per channel...
1870    */
1871
1872   if ((guint) media_type == FS_MEDIA_TYPE_VIDEO)
1873     {
1874       if (priv->funnel != NULL)
1875         {
1876           GstElement *output;
1877
1878           output = priv->video_output_sink;
1879
1880           gst_element_set_state (output, GST_STATE_NULL);
1881           gst_element_set_state (priv->funnel, GST_STATE_NULL);
1882
1883           gst_bin_remove (GST_BIN (priv->pipeline), output);
1884           gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1885           priv->funnel = NULL;
1886           return TRUE;
1887         }
1888     }
1889   else if (media_type == FS_MEDIA_TYPE_AUDIO)
1890     {
1891       if (priv->audio_output != NULL)
1892         {
1893           gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1894
1895           gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1896           priv->audio_output = NULL;
1897           return TRUE;
1898         }
1899     }
1900
1901   return FALSE;
1902 }
1903
1904 /* Called with global lock held */
1905 static GstPad *
1906 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1907 {
1908   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1909   GstPad *pad;
1910   GstElement *output;
1911
1912   if (priv->funnel == NULL)
1913     {
1914       output = priv->video_output_sink;
1915
1916       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1917
1918       if (!priv->funnel)
1919         {
1920           g_warning ("Could not create fsfunnel");
1921           return NULL;
1922         }
1923
1924       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
1925         {
1926           gst_object_unref (priv->funnel);
1927           priv->funnel = NULL;
1928           g_warning ("Could  not add funnel to pipeline");
1929           return NULL;
1930         }
1931
1932       if (!gst_bin_add (GST_BIN (priv->pipeline), output))
1933         {
1934           g_warning ("Could not add the video output widget to the pipeline");
1935           goto error;
1936         }
1937
1938       if (!gst_element_link (priv->funnel, output))
1939         {
1940           g_warning ("Could not link output sink to funnel");
1941           goto error_output_added;
1942         }
1943
1944       if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1945         {
1946           g_warning ("Could not start video sink");
1947           goto error_output_added;
1948         }
1949
1950       if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1951         {
1952           g_warning ("Could not start funnel");
1953           goto error_output_added;
1954         }
1955     }
1956
1957   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1958
1959   if (!pad)
1960     g_warning ("Could not get request pad from funnel");
1961
1962   return pad;
1963
1964
1965  error_output_added:
1966
1967   gst_element_set_locked_state (priv->funnel, TRUE);
1968   gst_element_set_locked_state (output, TRUE);
1969
1970   gst_element_set_state (priv->funnel, GST_STATE_NULL);
1971   gst_element_set_state (output, GST_STATE_NULL);
1972
1973   gst_bin_remove (GST_BIN (priv->pipeline), output);
1974   gst_element_set_locked_state (output, FALSE);
1975
1976  error:
1977
1978   gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1979   priv->funnel = NULL;
1980
1981   return NULL;
1982 }
1983
1984 /* Called with global lock held */
1985 static GstPad *
1986 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1987 {
1988   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1989   GstPad *pad;
1990   GstPadTemplate *template;
1991
1992   if (priv->audio_output == NULL)
1993     {
1994       priv->audio_output = empathy_audio_sink_new ();
1995       g_object_ref_sink (priv->audio_output);
1996
1997       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
1998         {
1999           g_warning ("Could not add audio sink to pipeline");
2000           g_object_unref (priv->audio_output);
2001           goto error_add_output;
2002         }
2003
2004       if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2005         {
2006           g_warning ("Could not start audio sink");
2007           goto error;
2008         }
2009     }
2010
2011   template = gst_element_class_get_pad_template (
2012     GST_ELEMENT_GET_CLASS (priv->audio_output), "sink%d");
2013
2014   pad = gst_element_request_pad (priv->audio_output,
2015     template, NULL, NULL);
2016
2017   if (pad == NULL)
2018     {
2019       g_warning ("Could not get sink pad from sink");
2020       return NULL;
2021     }
2022
2023   return pad;
2024
2025 error:
2026   gst_element_set_locked_state (priv->audio_output, TRUE);
2027   gst_element_set_state (priv->audio_output, GST_STATE_NULL);
2028   gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
2029   priv->audio_output = NULL;
2030
2031 error_add_output:
2032
2033   return NULL;
2034 }
2035
2036 static gboolean
2037 empathy_call_window_update_timer (gpointer user_data)
2038 {
2039   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2040   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2041   gchar *str;
2042   gdouble time_;
2043
2044   time_ = g_timer_elapsed (priv->timer, NULL);
2045
2046   /* Translators: 'status - minutes:seconds' the caller has been connected */
2047   str = g_strdup_printf (_("%s â€” %d:%02dm"),
2048       priv->call_state == HELD ? _("On hold") : _("Connected"),
2049       (int) time_ / 60, (int) time_ % 60);
2050   empathy_call_window_status_message (self, str);
2051   g_free (str);
2052
2053   return TRUE;
2054 }
2055
2056 #if 0
2057 static void
2058 display_error (EmpathyCallWindow *self,
2059     TpyCallChannel *call,
2060     const gchar *img,
2061     const gchar *title,
2062     const gchar *desc,
2063     const gchar *details)
2064 {
2065   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2066   GtkWidget *info_bar;
2067   GtkWidget *content_area;
2068   GtkWidget *hbox;
2069   GtkWidget *vbox;
2070   GtkWidget *image;
2071   GtkWidget *label;
2072   gchar *txt;
2073
2074   /* Create info bar */
2075   info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
2076       NULL);
2077
2078   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
2079
2080   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2081
2082   /* hbox containing the image and the messages vbox */
2083   hbox = gtk_hbox_new (FALSE, 3);
2084   gtk_container_add (GTK_CONTAINER (content_area), hbox);
2085
2086   /* Add image */
2087   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
2088   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2089
2090   /* vbox containing the main message and the details expander */
2091   vbox = gtk_vbox_new (FALSE, 3);
2092   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
2093
2094   /* Add text */
2095   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
2096
2097   label = gtk_label_new (NULL);
2098   gtk_label_set_markup (GTK_LABEL (label), txt);
2099   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2100   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2101   g_free (txt);
2102
2103   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
2104
2105   /* Add details */
2106   if (details != NULL)
2107     {
2108       GtkWidget *expander;
2109
2110       expander = gtk_expander_new (_("Technical Details"));
2111
2112       txt = g_strdup_printf ("<i>%s</i>", details);
2113
2114       label = gtk_label_new (NULL);
2115       gtk_label_set_markup (GTK_LABEL (label), txt);
2116       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2117       gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2118       g_free (txt);
2119
2120       gtk_container_add (GTK_CONTAINER (expander), label);
2121       gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
2122     }
2123
2124   g_signal_connect (info_bar, "response",
2125       G_CALLBACK (gtk_widget_destroy), NULL);
2126
2127   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
2128       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
2129   gtk_widget_show_all (info_bar);
2130 }
2131
2132 static gchar *
2133 media_stream_error_to_txt (EmpathyCallWindow *self,
2134     TpyCallChannel *call,
2135     gboolean audio,
2136     TpMediaStreamError error)
2137 {
2138   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2139   const gchar *cm = NULL;
2140   gchar *url;
2141   gchar *result;
2142
2143   switch (error)
2144     {
2145       case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
2146         if (audio)
2147           return g_strdup_printf (
2148               _("%s's software does not understand any of the audio formats "
2149                 "supported by your computer"),
2150             empathy_contact_get_alias (priv->contact));
2151         else
2152           return g_strdup_printf (
2153               _("%s's software does not understand any of the video formats "
2154                 "supported by your computer"),
2155             empathy_contact_get_alias (priv->contact));
2156
2157       case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
2158         return g_strdup_printf (
2159             _("Can't establish a connection to %s. "
2160               "One of you might be on a network that does not allow "
2161               "direct connections."),
2162           empathy_contact_get_alias (priv->contact));
2163
2164       case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
2165           return g_strdup (_("There was a failure on the network"));
2166
2167       case TP_MEDIA_STREAM_ERROR_NO_CODECS:
2168         if (audio)
2169           return g_strdup (_("The audio formats necessary for this call "
2170                 "are not installed on your computer"));
2171         else
2172           return g_strdup (_("The video formats necessary for this call "
2173                 "are not installed on your computer"));
2174
2175       case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
2176         tp_connection_parse_object_path (
2177             tp_channel_borrow_connection (TP_CHANNEL (call)),
2178             NULL, &cm);
2179
2180         url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
2181             "product=Telepathy&amp;component=%s", cm);
2182
2183         result = g_strdup_printf (
2184             _("Something unexpected happened in a Telepathy component. "
2185               "Please <a href=\"%s\">report this bug</a> and attach "
2186               "logs gathered from the 'Debug' window in the Help menu."), url);
2187
2188         g_free (url);
2189         g_free (cm);
2190         return result;
2191
2192       case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
2193         return g_strdup (_("There was a failure in the call engine"));
2194
2195       case TP_MEDIA_STREAM_ERROR_EOS:
2196         return g_strdup (_("The end of the stream was reached"));
2197
2198       case TP_MEDIA_STREAM_ERROR_UNKNOWN:
2199       default:
2200         return NULL;
2201     }
2202 }
2203
2204 static void
2205 empathy_call_window_stream_error (EmpathyCallWindow *self,
2206     TpyCallChannel *call,
2207     gboolean audio,
2208     guint code,
2209     const gchar *msg,
2210     const gchar *icon,
2211     const gchar *title)
2212 {
2213   gchar *desc;
2214
2215   desc = media_stream_error_to_txt (self, call, audio, code);
2216   if (desc == NULL)
2217     {
2218       /* No description, use the error message. That's not great as it's not
2219        * localized but it's better than nothing. */
2220       display_error (self, call, icon, title, msg, NULL);
2221     }
2222   else
2223     {
2224       display_error (self, call, icon, title, desc, msg);
2225       g_free (desc);
2226     }
2227 }
2228
2229 static void
2230 empathy_call_window_audio_stream_error (TpyCallChannel *call,
2231     guint code,
2232     const gchar *msg,
2233     EmpathyCallWindow *self)
2234 {
2235   empathy_call_window_stream_error (self, call, TRUE, code, msg,
2236       "gnome-stock-mic", _("Can't establish audio stream"));
2237 }
2238
2239 static void
2240 empathy_call_window_video_stream_error (TpyCallChannel *call,
2241     guint code,
2242     const gchar *msg,
2243     EmpathyCallWindow *self)
2244 {
2245   empathy_call_window_stream_error (self, call, FALSE, code, msg,
2246       "camera-web", _("Can't establish video stream"));
2247 }
2248 #endif
2249
2250 static void
2251 empathy_call_window_state_changed_cb (EmpathyCallHandler *handler,
2252     TpyCallState state,
2253     EmpathyCallWindow *self)
2254 {
2255   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2256   TpyCallChannel *call;
2257   gboolean can_send_video;
2258
2259   if (state != TPY_CALL_STATE_ACCEPTED)
2260     return;
2261
2262   if (priv->call_state == CONNECTED)
2263     return;
2264
2265   g_timer_start (priv->timer);
2266   priv->call_state = CONNECTED;
2267
2268   empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2269
2270   can_send_video = priv->video_input != NULL &&
2271     empathy_contact_can_voip_video (priv->contact);
2272
2273   g_object_get (priv->handler, "call-channel", &call, NULL);
2274
2275   if (tpy_call_channel_has_dtmf (call))
2276     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2277
2278   if (priv->video_input == NULL)
2279     empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
2280
2281   gtk_widget_set_sensitive (priv->camera_button, can_send_video);
2282
2283   gtk_action_set_sensitive (priv->redial, FALSE);
2284   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2285
2286   gtk_widget_set_sensitive (priv->mic_button, TRUE);
2287
2288   clutter_actor_hide (priv->video_output);
2289   gtk_widget_show (priv->remote_user_avatar_widget);
2290
2291   g_object_unref (call);
2292
2293   g_mutex_lock (priv->lock);
2294
2295   priv->timer_id = g_timeout_add_seconds (1,
2296     empathy_call_window_update_timer, self);
2297
2298   g_mutex_unlock (priv->lock);
2299
2300   empathy_call_window_update_timer (self);
2301
2302   gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
2303 }
2304
2305 static gboolean
2306 emapthy_call_window_show_video_output_cb (gpointer user_data)
2307 {
2308   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2309
2310   gtk_widget_hide (self->priv->remote_user_avatar_widget);
2311   clutter_actor_show (self->priv->video_output);
2312
2313   return FALSE;
2314 }
2315
2316 /* Called from the streaming thread */
2317 static gboolean
2318 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
2319   GstPad *src, guint media_type, gpointer user_data)
2320 {
2321   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2322   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2323   gboolean retval = FALSE;
2324
2325   GstPad *pad;
2326
2327   g_mutex_lock (priv->lock);
2328
2329   switch (media_type)
2330     {
2331       case TP_MEDIA_STREAM_TYPE_AUDIO:
2332         pad = empathy_call_window_get_audio_sink_pad (self);
2333         break;
2334       case TP_MEDIA_STREAM_TYPE_VIDEO:
2335         g_idle_add (emapthy_call_window_show_video_output_cb, self);
2336         pad = empathy_call_window_get_video_sink_pad (self);
2337         break;
2338       default:
2339         g_assert_not_reached ();
2340     }
2341
2342   if (pad == NULL)
2343     goto out;
2344
2345   if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2346       g_warning ("Could not link %s sink pad",
2347           media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2348   else
2349       retval = TRUE;
2350
2351   gst_object_unref (pad);
2352
2353  out:
2354
2355   /* If no sink could be linked, try to add fakesink to prevent the whole call
2356    * aborting */
2357
2358   if (!retval)
2359     {
2360       GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2361
2362       if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2363         {
2364           GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2365           if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2366               GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2367             {
2368               gst_element_set_locked_state (fakesink, TRUE);
2369               gst_element_set_state (fakesink, GST_STATE_NULL);
2370               gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2371             }
2372           else
2373             {
2374               DEBUG ("Could not link real sink, linked fakesink instead");
2375             }
2376           gst_object_unref (sinkpad);
2377         }
2378       else
2379         {
2380           gst_object_unref (fakesink);
2381         }
2382     }
2383
2384
2385   g_mutex_unlock (priv->lock);
2386
2387   return TRUE;
2388 }
2389
2390 static gboolean
2391 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
2392   GstPad *sink, FsMediaType media_type, gpointer user_data)
2393 {
2394   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2395   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2396   GstPad *pad;
2397   gboolean retval = FALSE;
2398
2399   switch (media_type)
2400     {
2401       case FS_MEDIA_TYPE_AUDIO:
2402         if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2403           {
2404             g_warning ("Could not add audio source to pipeline");
2405             break;
2406           }
2407
2408         pad = gst_element_get_static_pad (priv->audio_input, "src");
2409         if (!pad)
2410           {
2411             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2412             g_warning ("Could not get source pad from audio source");
2413             break;
2414           }
2415
2416         if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2417           {
2418             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2419             g_warning ("Could not link audio source to farsight");
2420             break;
2421           }
2422
2423         if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2424           {
2425             g_warning ("Could not start audio source");
2426             gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2427             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2428             break;
2429           }
2430
2431         retval = TRUE;
2432         break;
2433       case FS_MEDIA_TYPE_VIDEO:
2434         if (priv->video_tee != NULL)
2435           {
2436             pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2437             if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2438               {
2439                 g_warning ("Could not link video source input pipeline");
2440                 break;
2441               }
2442             gst_object_unref (pad);
2443           }
2444
2445         retval = TRUE;
2446         break;
2447       default:
2448         g_assert_not_reached ();
2449     }
2450
2451   return retval;
2452 }
2453
2454 static void
2455 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2456 {
2457   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2458   GstElement *preview;
2459
2460   disable_camera (self);
2461
2462   DEBUG ("remove video input");
2463   preview = priv->video_preview_sink;
2464
2465   gst_element_set_state (priv->video_input, GST_STATE_NULL);
2466   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2467   gst_element_set_state (preview, GST_STATE_NULL);
2468
2469   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2470     preview, NULL);
2471
2472   g_object_unref (priv->video_input);
2473   priv->video_input = NULL;
2474   g_object_unref (priv->video_tee);
2475   priv->video_tee = NULL;
2476   clutter_actor_destroy (priv->video_preview);
2477   priv->video_preview = NULL;
2478
2479   gtk_widget_set_sensitive (priv->camera_button, FALSE);
2480 }
2481
2482 static void
2483 start_call (EmpathyCallWindow *self)
2484 {
2485   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2486
2487   priv->call_started = TRUE;
2488   empathy_call_handler_start_call (priv->handler,
2489       gtk_get_current_event_time ());
2490
2491   if (empathy_call_handler_has_initial_video (priv->handler))
2492     {
2493       TpyCallChannel *call;
2494       TpySendingState s;
2495
2496       g_object_get (priv->handler, "call-channel", &call, NULL);
2497       s = tpy_call_channel_get_video_state (call);
2498
2499       if (s == TPY_SENDING_STATE_PENDING_SEND ||
2500           s == TPY_SENDING_STATE_SENDING)
2501         {
2502           /* Enable 'send video' buttons and display the preview */
2503           gtk_toggle_tool_button_set_active (
2504             GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), TRUE);
2505         }
2506       else
2507         {
2508           gtk_toggle_tool_button_set_active (
2509             GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
2510
2511           if (priv->video_preview == NULL)
2512             {
2513               create_video_preview (self);
2514               add_video_preview_to_pipeline (self);
2515             }
2516         }
2517
2518       g_object_unref (call);
2519     }
2520 }
2521
2522 static gboolean
2523 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2524   gpointer user_data)
2525 {
2526   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2527   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2528   GstState newstate;
2529
2530   empathy_call_handler_bus_message (priv->handler, bus, message);
2531
2532   switch (GST_MESSAGE_TYPE (message))
2533     {
2534       case GST_MESSAGE_STATE_CHANGED:
2535         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2536           {
2537             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2538             if (newstate == GST_STATE_PAUSED)
2539                 empathy_call_window_setup_video_input (self);
2540           }
2541         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2542             !priv->call_started)
2543           {
2544             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2545             if (newstate == GST_STATE_PAUSED)
2546               {
2547                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2548                 priv->pipeline_playing = TRUE;
2549
2550                 if (priv->start_call_when_playing)
2551                   start_call (self);
2552               }
2553           }
2554         break;
2555       case GST_MESSAGE_ERROR:
2556         {
2557           GError *error = NULL;
2558           GstElement *gst_error;
2559           gchar *debug;
2560
2561           gst_message_parse_error (message, &error, &debug);
2562           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2563
2564           g_message ("Element error: %s -- %s\n", error->message, debug);
2565
2566           if (g_str_has_prefix (gst_element_get_name (gst_error),
2567                 VIDEO_INPUT_ERROR_PREFIX))
2568             {
2569               /* Remove the video input and continue */
2570               if (priv->video_input != NULL)
2571                 empathy_call_window_remove_video_input (self);
2572               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2573             }
2574           else
2575             {
2576               empathy_call_window_disconnected (self, TRUE);
2577             }
2578           g_error_free (error);
2579           g_free (debug);
2580         }
2581       case GST_MESSAGE_UNKNOWN:
2582       case GST_MESSAGE_EOS:
2583       case GST_MESSAGE_WARNING:
2584       case GST_MESSAGE_INFO:
2585       case GST_MESSAGE_TAG:
2586       case GST_MESSAGE_BUFFERING:
2587       case GST_MESSAGE_STATE_DIRTY:
2588       case GST_MESSAGE_STEP_DONE:
2589       case GST_MESSAGE_CLOCK_PROVIDE:
2590       case GST_MESSAGE_CLOCK_LOST:
2591       case GST_MESSAGE_NEW_CLOCK:
2592       case GST_MESSAGE_STRUCTURE_CHANGE:
2593       case GST_MESSAGE_STREAM_STATUS:
2594       case GST_MESSAGE_APPLICATION:
2595       case GST_MESSAGE_ELEMENT:
2596       case GST_MESSAGE_SEGMENT_START:
2597       case GST_MESSAGE_SEGMENT_DONE:
2598       case GST_MESSAGE_DURATION:
2599       case GST_MESSAGE_ANY:
2600       default:
2601         break;
2602     }
2603
2604   return TRUE;
2605 }
2606
2607 static void
2608 empathy_call_window_members_changed_cb (TpyCallChannel *call,
2609     GHashTable *members,
2610     EmpathyCallWindow *self)
2611 {
2612   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2613   GHashTableIter iter;
2614   gpointer key, value;
2615   gboolean held = FALSE;
2616
2617   g_hash_table_iter_init (&iter, members);
2618   while (g_hash_table_iter_next (&iter, &key, &value))
2619     {
2620       if (GPOINTER_TO_INT (value) & TPY_CALL_MEMBER_FLAG_HELD)
2621         {
2622           /* This assumes this is a 1-1 call, otherwise one participant
2623            * putting the call on hold wouldn't mean the call is on hold
2624            * for everyone. */
2625           held = TRUE;
2626           break;
2627         }
2628     }
2629
2630   if (held)
2631     priv->call_state = HELD;
2632   else if (priv->call_state == HELD)
2633     priv->call_state = CONNECTED;
2634 }
2635
2636 static void
2637 call_handler_notify_call_cb (EmpathyCallHandler *handler,
2638     GParamSpec *spec,
2639     EmpathyCallWindow *self)
2640 {
2641   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2642   TpyCallChannel *call;
2643
2644   g_object_get (priv->handler, "call-channel", &call, NULL);
2645   if (call == NULL)
2646     return;
2647
2648 /* FIXME
2649   tp_g_signal_connect_object (call, "audio-stream-error",
2650       G_CALLBACK (empathy_call_window_audio_stream_error), self, 0);
2651   tp_g_signal_connect_object (call, "video-stream-error",
2652       G_CALLBACK (empathy_call_window_video_stream_error), self, 0);
2653 */
2654
2655   tp_g_signal_connect_object (call, "members-changed",
2656       G_CALLBACK (empathy_call_window_members_changed_cb), self, 0);
2657
2658   g_object_unref (call);
2659 }
2660
2661 static void
2662 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2663 {
2664   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2665   TpyCallChannel *call;
2666
2667   g_signal_connect (priv->handler, "state-changed",
2668     G_CALLBACK (empathy_call_window_state_changed_cb), window);
2669   g_signal_connect (priv->handler, "conference-added",
2670     G_CALLBACK (empathy_call_window_conference_added_cb), window);
2671   g_signal_connect (priv->handler, "conference-removed",
2672     G_CALLBACK (empathy_call_window_conference_removed_cb), window);
2673   g_signal_connect (priv->handler, "closed",
2674     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2675   g_signal_connect (priv->handler, "src-pad-added",
2676     G_CALLBACK (empathy_call_window_src_added_cb), window);
2677   g_signal_connect (priv->handler, "sink-pad-added",
2678     G_CALLBACK (empathy_call_window_sink_added_cb), window);
2679   g_signal_connect (priv->handler, "sink-pad-removed",
2680     G_CALLBACK (empathy_call_window_sink_removed_cb), window);
2681
2682   g_object_get (priv->handler, "call-channel", &call, NULL);
2683   if (call != NULL)
2684     {
2685       call_handler_notify_call_cb (priv->handler, NULL, window);
2686       g_object_unref (call);
2687     }
2688   else
2689     {
2690       /* call-channel doesn't exist yet, we'll connect signals once it has been
2691        * set */
2692       g_signal_connect (priv->handler, "notify::call-channel",
2693         G_CALLBACK (call_handler_notify_call_cb), window);
2694     }
2695
2696   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2697 }
2698
2699 static gboolean
2700 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2701   EmpathyCallWindow *window)
2702 {
2703   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2704
2705   if (priv->pipeline != NULL)
2706     {
2707       if (priv->bus_message_source_id != 0)
2708         {
2709           g_source_remove (priv->bus_message_source_id);
2710           priv->bus_message_source_id = 0;
2711         }
2712
2713       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2714     }
2715
2716   if (priv->call_state == CONNECTING)
2717     empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2718
2719   return FALSE;
2720 }
2721
2722 static void
2723 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2724 {
2725   GtkWidget *menu;
2726   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2727
2728   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2729             "/menubar1");
2730
2731   if (set_fullscreen)
2732     {
2733       gtk_widget_hide (priv->sidebar);
2734       gtk_widget_hide (menu);
2735       gtk_widget_hide (priv->vbox);
2736       gtk_widget_hide (priv->statusbar);
2737       gtk_widget_hide (priv->toolbar);
2738     }
2739   else
2740     {
2741       if (priv->sidebar_was_visible_before_fs)
2742         gtk_widget_show (priv->sidebar);
2743
2744       gtk_widget_show (menu);
2745       gtk_widget_show (priv->vbox);
2746       gtk_widget_show (priv->statusbar);
2747       gtk_widget_show (priv->toolbar);
2748
2749       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2750           priv->original_height_before_fs);
2751     }
2752 }
2753
2754 static void
2755 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2756 {
2757   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2758
2759   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2760       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2761   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2762       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2763
2764   if (priv->video_output != NULL)
2765     {
2766 #if 0
2767       gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2768           priv->video_output, TRUE, TRUE,
2769           set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2770           GTK_PACK_START);
2771 #endif
2772     }
2773
2774   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2775       priv->vbox, TRUE, TRUE,
2776       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2777       GTK_PACK_START);
2778 }
2779
2780 static gboolean
2781 empathy_call_window_state_event_cb (GtkWidget *widget,
2782   GdkEventWindowState *event, EmpathyCallWindow *window)
2783 {
2784   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2785     {
2786       EmpathyCallWindowPriv *priv = GET_PRIV (window);
2787       gboolean set_fullscreen = event->new_window_state &
2788         GDK_WINDOW_STATE_FULLSCREEN;
2789
2790       if (set_fullscreen)
2791         {
2792           gboolean sidebar_was_visible;
2793           GtkAllocation allocation;
2794           gint original_width, original_height;
2795
2796           gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2797           original_width = allocation.width;
2798           original_height = allocation.height;
2799
2800           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2801
2802           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2803           priv->original_width_before_fs = original_width;
2804           priv->original_height_before_fs = original_height;
2805
2806           if (priv->video_output_motion_handler_id == 0 &&
2807                 priv->video_output != NULL)
2808             {
2809               priv->video_output_motion_handler_id = g_signal_connect (
2810                   G_OBJECT (priv->video_container), "motion-notify-event",
2811                   G_CALLBACK (empathy_call_window_video_output_motion_notify),
2812                   window);
2813             }
2814         }
2815       else
2816         {
2817           disconnect_video_output_motion_handler (window);
2818         }
2819
2820       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2821           set_fullscreen);
2822       show_controls (window, set_fullscreen);
2823       show_borders (window, set_fullscreen);
2824       gtk_action_set_stock_id (priv->menu_fullscreen,
2825           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2826       priv->is_fullscreen = set_fullscreen;
2827   }
2828
2829   return FALSE;
2830 }
2831
2832 static void
2833 empathy_call_window_update_sidebar_buttons (EmpathyCallWindow *window,
2834     gboolean toggled)
2835 {
2836   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2837
2838   /* Update dialpad button */
2839   g_signal_handlers_block_by_func (priv->dialpad_button,
2840       empathy_call_window_dialpad_cb, window);
2841   gtk_toggle_tool_button_set_active (
2842       GTK_TOGGLE_TOOL_BUTTON (priv->dialpad_button),
2843       toggled);
2844   g_signal_handlers_unblock_by_func (priv->dialpad_button,
2845       empathy_call_window_dialpad_cb, window);
2846
2847   /* Update sidebar menu */
2848   g_signal_handlers_block_by_func (priv->menu_sidebar,
2849       empathy_call_window_sidebar_cb, window);
2850   gtk_toggle_action_set_active (
2851       GTK_TOGGLE_ACTION (priv->menu_sidebar),
2852       gtk_widget_get_visible (priv->sidebar));
2853   g_signal_handlers_unblock_by_func (priv->menu_sidebar,
2854       empathy_call_window_sidebar_cb, window);
2855 }
2856
2857 static void
2858 empathy_call_window_show_sidebar (EmpathyCallWindow *window,
2859     gboolean active)
2860 {
2861   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2862   int w, h, sidebar_width, handle_size;
2863   GtkAllocation allocation;
2864   gchar *page;
2865   gboolean dialpad_shown;
2866
2867   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2868   w = allocation.width;
2869   h = allocation.height;
2870
2871   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2872
2873   gtk_widget_get_preferred_width (priv->sidebar, &sidebar_width, NULL);
2874
2875   if (active)
2876     {
2877       gtk_widget_show (priv->sidebar);
2878       w += sidebar_width + handle_size;
2879     }
2880   else
2881     {
2882       w -= sidebar_width + handle_size;
2883       gtk_widget_hide (priv->sidebar);
2884     }
2885
2886   if (w > 0 && h > 0)
2887     gtk_window_resize (GTK_WINDOW (window), w, h);
2888
2889   /* Update the 'Dialpad' menu */
2890   page = ev_sidebar_get_current_page (EV_SIDEBAR (priv->sidebar));
2891   dialpad_shown = active && !tp_strdiff (page, "dialpad");
2892   g_free (page);
2893
2894   empathy_call_window_update_sidebar_buttons (window, dialpad_shown);
2895 }
2896
2897 static void
2898 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2899   CameraState state)
2900 {
2901   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2902   TpyCallChannel *call;
2903
2904   priv->sending_video = (state == CAMERA_STATE_ON);
2905
2906   if (state == CAMERA_STATE_ON)
2907     {
2908       /* When we start sending video, we want to show the video preview by
2909          default. */
2910       display_video_preview (window, TRUE);
2911     }
2912   else
2913     {
2914       display_video_preview (window, FALSE);
2915     }
2916
2917   if (priv->call_state != CONNECTED)
2918     return;
2919
2920   g_object_get (priv->handler, "call-channel", &call, NULL);
2921   DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
2922   tpy_call_channel_send_video (call, priv->sending_video);
2923   g_object_unref (call);
2924 }
2925
2926 static void
2927 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2928   EmpathyCallWindow *window)
2929 {
2930   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2931   gboolean active;
2932
2933   active = (gtk_toggle_tool_button_get_active (toggle));
2934
2935   if (active)
2936     {
2937       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2938         priv->volume);
2939       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2940     }
2941   else
2942     {
2943       /* TODO, Instead of setting the input volume to 0 we should probably
2944        * stop sending but this would cause the audio call to drop if both
2945        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2946        * in the future. GNOME #574574
2947        */
2948       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2949         0);
2950       gtk_adjustment_set_value (priv->audio_input_adj, 0);
2951     }
2952 }
2953
2954 static void
2955 empathy_call_window_sidebar_hidden_cb (EvSidebar *sidebar,
2956   EmpathyCallWindow *window)
2957 {
2958   empathy_call_window_show_sidebar (window, FALSE);
2959 }
2960
2961 static void
2962 empathy_call_window_sidebar_shown_cb (EvSidebar *sidebar,
2963   EmpathyCallWindow *window)
2964 {
2965   empathy_call_window_show_sidebar (window, TRUE);
2966 }
2967
2968 static void
2969 empathy_call_window_sidebar_changed_cb (EvSidebar *sidebar,
2970   const gchar *page,
2971   EmpathyCallWindow *window)
2972 {
2973   empathy_call_window_update_sidebar_buttons (window,
2974       !tp_strdiff (page, "dialpad"));
2975 }
2976
2977 static void
2978 empathy_call_window_hangup_cb (gpointer object,
2979                                EmpathyCallWindow *window)
2980 {
2981   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2982
2983   empathy_call_handler_stop_call (priv->handler);
2984
2985   if (empathy_call_window_disconnected (window, FALSE))
2986     gtk_widget_destroy (GTK_WIDGET (window));
2987 }
2988
2989 static void
2990 empathy_call_window_restart_call (EmpathyCallWindow *window)
2991 {
2992   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2993
2994   /* Remove error info bars */
2995   gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
2996       (GtkCallback) gtk_widget_destroy, NULL);
2997
2998   create_video_output_widget (window);
2999
3000   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
3001       G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
3002
3003   /* While the call was disconnected, the input volume might have changed.
3004    * However, since the audio_input source was destroyed, its volume has not
3005    * been updated during that time. That's why we manually update it here */
3006   empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
3007
3008   priv->outgoing = TRUE;
3009   empathy_call_window_set_state_connecting (window);
3010
3011   if (priv->pipeline_playing)
3012     start_call (window);
3013   else
3014     /* call will be started when the pipeline is ready */
3015     priv->start_call_when_playing = TRUE;
3016
3017
3018   empathy_call_window_setup_avatars (window, priv->handler);
3019
3020   gtk_action_set_sensitive (priv->redial, FALSE);
3021   gtk_widget_set_sensitive (priv->redial_button, FALSE);
3022 }
3023
3024 static void
3025 empathy_call_window_redial_cb (gpointer object,
3026     EmpathyCallWindow *window)
3027 {
3028   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3029
3030   if (priv->call_state == CONNECTED)
3031     priv->call_state = REDIALING;
3032
3033   empathy_call_handler_stop_call (priv->handler);
3034
3035   if (priv->call_state != CONNECTED)
3036     empathy_call_window_restart_call (window);
3037 }
3038
3039 static void
3040 empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
3041     EmpathyCallWindow *window)
3042 {
3043   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3044   gboolean active;
3045
3046   active = gtk_toggle_tool_button_get_active (button);
3047
3048   if (active)
3049     ev_sidebar_set_current_page (EV_SIDEBAR (priv->sidebar), "dialpad");
3050
3051   empathy_call_window_show_sidebar (window, active);
3052 }
3053
3054 static void
3055 empathy_call_window_sidebar_cb (GtkToggleAction *menu,
3056     EmpathyCallWindow *self)
3057 {
3058   empathy_call_window_show_sidebar (self,
3059       gtk_toggle_action_get_active (menu));
3060 }
3061
3062 static void
3063 empathy_call_window_fullscreen_cb (gpointer object,
3064                                    EmpathyCallWindow *window)
3065 {
3066   empathy_call_window_fullscreen_toggle (window);
3067 }
3068
3069 static void
3070 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
3071 {
3072   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3073
3074   if (priv->is_fullscreen)
3075     gtk_window_unfullscreen (GTK_WINDOW (window));
3076   else
3077     gtk_window_fullscreen (GTK_WINDOW (window));
3078 }
3079
3080 static gboolean
3081 empathy_call_window_video_button_press_cb (GtkWidget *video_preview,
3082   GdkEventButton *event, EmpathyCallWindow *window)
3083 {
3084   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
3085     {
3086       empathy_call_window_video_menu_popup (window, event->button);
3087       return TRUE;
3088     }
3089
3090   return FALSE;
3091 }
3092
3093 static gboolean
3094 empathy_call_window_key_press_cb (GtkWidget *video_output,
3095   GdkEventKey *event, EmpathyCallWindow *window)
3096 {
3097   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3098
3099   if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
3100     {
3101       /* Since we are in fullscreen mode, toggling will bring us back to
3102          normal mode. */
3103       empathy_call_window_fullscreen_toggle (window);
3104       return TRUE;
3105     }
3106
3107   return FALSE;
3108 }
3109
3110 static gboolean
3111 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
3112     GdkEventMotion *event, EmpathyCallWindow *window)
3113 {
3114   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3115
3116   if (priv->is_fullscreen)
3117     {
3118       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
3119       return TRUE;
3120     }
3121   return FALSE;
3122 }
3123
3124 static void
3125 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
3126   guint button)
3127 {
3128   GtkWidget *menu;
3129   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3130
3131   menu = gtk_ui_manager_get_widget (priv->ui_manager,
3132             "/video-popup");
3133   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
3134       button, gtk_get_current_event_time ());
3135   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
3136 }
3137
3138 static void
3139 empathy_call_window_status_message (EmpathyCallWindow *window,
3140   gchar *message)
3141 {
3142   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3143
3144   if (priv->context_id == 0)
3145     {
3146       priv->context_id = gtk_statusbar_get_context_id (
3147         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
3148     }
3149   else
3150     {
3151       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
3152     }
3153
3154   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
3155     message);
3156 }
3157
3158 static void
3159 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
3160   gdouble value, EmpathyCallWindow *window)
3161 {
3162   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3163
3164   if (priv->audio_output == NULL)
3165     return;
3166
3167   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
3168     value);
3169 }