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