]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
CallWindow: fix toggling the microphone on and off
[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   g_signal_connect (priv->settings, "changed::"EMPATHY_PREFS_CALL_SOUND_VOLUME,
988       G_CALLBACK (empathy_call_window_prefs_volume_changed_cb), self);
989
990   empathy_geometry_bind (GTK_WINDOW (self), "call-window");
991   /* These signals are used to track the window position and save it
992    * when the window is destroyed. We need to do this as we don't want
993    * the window geometry to be saved with the dialpad taken into account. */
994   g_signal_connect (self, "destroy",
995       G_CALLBACK (empathy_call_window_destroyed_cb), self);
996   g_signal_connect (self, "configure-event",
997       G_CALLBACK (empathy_call_window_configure_event_cb), self);
998   g_signal_connect (self, "window-state-event",
999       G_CALLBACK (empathy_call_window_configure_event_cb), self);
1000
1001   /* Don't display labels in both toolbars */
1002   gtk_toolbar_set_style (GTK_TOOLBAR (priv->toolbar), GTK_TOOLBAR_ICONS);
1003   gtk_toolbar_set_style (GTK_TOOLBAR (priv->bottom_toolbar), GTK_TOOLBAR_ICONS);
1004 }
1005
1006 /* Instead of specifying a width and a height, we specify only one size. That's
1007    because we want a square avatar icon.  */
1008 static void
1009 init_contact_avatar_with_size (EmpathyContact *contact,
1010     GtkWidget *image_widget,
1011     gint size)
1012 {
1013   GdkPixbuf *pixbuf_avatar = NULL;
1014
1015   if (contact != NULL)
1016     {
1017       pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1018         size, size);
1019     }
1020
1021   if (pixbuf_avatar == NULL)
1022     {
1023       pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
1024           EMPATHY_IMAGE_AVATAR_DEFAULT, size);
1025     }
1026
1027   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1028
1029   if (pixbuf_avatar != NULL)
1030     g_object_unref (pixbuf_avatar);
1031 }
1032
1033 static void
1034 set_window_title (EmpathyCallWindow *self)
1035 {
1036   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1037   gchar *tmp;
1038
1039   if (priv->contact != NULL)
1040     {
1041       /* translators: Call is a noun and %s is the contact name. This string
1042        * is used in the window title */
1043       tmp = g_strdup_printf (_("Call with %s"),
1044           empathy_contact_get_alias (priv->contact));
1045       gtk_window_set_title (GTK_WINDOW (self), tmp);
1046       g_free (tmp);
1047     }
1048   else
1049     {
1050       g_warning ("Unknown remote contact!");
1051     }
1052 }
1053
1054 static void
1055 set_remote_user_name (EmpathyCallWindow *self,
1056   EmpathyContact *contact)
1057 {
1058   const gchar *alias = empathy_contact_get_alias (contact);
1059   const gchar *status = empathy_contact_get_status (contact);
1060   gchar *label;
1061
1062   label = g_strdup_printf ("%s\n<small>%s</small>", alias, status);
1063   gtk_label_set_markup (GTK_LABEL (self->priv->remote_user_name_toolbar),
1064       label);
1065   g_free (label);
1066 }
1067
1068 static void
1069 contact_name_changed_cb (EmpathyContact *contact,
1070     GParamSpec *pspec,
1071     EmpathyCallWindow *self)
1072 {
1073   set_window_title (self);
1074   set_remote_user_name (self, contact);
1075 }
1076
1077 static void
1078 contact_presence_changed_cb (EmpathyContact *contact,
1079     GParamSpec *pspec,
1080     EmpathyCallWindow *self)
1081 {
1082   set_remote_user_name (self, contact);
1083 }
1084
1085 static void
1086 contact_avatar_changed_cb (EmpathyContact *contact,
1087     GParamSpec *pspec,
1088     EmpathyCallWindow *self)
1089 {
1090   int size;
1091   GtkAllocation allocation;
1092   GtkWidget *avatar_widget;
1093
1094   avatar_widget = self->priv->remote_user_avatar_widget;
1095
1096   gtk_widget_get_allocation (avatar_widget, &allocation);
1097   size = allocation.height;
1098
1099   if (size == 0)
1100     {
1101       /* the widget is not allocated yet, set a default size */
1102       size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1103           REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1104     }
1105
1106   init_contact_avatar_with_size (contact, avatar_widget, size);
1107
1108   avatar_widget = self->priv->remote_user_avatar_toolbar;
1109
1110   gtk_widget_get_allocation (avatar_widget, &allocation);
1111   size = allocation.height;
1112
1113   if (size == 0)
1114     {
1115       /* the widget is not allocated yet, set a default size */
1116       size = SMALL_TOOLBAR_SIZE;
1117     }
1118
1119   init_contact_avatar_with_size (contact, avatar_widget, size);
1120 }
1121
1122 static void
1123 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
1124     EmpathyCallHandler *handler)
1125 {
1126   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1127
1128   tp_g_signal_connect_object (priv->contact, "notify::name",
1129       G_CALLBACK (contact_name_changed_cb), self, 0);
1130   tp_g_signal_connect_object (priv->contact, "notify::avatar",
1131     G_CALLBACK (contact_avatar_changed_cb), self, 0);
1132   tp_g_signal_connect_object (priv->contact, "notify::presence",
1133       G_CALLBACK (contact_presence_changed_cb), self, 0);
1134
1135   set_window_title (self);
1136   set_remote_user_name (self, priv->contact);
1137
1138   init_contact_avatar_with_size (priv->contact,
1139       priv->remote_user_avatar_widget,
1140       MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1141           REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1142
1143   init_contact_avatar_with_size (priv->contact,
1144       priv->remote_user_avatar_toolbar,
1145       SMALL_TOOLBAR_SIZE);
1146
1147   /* The remote avatar is shown by default and will be hidden when we receive
1148      video from the remote side. */
1149   clutter_actor_hide (priv->video_output);
1150   gtk_widget_show (priv->remote_user_avatar_widget);
1151 }
1152
1153 static void
1154 update_send_codec (EmpathyCallWindow *self,
1155     gboolean audio)
1156 {
1157   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1158   FsCodec *codec;
1159   GtkWidget *widget;
1160   gchar *tmp;
1161
1162   if (audio)
1163     {
1164       codec = empathy_call_handler_get_send_audio_codec (priv->handler);
1165       widget = priv->acodec_encoding_label;
1166     }
1167   else
1168     {
1169       codec = empathy_call_handler_get_send_video_codec (priv->handler);
1170       widget = priv->vcodec_encoding_label;
1171     }
1172
1173   if (codec == NULL)
1174     return;
1175
1176   tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
1177   gtk_label_set_text (GTK_LABEL (widget), tmp);
1178   g_free (tmp);
1179 }
1180
1181 static void
1182 send_audio_codec_notify_cb (GObject *object,
1183     GParamSpec *pspec,
1184     gpointer user_data)
1185 {
1186   EmpathyCallWindow *self = user_data;
1187
1188   update_send_codec (self, TRUE);
1189 }
1190
1191 static void
1192 send_video_codec_notify_cb (GObject *object,
1193     GParamSpec *pspec,
1194     gpointer user_data)
1195 {
1196   EmpathyCallWindow *self = user_data;
1197
1198   update_send_codec (self, FALSE);
1199 }
1200
1201 static void
1202 update_recv_codec (EmpathyCallWindow *self,
1203     gboolean audio)
1204 {
1205   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1206   GList *codecs, *l;
1207   GtkWidget *widget;
1208   GString *str = NULL;
1209
1210   if (audio)
1211     {
1212       codecs = empathy_call_handler_get_recv_audio_codecs (priv->handler);
1213       widget = priv->acodec_decoding_label;
1214     }
1215   else
1216     {
1217       codecs = empathy_call_handler_get_recv_video_codecs (priv->handler);
1218       widget = priv->vcodec_decoding_label;
1219     }
1220
1221   if (codecs == NULL)
1222     return;
1223
1224   for (l = codecs; l != NULL; l = g_list_next (l))
1225     {
1226       FsCodec *codec = l->data;
1227
1228       if (str == NULL)
1229         str = g_string_new (NULL);
1230       else
1231         g_string_append (str, ", ");
1232
1233       g_string_append_printf (str, "%s/%u", codec->encoding_name,
1234           codec->clock_rate);
1235     }
1236
1237   gtk_label_set_text (GTK_LABEL (widget), str->str);
1238   g_string_free (str, TRUE);
1239 }
1240
1241 static void
1242 recv_audio_codecs_notify_cb (GObject *object,
1243     GParamSpec *pspec,
1244     gpointer user_data)
1245 {
1246   EmpathyCallWindow *self = user_data;
1247
1248   update_recv_codec (self, TRUE);
1249 }
1250
1251 static void
1252 recv_video_codecs_notify_cb (GObject *object,
1253     GParamSpec *pspec,
1254     gpointer user_data)
1255 {
1256   EmpathyCallWindow *self = user_data;
1257
1258   update_recv_codec (self, FALSE);
1259 }
1260
1261 static const gchar *
1262 candidate_type_to_str (FsCandidate *candidate)
1263 {
1264   switch (candidate->type)
1265     {
1266       case FS_CANDIDATE_TYPE_HOST:
1267         return "host";
1268       case FS_CANDIDATE_TYPE_SRFLX:
1269         return "server reflexive";
1270       case FS_CANDIDATE_TYPE_PRFLX:
1271         return "peer reflexive";
1272       case FS_CANDIDATE_TYPE_RELAY:
1273         return "relay";
1274       case FS_CANDIDATE_TYPE_MULTICAST:
1275         return "multicast";
1276     }
1277
1278   return NULL;
1279 }
1280
1281 static const gchar *
1282 candidate_type_to_desc (FsCandidate *candidate)
1283 {
1284   switch (candidate->type)
1285     {
1286       case FS_CANDIDATE_TYPE_HOST:
1287         return _("The IP address as seen by the machine");
1288       case FS_CANDIDATE_TYPE_SRFLX:
1289         return _("The IP address as seen by a server on the Internet");
1290       case FS_CANDIDATE_TYPE_PRFLX:
1291         return _("The IP address of the peer as seen by the other side");
1292       case FS_CANDIDATE_TYPE_RELAY:
1293         return _("The IP address of a relay server");
1294       case FS_CANDIDATE_TYPE_MULTICAST:
1295         return _("The IP address of the multicast group");
1296     }
1297
1298   return NULL;
1299 }
1300
1301 static void
1302 update_candidat_widget (EmpathyCallWindow *self,
1303     GtkWidget *label,
1304     GtkWidget *img,
1305     FsCandidate *candidate)
1306 {
1307   gchar *str;
1308
1309   g_assert (candidate != NULL);
1310   str = g_strdup_printf ("%s %u (%s)", candidate->ip,
1311       candidate->port, candidate_type_to_str (candidate));
1312
1313   gtk_label_set_text (GTK_LABEL (label), str);
1314   gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
1315
1316   g_free (str);
1317 }
1318
1319 static void
1320 candidates_changed_cb (GObject *object,
1321     FsMediaType type,
1322     EmpathyCallWindow *self)
1323 {
1324   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1325   FsCandidate *candidate = NULL;
1326
1327   if (type == FS_MEDIA_TYPE_VIDEO)
1328     {
1329       /* Update remote candidate */
1330       candidate = empathy_call_handler_get_video_remote_candidate (
1331           priv->handler);
1332
1333       update_candidat_widget (self, priv->video_remote_candidate_label,
1334           priv->video_remote_candidate_info_img, candidate);
1335
1336       /* Update local candidate */
1337       candidate = empathy_call_handler_get_video_local_candidate (
1338           priv->handler);
1339
1340       update_candidat_widget (self, priv->video_local_candidate_label,
1341           priv->video_local_candidate_info_img, candidate);
1342     }
1343   else
1344     {
1345       /* Update remote candidate */
1346       candidate = empathy_call_handler_get_audio_remote_candidate (
1347           priv->handler);
1348
1349       update_candidat_widget (self, priv->audio_remote_candidate_label,
1350           priv->audio_remote_candidate_info_img, candidate);
1351
1352       /* Update local candidate */
1353       candidate = empathy_call_handler_get_audio_local_candidate (
1354           priv->handler);
1355
1356       update_candidat_widget (self, priv->audio_local_candidate_label,
1357           priv->audio_local_candidate_info_img, candidate);
1358     }
1359 }
1360
1361 static void
1362 empathy_call_window_constructed (GObject *object)
1363 {
1364   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1365   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1366   TpyCallChannel *call;
1367
1368   g_assert (priv->handler != NULL);
1369
1370   g_object_get (priv->handler, "call-channel", &call, NULL);
1371   priv->outgoing = (call == NULL);
1372   if (call != NULL)
1373     g_object_unref (call);
1374
1375   g_object_get (priv->handler, "target-contact", &priv->contact, NULL);
1376   g_assert (priv->contact != NULL);
1377
1378   empathy_call_window_setup_avatars (self, priv->handler);
1379   empathy_call_window_set_state_connecting (self);
1380
1381   if (!empathy_call_handler_has_initial_video (priv->handler))
1382     {
1383       gtk_toggle_tool_button_set_active (
1384           GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1385     }
1386   /* If call has InitialVideo, the preview will be started once the call has
1387    * been started (start_call()). */
1388
1389   update_send_codec (self, TRUE);
1390   update_send_codec (self, FALSE);
1391   update_recv_codec (self, TRUE);
1392   update_recv_codec (self, FALSE);
1393
1394   tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
1395       G_CALLBACK (send_audio_codec_notify_cb), self, 0);
1396   tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
1397       G_CALLBACK (send_video_codec_notify_cb), self, 0);
1398   tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
1399       G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
1400   tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
1401       G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
1402
1403   tp_g_signal_connect_object (priv->handler, "candidates-changed",
1404       G_CALLBACK (candidates_changed_cb), self, 0);
1405 }
1406
1407 static void empathy_call_window_dispose (GObject *object);
1408 static void empathy_call_window_finalize (GObject *object);
1409
1410 static void
1411 empathy_call_window_set_property (GObject *object,
1412   guint property_id, const GValue *value, GParamSpec *pspec)
1413 {
1414   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1415
1416   switch (property_id)
1417     {
1418       case PROP_CALL_HANDLER:
1419         priv->handler = g_value_dup_object (value);
1420         break;
1421       default:
1422         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1423     }
1424 }
1425
1426 static void
1427 empathy_call_window_get_property (GObject *object,
1428   guint property_id, GValue *value, GParamSpec *pspec)
1429 {
1430   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1431
1432   switch (property_id)
1433     {
1434       case PROP_CALL_HANDLER:
1435         g_value_set_object (value, priv->handler);
1436         break;
1437       default:
1438         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1439     }
1440 }
1441
1442 static void
1443 empathy_call_window_class_init (
1444   EmpathyCallWindowClass *empathy_call_window_class)
1445 {
1446   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1447   GParamSpec *param_spec;
1448
1449   g_type_class_add_private (empathy_call_window_class,
1450     sizeof (EmpathyCallWindowPriv));
1451
1452   object_class->constructed = empathy_call_window_constructed;
1453   object_class->set_property = empathy_call_window_set_property;
1454   object_class->get_property = empathy_call_window_get_property;
1455
1456   object_class->dispose = empathy_call_window_dispose;
1457   object_class->finalize = empathy_call_window_finalize;
1458
1459   param_spec = g_param_spec_object ("handler",
1460     "handler", "The call handler",
1461     EMPATHY_TYPE_CALL_HANDLER,
1462     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1463   g_object_class_install_property (object_class,
1464     PROP_CALL_HANDLER, param_spec);
1465 }
1466
1467 void
1468 empathy_call_window_dispose (GObject *object)
1469 {
1470   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1471   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1472
1473   if (priv->dispose_has_run)
1474     return;
1475
1476   priv->dispose_has_run = TRUE;
1477
1478   if (priv->handler != NULL)
1479     {
1480       empathy_call_handler_stop_call (priv->handler);
1481       tp_clear_object (&priv->handler);
1482     }
1483
1484   if (priv->bus_message_source_id != 0)
1485     {
1486       g_source_remove (priv->bus_message_source_id);
1487       priv->bus_message_source_id = 0;
1488     }
1489
1490   if (priv->got_video_src > 0)
1491     {
1492       g_source_remove (priv->got_video_src);
1493       priv->got_video_src = 0;
1494     }
1495
1496   tp_clear_object (&priv->pipeline);
1497   tp_clear_object (&priv->video_input);
1498   tp_clear_object (&priv->audio_input);
1499   tp_clear_object (&priv->video_tee);
1500   tp_clear_object (&priv->ui_manager);
1501   tp_clear_object (&priv->fullscreen);
1502   tp_clear_object (&priv->camera_monitor);
1503   tp_clear_object (&priv->settings);
1504
1505   g_list_free_full (priv->notifiers, g_object_unref);
1506
1507   if (priv->timer_id != 0)
1508     g_source_remove (priv->timer_id);
1509   priv->timer_id = 0;
1510
1511   if (priv->contact != NULL)
1512     {
1513       g_signal_handlers_disconnect_by_func (priv->contact,
1514           contact_name_changed_cb, self);
1515       priv->contact = NULL;
1516     }
1517
1518
1519   tp_clear_object (&priv->sound_mgr);
1520
1521   tp_clear_object (&priv->mic_menu);
1522
1523   G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1524 }
1525
1526 static void
1527 disconnect_video_output_motion_handler (EmpathyCallWindow *self)
1528 {
1529   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1530
1531   if (priv->video_output_motion_handler_id != 0)
1532     {
1533       g_signal_handler_disconnect (G_OBJECT (priv->video_container),
1534           priv->video_output_motion_handler_id);
1535       priv->video_output_motion_handler_id = 0;
1536     }
1537 }
1538
1539 void
1540 empathy_call_window_finalize (GObject *object)
1541 {
1542   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1543   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1544
1545   disconnect_video_output_motion_handler (self);
1546
1547   /* free any data held directly by the object here */
1548   g_mutex_free (priv->lock);
1549
1550   g_timer_destroy (priv->timer);
1551
1552   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1553 }
1554
1555
1556 EmpathyCallWindow *
1557 empathy_call_window_new (EmpathyCallHandler *handler)
1558 {
1559   return EMPATHY_CALL_WINDOW (
1560     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1561 }
1562
1563 static void
1564 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1565   GstElement *conference, gpointer user_data)
1566 {
1567   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1568   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1569   FsElementAddedNotifier *notifier;
1570   GKeyFile *keyfile;
1571
1572   DEBUG ("Conference added");
1573
1574   /* Add notifier to set the various element properties as needed */
1575   notifier = fs_element_added_notifier_new ();
1576   keyfile = fs_utils_get_default_element_properties (conference);
1577
1578   if (keyfile != NULL)
1579     fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile);
1580
1581   fs_element_added_notifier_add (notifier, GST_BIN (priv->pipeline));
1582
1583   priv->notifiers = g_list_prepend (priv->notifiers, notifier);
1584
1585   gst_bin_add (GST_BIN (priv->pipeline), conference);
1586   gst_element_set_state (conference, GST_STATE_PLAYING);
1587 }
1588
1589 static void
1590 empathy_call_window_conference_removed_cb (EmpathyCallHandler *handler,
1591   GstElement *conference, gpointer user_data)
1592 {
1593   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1594   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1595
1596   gst_bin_remove (GST_BIN (priv->pipeline), conference);
1597   gst_element_set_state (conference, GST_STATE_NULL);
1598 }
1599
1600 static gboolean
1601 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1602 {
1603   GstStateChangeReturn state_change_return;
1604   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1605
1606   if (priv->pipeline == NULL)
1607     return TRUE;
1608
1609   if (priv->bus_message_source_id != 0)
1610     {
1611       g_source_remove (priv->bus_message_source_id);
1612       priv->bus_message_source_id = 0;
1613     }
1614
1615   state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1616
1617   if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1618         state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1619     {
1620       if (priv->pipeline != NULL)
1621         g_object_unref (priv->pipeline);
1622       priv->pipeline = NULL;
1623
1624       if (priv->audio_output != NULL)
1625         g_object_unref (priv->audio_output);
1626       priv->audio_output = NULL;
1627
1628       if (priv->video_tee != NULL)
1629         g_object_unref (priv->video_tee);
1630       priv->video_tee = NULL;
1631
1632       if (priv->video_preview != NULL)
1633         clutter_actor_destroy (priv->video_preview);
1634       priv->video_preview = NULL;
1635
1636       priv->funnel = NULL;
1637
1638       create_pipeline (self);
1639       /* Call will be started when user will hit the 'redial' button */
1640       priv->start_call_when_playing = FALSE;
1641       gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1642
1643       return TRUE;
1644     }
1645   else
1646     {
1647       g_message ("Error: could not destroy pipeline. Closing call window");
1648       gtk_widget_destroy (GTK_WIDGET (self));
1649
1650       return FALSE;
1651     }
1652 }
1653
1654 static void
1655 reset_details_pane (EmpathyCallWindow *self)
1656 {
1657   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1658
1659   gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label), _("Unknown"));
1660   gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label), _("Unknown"));
1661   gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label), _("Unknown"));
1662   gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label), _("Unknown"));
1663 }
1664
1665 static gboolean
1666 empathy_call_window_disconnected (EmpathyCallWindow *self,
1667     gboolean restart)
1668 {
1669   gboolean could_disconnect = FALSE;
1670   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1671   gboolean could_reset_pipeline;
1672
1673   /* Leave full screen mode if needed */
1674   gtk_window_unfullscreen (GTK_WINDOW (self));
1675
1676   gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1677   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1678
1679   could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1680
1681   if (priv->call_state == CONNECTING)
1682       empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
1683
1684   if (priv->call_state != REDIALING)
1685     priv->call_state = DISCONNECTED;
1686
1687   if (could_reset_pipeline)
1688     {
1689       g_mutex_lock (priv->lock);
1690
1691       g_timer_stop (priv->timer);
1692
1693       if (priv->timer_id != 0)
1694         g_source_remove (priv->timer_id);
1695       priv->timer_id = 0;
1696
1697       g_mutex_unlock (priv->lock);
1698
1699       if (!restart)
1700         /* We are about to destroy the window, no need to update it or create
1701          * a video preview */
1702         return TRUE;
1703
1704       empathy_call_window_status_message (self, _("Disconnected"));
1705
1706       empathy_call_window_show_hangup_button (self, FALSE);
1707
1708       /* Unsensitive the camera and mic button */
1709       gtk_widget_set_sensitive (priv->camera_button, FALSE);
1710       gtk_widget_set_sensitive (priv->mic_button, FALSE);
1711
1712       /* Be sure that the mic button is enabled */
1713       gtk_toggle_tool_button_set_active (
1714           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1715
1716       if (priv->camera_state == CAMERA_STATE_ON)
1717         {
1718           /* Restart the preview with the new pipeline. */
1719           display_video_preview (self, TRUE);
1720         }
1721
1722       /* destroy the video output; it will be recreated when we'll redial */
1723       disconnect_video_output_motion_handler (self);
1724       if (priv->video_output != NULL)
1725         clutter_actor_destroy (priv->video_output);
1726       priv->video_output = NULL;
1727       if (priv->got_video_src > 0)
1728         {
1729           g_source_remove (priv->got_video_src);
1730           priv->got_video_src = 0;
1731         }
1732
1733       gtk_widget_show (priv->remote_user_avatar_widget);
1734
1735       reset_details_pane (self);
1736
1737       priv->sending_video = FALSE;
1738       priv->call_started = FALSE;
1739
1740       could_disconnect = TRUE;
1741
1742       /* TODO: display the self avatar of the preview (depends if the "Always
1743        * Show Video Preview" is enabled or not) */
1744     }
1745
1746   return could_disconnect;
1747 }
1748
1749
1750 static void
1751 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1752     gpointer user_data)
1753 {
1754   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1755   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1756
1757   if (empathy_call_window_disconnected (self, TRUE) &&
1758       priv->call_state == REDIALING)
1759       empathy_call_window_restart_call (self);
1760 }
1761
1762 static gboolean
1763 empathy_call_window_sink_removed_cb (EmpathyCallHandler *handler,
1764     GstPad *sink,
1765     FsMediaType media_type,
1766     EmpathyCallWindow *self)
1767 {
1768   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1769
1770   DEBUG ("removing content");
1771
1772   /*
1773    * This assumes that there is only one video stream per channel...
1774    */
1775
1776   if ((guint) media_type == FS_MEDIA_TYPE_VIDEO)
1777     {
1778       if (priv->funnel != NULL)
1779         {
1780           GstElement *output;
1781
1782           output = priv->video_output_sink;
1783
1784           gst_element_set_state (output, GST_STATE_NULL);
1785           gst_element_set_state (priv->funnel, GST_STATE_NULL);
1786
1787           gst_bin_remove (GST_BIN (priv->pipeline), output);
1788           gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1789           priv->funnel = NULL;
1790           return TRUE;
1791         }
1792     }
1793   else if (media_type == FS_MEDIA_TYPE_AUDIO)
1794     {
1795       if (priv->audio_output != NULL)
1796         {
1797           gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1798
1799           gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1800           priv->audio_output = NULL;
1801           return TRUE;
1802         }
1803     }
1804
1805   return FALSE;
1806 }
1807
1808 /* Called with global lock held */
1809 static GstPad *
1810 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1811 {
1812   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1813   GstPad *pad;
1814   GstElement *output;
1815
1816   if (priv->funnel == NULL)
1817     {
1818       output = priv->video_output_sink;
1819
1820       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1821
1822       if (!priv->funnel)
1823         {
1824           g_warning ("Could not create fsfunnel");
1825           return NULL;
1826         }
1827
1828       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
1829         {
1830           gst_object_unref (priv->funnel);
1831           priv->funnel = NULL;
1832           g_warning ("Could  not add funnel to pipeline");
1833           return NULL;
1834         }
1835
1836       if (!gst_bin_add (GST_BIN (priv->pipeline), output))
1837         {
1838           g_warning ("Could not add the video output widget to the pipeline");
1839           goto error;
1840         }
1841
1842       if (!gst_element_link (priv->funnel, output))
1843         {
1844           g_warning ("Could not link output sink to funnel");
1845           goto error_output_added;
1846         }
1847
1848       if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1849         {
1850           g_warning ("Could not start video sink");
1851           goto error_output_added;
1852         }
1853
1854       if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1855         {
1856           g_warning ("Could not start funnel");
1857           goto error_output_added;
1858         }
1859     }
1860
1861   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1862
1863   if (!pad)
1864     g_warning ("Could not get request pad from funnel");
1865
1866   return pad;
1867
1868
1869  error_output_added:
1870
1871   gst_element_set_locked_state (priv->funnel, TRUE);
1872   gst_element_set_locked_state (output, TRUE);
1873
1874   gst_element_set_state (priv->funnel, GST_STATE_NULL);
1875   gst_element_set_state (output, GST_STATE_NULL);
1876
1877   gst_bin_remove (GST_BIN (priv->pipeline), output);
1878   gst_element_set_locked_state (output, FALSE);
1879
1880  error:
1881
1882   gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1883   priv->funnel = NULL;
1884
1885   return NULL;
1886 }
1887
1888 /* Called with global lock held */
1889 static GstPad *
1890 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1891 {
1892   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1893   GstPad *pad;
1894   GstPadTemplate *template;
1895
1896   if (priv->audio_output == NULL)
1897     {
1898       priv->audio_output = empathy_audio_sink_new ();
1899       g_object_ref_sink (priv->audio_output);
1900
1901       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
1902         {
1903           g_warning ("Could not add audio sink to pipeline");
1904           g_object_unref (priv->audio_output);
1905           goto error_add_output;
1906         }
1907
1908       if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
1909         {
1910           g_warning ("Could not start audio sink");
1911           goto error;
1912         }
1913     }
1914
1915   template = gst_element_class_get_pad_template (
1916     GST_ELEMENT_GET_CLASS (priv->audio_output), "sink%d");
1917
1918   pad = gst_element_request_pad (priv->audio_output,
1919     template, NULL, NULL);
1920
1921   if (pad == NULL)
1922     {
1923       g_warning ("Could not get sink pad from sink");
1924       return NULL;
1925     }
1926
1927   return pad;
1928
1929 error:
1930   gst_element_set_locked_state (priv->audio_output, TRUE);
1931   gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1932   gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1933   priv->audio_output = NULL;
1934
1935 error_add_output:
1936
1937   return NULL;
1938 }
1939
1940 static gboolean
1941 empathy_call_window_update_timer (gpointer user_data)
1942 {
1943   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1944   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1945   const gchar *status;
1946   gchar *str;
1947   gdouble time_;
1948
1949   time_ = g_timer_elapsed (priv->timer, NULL);
1950
1951   if (priv->call_state == HELD)
1952     status = _("On hold");
1953   else if (!gtk_toggle_tool_button_get_active (
1954       GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
1955     status = _("Mute");
1956   else
1957     status = _("Duration");
1958
1959   /* Translators: 'status - minutes:seconds' the caller has been connected */
1960   str = g_strdup_printf (_("%s â€” %d:%02dm"),
1961       status,
1962       (int) time_ / 60, (int) time_ % 60);
1963   empathy_call_window_status_message (self, str);
1964   g_free (str);
1965
1966   return TRUE;
1967 }
1968
1969 #if 0
1970 static void
1971 display_error (EmpathyCallWindow *self,
1972     TpyCallChannel *call,
1973     const gchar *img,
1974     const gchar *title,
1975     const gchar *desc,
1976     const gchar *details)
1977 {
1978   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1979   GtkWidget *info_bar;
1980   GtkWidget *content_area;
1981   GtkWidget *hbox;
1982   GtkWidget *vbox;
1983   GtkWidget *image;
1984   GtkWidget *label;
1985   gchar *txt;
1986
1987   /* Create info bar */
1988   info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1989       NULL);
1990
1991   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1992
1993   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1994
1995   /* hbox containing the image and the messages vbox */
1996   hbox = gtk_hbox_new (FALSE, 3);
1997   gtk_container_add (GTK_CONTAINER (content_area), hbox);
1998
1999   /* Add image */
2000   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
2001   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2002
2003   /* vbox containing the main message and the details expander */
2004   vbox = gtk_vbox_new (FALSE, 3);
2005   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
2006
2007   /* Add text */
2008   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
2009
2010   label = gtk_label_new (NULL);
2011   gtk_label_set_markup (GTK_LABEL (label), txt);
2012   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2013   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2014   g_free (txt);
2015
2016   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
2017
2018   /* Add details */
2019   if (details != NULL)
2020     {
2021       GtkWidget *expander;
2022
2023       expander = gtk_expander_new (_("Technical Details"));
2024
2025       txt = g_strdup_printf ("<i>%s</i>", details);
2026
2027       label = gtk_label_new (NULL);
2028       gtk_label_set_markup (GTK_LABEL (label), txt);
2029       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2030       gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2031       g_free (txt);
2032
2033       gtk_container_add (GTK_CONTAINER (expander), label);
2034       gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
2035     }
2036
2037   g_signal_connect (info_bar, "response",
2038       G_CALLBACK (gtk_widget_destroy), NULL);
2039
2040   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
2041       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
2042   gtk_widget_show_all (info_bar);
2043 }
2044
2045 static gchar *
2046 media_stream_error_to_txt (EmpathyCallWindow *self,
2047     TpyCallChannel *call,
2048     gboolean audio,
2049     TpMediaStreamError error)
2050 {
2051   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2052   const gchar *cm = NULL;
2053   gchar *url;
2054   gchar *result;
2055
2056   switch (error)
2057     {
2058       case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
2059         if (audio)
2060           return g_strdup_printf (
2061               _("%s's software does not understand any of the audio formats "
2062                 "supported by your computer"),
2063             empathy_contact_get_alias (priv->contact));
2064         else
2065           return g_strdup_printf (
2066               _("%s's software does not understand any of the video formats "
2067                 "supported by your computer"),
2068             empathy_contact_get_alias (priv->contact));
2069
2070       case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
2071         return g_strdup_printf (
2072             _("Can't establish a connection to %s. "
2073               "One of you might be on a network that does not allow "
2074               "direct connections."),
2075           empathy_contact_get_alias (priv->contact));
2076
2077       case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
2078           return g_strdup (_("There was a failure on the network"));
2079
2080       case TP_MEDIA_STREAM_ERROR_NO_CODECS:
2081         if (audio)
2082           return g_strdup (_("The audio formats necessary for this call "
2083                 "are not installed on your computer"));
2084         else
2085           return g_strdup (_("The video formats necessary for this call "
2086                 "are not installed on your computer"));
2087
2088       case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
2089         tp_connection_parse_object_path (
2090             tp_channel_borrow_connection (TP_CHANNEL (call)),
2091             NULL, &cm);
2092
2093         url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
2094             "product=Telepathy&amp;component=%s", cm);
2095
2096         result = g_strdup_printf (
2097             _("Something unexpected happened in a Telepathy component. "
2098               "Please <a href=\"%s\">report this bug</a> and attach "
2099               "logs gathered from the 'Debug' window in the Help menu."), url);
2100
2101         g_free (url);
2102         g_free (cm);
2103         return result;
2104
2105       case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
2106         return g_strdup (_("There was a failure in the call engine"));
2107
2108       case TP_MEDIA_STREAM_ERROR_EOS:
2109         return g_strdup (_("The end of the stream was reached"));
2110
2111       case TP_MEDIA_STREAM_ERROR_UNKNOWN:
2112       default:
2113         return NULL;
2114     }
2115 }
2116
2117 static void
2118 empathy_call_window_stream_error (EmpathyCallWindow *self,
2119     TpyCallChannel *call,
2120     gboolean audio,
2121     guint code,
2122     const gchar *msg,
2123     const gchar *icon,
2124     const gchar *title)
2125 {
2126   gchar *desc;
2127
2128   desc = media_stream_error_to_txt (self, call, audio, code);
2129   if (desc == NULL)
2130     {
2131       /* No description, use the error message. That's not great as it's not
2132        * localized but it's better than nothing. */
2133       display_error (self, call, icon, title, msg, NULL);
2134     }
2135   else
2136     {
2137       display_error (self, call, icon, title, desc, msg);
2138       g_free (desc);
2139     }
2140 }
2141
2142 static void
2143 empathy_call_window_audio_stream_error (TpyCallChannel *call,
2144     guint code,
2145     const gchar *msg,
2146     EmpathyCallWindow *self)
2147 {
2148   empathy_call_window_stream_error (self, call, TRUE, code, msg,
2149       "gnome-stock-mic", _("Can't establish audio stream"));
2150 }
2151
2152 static void
2153 empathy_call_window_video_stream_error (TpyCallChannel *call,
2154     guint code,
2155     const gchar *msg,
2156     EmpathyCallWindow *self)
2157 {
2158   empathy_call_window_stream_error (self, call, FALSE, code, msg,
2159       "camera-web", _("Can't establish video stream"));
2160 }
2161 #endif
2162
2163 static void
2164 empathy_call_window_state_changed_cb (EmpathyCallHandler *handler,
2165     TpyCallState state,
2166     EmpathyCallWindow *self)
2167 {
2168   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2169   TpyCallChannel *call;
2170   gboolean can_send_video;
2171
2172   if (state != TPY_CALL_STATE_ACCEPTED)
2173     return;
2174
2175   if (priv->call_state == CONNECTED)
2176     return;
2177
2178   g_timer_start (priv->timer);
2179   priv->call_state = CONNECTED;
2180
2181   empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2182
2183   can_send_video = priv->video_input != NULL &&
2184     empathy_contact_can_voip_video (priv->contact) &&
2185     empathy_camera_monitor_get_available (priv->camera_monitor);
2186
2187   g_object_get (priv->handler, "call-channel", &call, NULL);
2188
2189   if (tpy_call_channel_has_dtmf (call))
2190     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2191
2192   if (priv->video_input == NULL)
2193     empathy_call_window_set_send_video (self, CAMERA_STATE_OFF);
2194
2195   gtk_widget_set_sensitive (priv->camera_button, can_send_video);
2196
2197   empathy_call_window_show_hangup_button (self, TRUE);
2198
2199   gtk_widget_set_sensitive (priv->mic_button, TRUE);
2200
2201   clutter_actor_hide (priv->video_output);
2202   gtk_widget_show (priv->remote_user_avatar_widget);
2203
2204   g_object_unref (call);
2205
2206   g_mutex_lock (priv->lock);
2207
2208   priv->timer_id = g_timeout_add_seconds (1,
2209     empathy_call_window_update_timer, self);
2210
2211   g_mutex_unlock (priv->lock);
2212
2213   empathy_call_window_update_timer (self);
2214
2215   gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
2216 }
2217
2218 static gboolean
2219 empathy_call_window_show_video_output_cb (gpointer user_data)
2220 {
2221   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2222
2223   if (self->priv->video_output != NULL)
2224     {
2225       gtk_widget_hide (self->priv->remote_user_avatar_widget);
2226       clutter_actor_show (self->priv->video_output);
2227     }
2228
2229   return FALSE;
2230 }
2231
2232 static gboolean
2233 empathy_call_window_check_video_cb (gpointer data)
2234 {
2235   EmpathyCallWindow *self = data;
2236
2237   if (self->priv->got_video)
2238     {
2239       self->priv->got_video = FALSE;
2240       return TRUE;
2241     }
2242
2243   /* No video in the last N seconds, display the remote avatar */
2244   empathy_call_window_show_video_output (self, FALSE);
2245
2246   return TRUE;
2247 }
2248
2249 /* Called from the streaming thread */
2250 static gboolean
2251 empathy_call_window_video_probe_cb (GstPad *pad,
2252     GstMiniObject *mini_obj,
2253     EmpathyCallWindow *self)
2254 {
2255   /* Ignore events */
2256   if (GST_IS_EVENT (mini_obj))
2257     return TRUE;
2258
2259   if (G_UNLIKELY (!self->priv->got_video))
2260     {
2261       /* show the remote video */
2262       g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
2263           empathy_call_window_show_video_output_cb,
2264           g_object_ref (self), g_object_unref);
2265
2266       self->priv->got_video = TRUE;
2267     }
2268
2269   return TRUE;
2270 }
2271
2272 /* Called from the streaming thread */
2273 static gboolean
2274 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
2275   GstPad *src, guint media_type, gpointer user_data)
2276 {
2277   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2278   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2279   gboolean retval = FALSE;
2280
2281   GstPad *pad;
2282
2283   g_mutex_lock (priv->lock);
2284
2285   switch (media_type)
2286     {
2287       case TP_MEDIA_STREAM_TYPE_AUDIO:
2288         pad = empathy_call_window_get_audio_sink_pad (self);
2289         break;
2290       case TP_MEDIA_STREAM_TYPE_VIDEO:
2291         g_idle_add (empathy_call_window_show_video_output_cb, self);
2292         pad = empathy_call_window_get_video_sink_pad (self);
2293
2294         gst_pad_add_data_probe (src,
2295             G_CALLBACK (empathy_call_window_video_probe_cb), self);
2296         if (priv->got_video_src > 0)
2297           g_source_remove (priv->got_video_src);
2298         priv->got_video_src = g_timeout_add_seconds (5,
2299             empathy_call_window_check_video_cb, self);
2300         break;
2301       default:
2302         g_assert_not_reached ();
2303     }
2304
2305   if (pad == NULL)
2306     goto out;
2307
2308   if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2309       g_warning ("Could not link %s sink pad",
2310           media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2311   else
2312       retval = TRUE;
2313
2314   gst_object_unref (pad);
2315
2316  out:
2317
2318   /* If no sink could be linked, try to add fakesink to prevent the whole call
2319    * aborting */
2320
2321   if (!retval)
2322     {
2323       GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2324
2325       if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2326         {
2327           GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2328           if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2329               GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2330             {
2331               gst_element_set_locked_state (fakesink, TRUE);
2332               gst_element_set_state (fakesink, GST_STATE_NULL);
2333               gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2334             }
2335           else
2336             {
2337               DEBUG ("Could not link real sink, linked fakesink instead");
2338             }
2339           gst_object_unref (sinkpad);
2340         }
2341       else
2342         {
2343           gst_object_unref (fakesink);
2344         }
2345     }
2346
2347
2348   g_mutex_unlock (priv->lock);
2349
2350   return TRUE;
2351 }
2352
2353 static gboolean
2354 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
2355   GstPad *sink, FsMediaType media_type, gpointer user_data)
2356 {
2357   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2358   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2359   GstPad *pad;
2360   gboolean retval = FALSE;
2361
2362   switch (media_type)
2363     {
2364       case FS_MEDIA_TYPE_AUDIO:
2365         if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2366           {
2367             g_warning ("Could not add audio source to pipeline");
2368             break;
2369           }
2370
2371         pad = gst_element_get_static_pad (priv->audio_input, "src");
2372         if (!pad)
2373           {
2374             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2375             g_warning ("Could not get source pad from audio source");
2376             break;
2377           }
2378
2379         if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2380           {
2381             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2382             g_warning ("Could not link audio source to farsight");
2383             break;
2384           }
2385
2386         if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2387           {
2388             g_warning ("Could not start audio source");
2389             gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2390             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2391             break;
2392           }
2393
2394         retval = TRUE;
2395         break;
2396       case FS_MEDIA_TYPE_VIDEO:
2397         if (priv->video_tee != NULL)
2398           {
2399             pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2400             if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2401               {
2402                 g_warning ("Could not link video source input pipeline");
2403                 break;
2404               }
2405             gst_object_unref (pad);
2406           }
2407
2408         retval = TRUE;
2409         break;
2410       default:
2411         g_assert_not_reached ();
2412     }
2413
2414   return retval;
2415 }
2416
2417 static void
2418 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
2419 {
2420   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2421   GstElement *preview;
2422
2423   disable_camera (self);
2424
2425   DEBUG ("remove video input");
2426   preview = priv->video_preview_sink;
2427
2428   gst_element_set_state (priv->video_input, GST_STATE_NULL);
2429   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2430   gst_element_set_state (preview, GST_STATE_NULL);
2431
2432   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2433     preview, NULL);
2434
2435   g_object_unref (priv->video_input);
2436   priv->video_input = NULL;
2437   g_object_unref (priv->video_tee);
2438   priv->video_tee = NULL;
2439   clutter_actor_destroy (priv->video_preview);
2440   priv->video_preview = NULL;
2441
2442   gtk_widget_set_sensitive (priv->camera_button, FALSE);
2443 }
2444
2445 static void
2446 start_call (EmpathyCallWindow *self)
2447 {
2448   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2449
2450   priv->call_started = TRUE;
2451   empathy_call_handler_start_call (priv->handler,
2452       gtk_get_current_event_time ());
2453
2454   if (empathy_call_handler_has_initial_video (priv->handler))
2455     {
2456       TpyCallChannel *call;
2457       TpySendingState s;
2458
2459       g_object_get (priv->handler, "call-channel", &call, NULL);
2460       s = tpy_call_channel_get_video_state (call);
2461
2462       if (s == TPY_SENDING_STATE_PENDING_SEND ||
2463           s == TPY_SENDING_STATE_SENDING)
2464         {
2465           /* Enable 'send video' buttons and display the preview */
2466           gtk_toggle_tool_button_set_active (
2467             GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), TRUE);
2468         }
2469       else
2470         {
2471           gtk_toggle_tool_button_set_active (
2472             GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
2473
2474           if (priv->video_preview == NULL)
2475             {
2476               create_video_preview (self);
2477               add_video_preview_to_pipeline (self);
2478             }
2479         }
2480
2481       g_object_unref (call);
2482     }
2483 }
2484
2485 static gboolean
2486 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
2487   gpointer user_data)
2488 {
2489   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
2490   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2491   GstState newstate;
2492
2493   empathy_call_handler_bus_message (priv->handler, bus, message);
2494
2495   switch (GST_MESSAGE_TYPE (message))
2496     {
2497       case GST_MESSAGE_STATE_CHANGED:
2498         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2499           {
2500             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2501           }
2502         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2503             !priv->call_started)
2504           {
2505             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2506             if (newstate == GST_STATE_PAUSED)
2507               {
2508                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2509                 priv->pipeline_playing = TRUE;
2510
2511                 if (priv->start_call_when_playing)
2512                   start_call (self);
2513               }
2514           }
2515         break;
2516       case GST_MESSAGE_ERROR:
2517         {
2518           GError *error = NULL;
2519           GstElement *gst_error;
2520           gchar *debug;
2521
2522           gst_message_parse_error (message, &error, &debug);
2523           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2524
2525           g_message ("Element error: %s -- %s\n", error->message, debug);
2526
2527           if (g_str_has_prefix (gst_element_get_name (gst_error),
2528                 VIDEO_INPUT_ERROR_PREFIX))
2529             {
2530               /* Remove the video input and continue */
2531               if (priv->video_input != NULL)
2532                 empathy_call_window_remove_video_input (self);
2533               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2534             }
2535           else
2536             {
2537               empathy_call_window_disconnected (self, TRUE);
2538             }
2539           g_error_free (error);
2540           g_free (debug);
2541         }
2542       case GST_MESSAGE_UNKNOWN:
2543       case GST_MESSAGE_EOS:
2544       case GST_MESSAGE_WARNING:
2545       case GST_MESSAGE_INFO:
2546       case GST_MESSAGE_TAG:
2547       case GST_MESSAGE_BUFFERING:
2548       case GST_MESSAGE_STATE_DIRTY:
2549       case GST_MESSAGE_STEP_DONE:
2550       case GST_MESSAGE_CLOCK_PROVIDE:
2551       case GST_MESSAGE_CLOCK_LOST:
2552       case GST_MESSAGE_NEW_CLOCK:
2553       case GST_MESSAGE_STRUCTURE_CHANGE:
2554       case GST_MESSAGE_STREAM_STATUS:
2555       case GST_MESSAGE_APPLICATION:
2556       case GST_MESSAGE_ELEMENT:
2557       case GST_MESSAGE_SEGMENT_START:
2558       case GST_MESSAGE_SEGMENT_DONE:
2559       case GST_MESSAGE_DURATION:
2560       case GST_MESSAGE_ANY:
2561       default:
2562         break;
2563     }
2564
2565   return TRUE;
2566 }
2567
2568 static void
2569 empathy_call_window_members_changed_cb (TpyCallChannel *call,
2570     GHashTable *members,
2571     EmpathyCallWindow *self)
2572 {
2573   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2574   GHashTableIter iter;
2575   gpointer key, value;
2576   gboolean held = FALSE;
2577
2578   g_hash_table_iter_init (&iter, members);
2579   while (g_hash_table_iter_next (&iter, &key, &value))
2580     {
2581       if (GPOINTER_TO_INT (value) & TPY_CALL_MEMBER_FLAG_HELD)
2582         {
2583           /* This assumes this is a 1-1 call, otherwise one participant
2584            * putting the call on hold wouldn't mean the call is on hold
2585            * for everyone. */
2586           held = TRUE;
2587           break;
2588         }
2589     }
2590
2591   if (held)
2592     priv->call_state = HELD;
2593   else if (priv->call_state == HELD)
2594     priv->call_state = CONNECTED;
2595 }
2596
2597 static void
2598 call_handler_notify_call_cb (EmpathyCallHandler *handler,
2599     GParamSpec *spec,
2600     EmpathyCallWindow *self)
2601 {
2602   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2603   TpyCallChannel *call;
2604
2605   g_object_get (priv->handler, "call-channel", &call, NULL);
2606   if (call == NULL)
2607     return;
2608
2609 /* FIXME
2610   tp_g_signal_connect_object (call, "audio-stream-error",
2611       G_CALLBACK (empathy_call_window_audio_stream_error), self, 0);
2612   tp_g_signal_connect_object (call, "video-stream-error",
2613       G_CALLBACK (empathy_call_window_video_stream_error), self, 0);
2614 */
2615
2616   tp_g_signal_connect_object (call, "members-changed",
2617       G_CALLBACK (empathy_call_window_members_changed_cb), self, 0);
2618
2619   g_object_unref (call);
2620 }
2621
2622 static void
2623 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
2624 {
2625   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2626   TpyCallChannel *call;
2627   gint width;
2628
2629   /* Make the hangup button twice as wide */
2630   width = gtk_widget_get_allocated_width (priv->hangup_button);
2631   gtk_widget_set_size_request (priv->hangup_button, width * 2, -1);
2632
2633   g_signal_connect (priv->handler, "state-changed",
2634     G_CALLBACK (empathy_call_window_state_changed_cb), window);
2635   g_signal_connect (priv->handler, "conference-added",
2636     G_CALLBACK (empathy_call_window_conference_added_cb), window);
2637   g_signal_connect (priv->handler, "conference-removed",
2638     G_CALLBACK (empathy_call_window_conference_removed_cb), window);
2639   g_signal_connect (priv->handler, "closed",
2640     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
2641   g_signal_connect (priv->handler, "src-pad-added",
2642     G_CALLBACK (empathy_call_window_src_added_cb), window);
2643   g_signal_connect (priv->handler, "sink-pad-added",
2644     G_CALLBACK (empathy_call_window_sink_added_cb), window);
2645   g_signal_connect (priv->handler, "sink-pad-removed",
2646     G_CALLBACK (empathy_call_window_sink_removed_cb), window);
2647
2648   g_object_get (priv->handler, "call-channel", &call, NULL);
2649   if (call != NULL)
2650     {
2651       call_handler_notify_call_cb (priv->handler, NULL, window);
2652       g_object_unref (call);
2653     }
2654   else
2655     {
2656       /* call-channel doesn't exist yet, we'll connect signals once it has been
2657        * set */
2658       g_signal_connect (priv->handler, "notify::call-channel",
2659         G_CALLBACK (call_handler_notify_call_cb), window);
2660     }
2661
2662   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2663 }
2664
2665 static gboolean
2666 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2667   EmpathyCallWindow *window)
2668 {
2669   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2670
2671   if (priv->pipeline != NULL)
2672     {
2673       if (priv->bus_message_source_id != 0)
2674         {
2675           g_source_remove (priv->bus_message_source_id);
2676           priv->bus_message_source_id = 0;
2677         }
2678
2679       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2680     }
2681
2682   if (priv->call_state == CONNECTING)
2683     empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2684
2685   return FALSE;
2686 }
2687
2688 static void
2689 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2690 {
2691   GtkWidget *menu;
2692   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2693
2694   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2695             "/menubar1");
2696
2697   if (set_fullscreen)
2698     {
2699       gtk_widget_hide (priv->dtmf_panel);
2700       gtk_widget_hide (menu);
2701       gtk_widget_hide (priv->toolbar);
2702     }
2703   else
2704     {
2705       if (priv->dialpad_was_visible_before_fs)
2706         gtk_widget_show (priv->dtmf_panel);
2707
2708       gtk_widget_show (menu);
2709       gtk_widget_show (priv->toolbar);
2710
2711       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2712           priv->original_height_before_fs);
2713     }
2714 }
2715
2716 static void
2717 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2718 {
2719   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2720
2721   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2722       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2723   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2724       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2725
2726   if (priv->video_output != NULL)
2727     {
2728 #if 0
2729       gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2730           priv->video_output, TRUE, TRUE,
2731           set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2732           GTK_PACK_START);
2733 #endif
2734     }
2735 }
2736
2737 static gboolean
2738 empathy_call_window_state_event_cb (GtkWidget *widget,
2739   GdkEventWindowState *event, EmpathyCallWindow *window)
2740 {
2741   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2742     {
2743       EmpathyCallWindowPriv *priv = GET_PRIV (window);
2744       gboolean set_fullscreen = event->new_window_state &
2745         GDK_WINDOW_STATE_FULLSCREEN;
2746
2747       if (set_fullscreen)
2748         {
2749           gboolean dialpad_was_visible;
2750           GtkAllocation allocation;
2751           gint original_width, original_height;
2752
2753           gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2754           original_width = allocation.width;
2755           original_height = allocation.height;
2756
2757           g_object_get (priv->dtmf_panel,
2758               "visible", &dialpad_was_visible,
2759               NULL);
2760
2761           priv->dialpad_was_visible_before_fs = dialpad_was_visible;
2762           priv->original_width_before_fs = original_width;
2763           priv->original_height_before_fs = original_height;
2764
2765           if (priv->video_output_motion_handler_id == 0 &&
2766                 priv->video_output != NULL)
2767             {
2768               priv->video_output_motion_handler_id = g_signal_connect (
2769                   G_OBJECT (priv->video_container), "motion-notify-event",
2770                   G_CALLBACK (empathy_call_window_video_output_motion_notify),
2771                   window);
2772             }
2773         }
2774       else
2775         {
2776           disconnect_video_output_motion_handler (window);
2777         }
2778
2779       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2780           set_fullscreen);
2781       show_controls (window, set_fullscreen);
2782       show_borders (window, set_fullscreen);
2783       gtk_action_set_stock_id (priv->menu_fullscreen,
2784           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2785       priv->is_fullscreen = set_fullscreen;
2786   }
2787
2788   return FALSE;
2789 }
2790
2791 static void
2792 empathy_call_window_show_dialpad (EmpathyCallWindow *window,
2793     gboolean active)
2794 {
2795   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2796   int w, h, dialpad_width;
2797   GtkAllocation allocation;
2798
2799   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2800   w = allocation.width;
2801   h = allocation.height;
2802
2803   gtk_widget_get_preferred_width (priv->dtmf_panel, &dialpad_width, NULL);
2804
2805   if (active)
2806     {
2807       gtk_widget_show (priv->dtmf_panel);
2808       w += dialpad_width;
2809     }
2810   else
2811     {
2812       w -= dialpad_width;
2813       gtk_widget_hide (priv->dtmf_panel);
2814     }
2815
2816   if (w > 0 && h > 0)
2817     gtk_window_resize (GTK_WINDOW (window), w, h);
2818 }
2819
2820 static void
2821 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2822   CameraState state)
2823 {
2824   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2825   TpyCallChannel *call;
2826
2827   priv->sending_video = (state == CAMERA_STATE_ON);
2828
2829   if (state == CAMERA_STATE_ON)
2830     {
2831       /* When we start sending video, we want to show the video preview by
2832          default. */
2833       display_video_preview (window, TRUE);
2834     }
2835   else
2836     {
2837       display_video_preview (window, FALSE);
2838     }
2839
2840   if (priv->call_state != CONNECTED)
2841     return;
2842
2843   g_object_get (priv->handler, "call-channel", &call, NULL);
2844   DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
2845   tpy_call_channel_send_video (call, priv->sending_video);
2846   g_object_unref (call);
2847 }
2848
2849 static void
2850 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2851   EmpathyCallWindow *self)
2852 {
2853   EmpathyCallWindowPriv *priv = GET_PRIV (self);
2854   gboolean active;
2855
2856   active = (gtk_toggle_tool_button_get_active (toggle));
2857
2858   /* We don't want the settings callback to react to this change to avoid
2859    * a loop. */
2860   g_signal_handlers_block_by_func (priv->settings,
2861       empathy_call_window_prefs_volume_changed_cb, self);
2862
2863   if (active)
2864     {
2865       g_settings_set_double (priv->settings, EMPATHY_PREFS_CALL_SOUND_VOLUME,
2866           priv->volume * 100);
2867     }
2868   else
2869     {
2870       /* TODO, Instead of setting the input volume to 0 we should probably
2871        * stop sending but this would cause the audio call to drop if both
2872        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2873        * in the future. GNOME #574574
2874        */
2875       g_settings_set_double (priv->settings, EMPATHY_PREFS_CALL_SOUND_VOLUME,
2876           0);
2877     }
2878
2879     g_signal_handlers_unblock_by_func (priv->settings,
2880       empathy_call_window_prefs_volume_changed_cb, self);
2881 }
2882
2883 static void
2884 empathy_call_window_hangup_cb (gpointer object,
2885     EmpathyCallWindow *self)
2886 {
2887   empathy_call_handler_stop_call (self->priv->handler);
2888
2889   empathy_call_window_disconnected (self, TRUE);
2890 }
2891
2892 static void
2893 empathy_call_window_restart_call (EmpathyCallWindow *window)
2894 {
2895   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2896
2897   /* Remove error info bars */
2898   gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
2899       (GtkCallback) gtk_widget_destroy, NULL);
2900
2901   create_video_output_widget (window);
2902
2903   /* While the call was disconnected, the input volume might have changed.
2904    * However, since the audio_input source was destroyed, its volume has not
2905    * been updated during that time. That's why we manually update it here */
2906   empathy_call_window_mic_volume_changed (window);
2907
2908   priv->outgoing = TRUE;
2909   empathy_call_window_set_state_connecting (window);
2910
2911   if (priv->pipeline_playing)
2912     start_call (window);
2913   else
2914     /* call will be started when the pipeline is ready */
2915     priv->start_call_when_playing = TRUE;
2916
2917   empathy_call_window_setup_avatars (window, priv->handler);
2918
2919   empathy_call_window_show_hangup_button (window, TRUE);
2920 }
2921
2922 static void
2923 empathy_call_window_dialpad_cb (GtkToggleToolButton *button,
2924     EmpathyCallWindow *window)
2925 {
2926   gboolean active;
2927
2928   active = gtk_toggle_tool_button_get_active (button);
2929
2930   empathy_call_window_show_dialpad (window, active);
2931 }
2932
2933 static void
2934 empathy_call_window_fullscreen_cb (gpointer object,
2935                                    EmpathyCallWindow *window)
2936 {
2937   empathy_call_window_fullscreen_toggle (window);
2938 }
2939
2940 static void
2941 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2942 {
2943   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2944
2945   if (priv->is_fullscreen)
2946     gtk_window_unfullscreen (GTK_WINDOW (window));
2947   else
2948     gtk_window_fullscreen (GTK_WINDOW (window));
2949 }
2950
2951 static gboolean
2952 empathy_call_window_video_button_press_cb (GtkWidget *video_preview,
2953   GdkEventButton *event, EmpathyCallWindow *window)
2954 {
2955   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2956     {
2957       empathy_call_window_video_menu_popup (window, event->button);
2958       return TRUE;
2959     }
2960
2961   return FALSE;
2962 }
2963
2964 static gboolean
2965 empathy_call_window_key_press_cb (GtkWidget *video_output,
2966   GdkEventKey *event, EmpathyCallWindow *window)
2967 {
2968   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2969
2970   if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
2971     {
2972       /* Since we are in fullscreen mode, toggling will bring us back to
2973          normal mode. */
2974       empathy_call_window_fullscreen_toggle (window);
2975       return TRUE;
2976     }
2977
2978   return FALSE;
2979 }
2980
2981 static gboolean
2982 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2983     GdkEventMotion *event, EmpathyCallWindow *window)
2984 {
2985   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2986
2987   if (priv->is_fullscreen)
2988     {
2989       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2990       return TRUE;
2991     }
2992   return FALSE;
2993 }
2994
2995 static void
2996 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2997   guint button)
2998 {
2999   GtkWidget *menu;
3000   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3001
3002   menu = gtk_ui_manager_get_widget (priv->ui_manager,
3003             "/video-popup");
3004   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
3005       button, gtk_get_current_event_time ());
3006   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
3007 }
3008
3009 static void
3010 empathy_call_window_status_message (EmpathyCallWindow *self,
3011   gchar *message)
3012 {
3013   gtk_label_set_label (GTK_LABEL (self->priv->status_label), message);
3014 }
3015
3016 static void
3017 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
3018   gdouble value, EmpathyCallWindow *window)
3019 {
3020   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3021
3022   if (priv->audio_output == NULL)
3023     return;
3024
3025   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
3026     value);
3027 }
3028
3029 GtkUIManager *
3030 empathy_call_window_get_ui_manager (EmpathyCallWindow *window)
3031 {
3032   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3033
3034   return priv->ui_manager;
3035 }
3036
3037 EmpathyGstAudioSrc *
3038 empathy_call_window_get_audio_src (EmpathyCallWindow *window)
3039 {
3040   EmpathyCallWindowPriv *priv = GET_PRIV (window);
3041
3042   return (EmpathyGstAudioSrc *) priv->audio_input;
3043 }