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