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