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