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