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