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