]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
set the icon of the camera_off button
[empathy.git] / src / empathy-call-window.c
1 /*
2  * empathy-call-window.c - Source for EmpathyCallWindow
3  * Copyright (C) 2008-2009 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 <telepathy-farsight/channel.h>
33
34 #include <gst/farsight/fs-element-added-notifier.h>
35
36 #include <libempathy/empathy-tp-contact-factory.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-utils.h>
39 #include <libempathy-gtk/empathy-avatar-image.h>
40 #include <libempathy-gtk/empathy-video-widget.h>
41 #include <libempathy-gtk/empathy-audio-src.h>
42 #include <libempathy-gtk/empathy-audio-sink.h>
43 #include <libempathy-gtk/empathy-video-src.h>
44 #include <libempathy-gtk/empathy-ui-utils.h>
45 #include <libempathy-gtk/empathy-sound.h>
46
47 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
48 #include <libempathy/empathy-debug.h>
49
50 #include "empathy-call-window.h"
51 #include "empathy-call-window-fullscreen.h"
52 #include "empathy-sidebar.h"
53
54 #define BUTTON_ID "empathy-call-dtmf-button-id"
55
56 #define CONTENT_HBOX_BORDER_WIDTH 6
57 #define CONTENT_HBOX_SPACING 3
58 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
59
60 #define SELF_VIDEO_SECTION_WIDTH 160
61 #define SELF_VIDEO_SECTION_HEIGTH 120
62
63 /* The avatar's default width and height are set to the same value because we
64    want a square icon. */
65 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
66 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
67   EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
68
69 /* If an video input error occurs, the error message will start with "v4l" */
70 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
71
72 /* The time interval in milliseconds between 2 outgoing rings */
73 #define MS_BETWEEN_RING 500
74
75 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
76
77 /* signal enum */
78 #if 0
79 enum
80 {
81     LAST_SIGNAL
82 };
83
84 static guint signals[LAST_SIGNAL] = {0};
85 #endif
86
87 enum {
88   PROP_CALL_HANDLER = 1,
89 };
90
91 typedef enum {
92   CONNECTING,
93   CONNECTED,
94   DISCONNECTED,
95   REDIALING
96 } CallState;
97
98 /* private structure */
99 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
100
101 struct _EmpathyCallWindowPriv
102 {
103   gboolean dispose_has_run;
104   EmpathyCallHandler *handler;
105   EmpathyContact *contact;
106
107   guint call_state;
108   gboolean outgoing;
109
110   GtkUIManager *ui_manager;
111   GtkWidget *errors_vbox;
112   GtkWidget *video_output;
113   GtkWidget *video_preview;
114   GtkWidget *remote_user_avatar_widget;
115   GtkWidget *self_user_avatar_widget;
116   GtkWidget *sidebar;
117   GtkWidget *sidebar_button;
118   GtkWidget *statusbar;
119   GtkWidget *volume_button;
120   GtkWidget *redial_button;
121   GtkWidget *mic_button;
122   GtkWidget *camera_button;
123   GtkWidget *toolbar;
124   GtkWidget *pane;
125   GtkAction *always_show_preview;
126   GtkAction *send_video;
127   GtkAction *redial;
128   GtkAction *menu_fullscreen;
129   GtkWidget *tool_button_camera_off;
130
131   /* The frames and boxes that contain self and remote avatar and video
132      input/output. When we redial, we destroy and re-create the boxes */
133   GtkWidget *remote_user_output_frame;
134   GtkWidget *self_user_output_frame;
135   GtkWidget *remote_user_output_hbox;
136   GtkWidget *self_user_output_hbox;
137
138   /* We keep a reference on the hbox which contains the main content so we can
139      easilly repack everything when toggling fullscreen */
140   GtkWidget *content_hbox;
141
142   /* This vbox is contained in the content_hbox and it contains the
143      self_user_output_frame and the sidebar button. When toggling fullscreen,
144      it needs to be repacked. We keep a reference on it for easier access. */
145   GtkWidget *vbox;
146
147   gulong video_output_motion_handler_id;
148   guint bus_message_source_id;
149
150   gdouble volume;
151   GtkWidget *volume_scale;
152   GtkWidget *volume_progress_bar;
153   GtkAdjustment *audio_input_adj;
154
155   GtkWidget *dtmf_panel;
156
157   GstElement *video_input;
158   GstElement *audio_input;
159   GstElement *audio_output;
160   GstElement *pipeline;
161   GstElement *video_tee;
162
163   GstElement *funnel;
164   GstElement *liveadder;
165
166   FsElementAddedNotifier *fsnotifier;
167
168   guint context_id;
169
170   GTimer *timer;
171   guint timer_id;
172
173   GtkWidget *video_contrast;
174   GtkWidget *video_brightness;
175   GtkWidget *video_gamma;
176
177   GMutex *lock;
178   gboolean call_started;
179   gboolean sending_video;
180
181   EmpathyCallWindowFullscreen *fullscreen;
182   gboolean is_fullscreen;
183
184   /* Those fields represent the state of the window before it actually was in
185      fullscreen mode. */
186   gboolean sidebar_was_visible_before_fs;
187   gint original_width_before_fs;
188   gint original_height_before_fs;
189 };
190
191 #define GET_PRIV(o) \
192   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW, \
193     EmpathyCallWindowPriv))
194
195 static void empathy_call_window_realized_cb (GtkWidget *widget,
196   EmpathyCallWindow *window);
197
198 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
199   GdkEvent *event, EmpathyCallWindow *window);
200
201 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
202   GdkEventWindowState *event, EmpathyCallWindow *window);
203
204 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
205   EmpathyCallWindow *window);
206
207 static void empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
208   EmpathyCallWindow *window);
209
210 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
211   gboolean send);
212
213 static void empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
214   EmpathyCallWindow *window);
215
216 static void empathy_call_window_always_show_preview_toggled_cb (
217   GtkToggleAction *toggle, EmpathyCallWindow *window);
218
219 static void empathy_call_window_mic_toggled_cb (
220   GtkToggleToolButton *toggle, EmpathyCallWindow *window);
221
222 static void empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
223   EmpathyCallWindow *window);
224
225 static void empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
226   EmpathyCallWindow *window);
227
228 static void empathy_call_window_hangup_cb (gpointer object,
229   EmpathyCallWindow *window);
230
231 static void empathy_call_window_fullscreen_cb (gpointer object,
232   EmpathyCallWindow *window);
233
234 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
235
236 static gboolean empathy_call_window_video_button_press_cb (
237   GtkWidget *video_output, GdkEventButton *event, EmpathyCallWindow *window);
238
239 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
240   GdkEventKey *event, EmpathyCallWindow *window);
241
242 static gboolean empathy_call_window_video_output_motion_notify (
243   GtkWidget *widget, GdkEventMotion *event, EmpathyCallWindow *window);
244
245 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
246   guint button);
247
248 static void empathy_call_window_redial_cb (gpointer object,
249   EmpathyCallWindow *window);
250
251 static void empathy_call_window_restart_call (EmpathyCallWindow *window);
252
253 static void empathy_call_window_status_message (EmpathyCallWindow *window,
254   gchar *message);
255
256 static void empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
257   EmpathyCallWindow *window);
258
259 static gboolean empathy_call_window_bus_message (GstBus *bus,
260   GstMessage *message, gpointer user_data);
261
262 static void
263 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
264   gdouble value, EmpathyCallWindow *window);
265
266 static void
267 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
268 {
269   EmpathyCallWindowPriv *priv = GET_PRIV (self);
270   GtkToolItem *tool_item;
271   GtkWidget *camera_off_icon;
272   GdkPixbuf *pixbuf, *modded_pixbuf;
273
274   /* set the icon of the 'camera off' button by greying off the webcam icon */
275   pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
276       GTK_ICON_SIZE_SMALL_TOOLBAR);
277
278   modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
279       gdk_pixbuf_get_width (pixbuf),
280       gdk_pixbuf_get_height (pixbuf));
281
282   gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
283   g_object_unref (pixbuf);
284
285   camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
286   g_object_unref (modded_pixbuf);
287   gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
288         priv->tool_button_camera_off), camera_off_icon);
289
290   /* Add an empty expanded GtkToolItem so the volume button is at the end of
291    * the toolbar. */
292   tool_item = gtk_tool_item_new ();
293   gtk_tool_item_set_expand (tool_item, TRUE);
294   gtk_widget_show (GTK_WIDGET (tool_item));
295   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
296
297   priv->volume_button = gtk_volume_button_new ();
298   /* FIXME listen to the audiosinks signals and update the button according to
299    * that, for now starting out at 1.0 and assuming only the app changes the
300    * volume will do */
301   gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
302   g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
303     G_CALLBACK (empathy_call_window_volume_changed_cb), self);
304
305   tool_item = gtk_tool_item_new ();
306   gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
307   gtk_widget_show_all (GTK_WIDGET (tool_item));
308   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
309 }
310
311 static void
312 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
313 {
314   EmpathyCallWindowPriv *priv = GET_PRIV (window);
315   EmpathyTpCall *call;
316   GQuark button_quark;
317   TpDTMFEvent event;
318
319   g_object_get (priv->handler, "tp-call", &call, NULL);
320
321   button_quark = g_quark_from_static_string (BUTTON_ID);
322   event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
323     button_quark));
324
325   empathy_tp_call_start_tone (call, event);
326
327   g_object_unref (call);
328 }
329
330 static void
331 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
332 {
333   EmpathyCallWindowPriv *priv = GET_PRIV (window);
334   EmpathyTpCall *call;
335
336   g_object_get (priv->handler, "tp-call", &call, NULL);
337
338   empathy_tp_call_stop_tone (call);
339
340   g_object_unref (call);
341 }
342
343 static GtkWidget *
344 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
345 {
346   GtkWidget *table;
347   int i;
348   GQuark button_quark;
349   struct {
350     gchar *label;
351     TpDTMFEvent event;
352   } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
353                       { "2", TP_DTMF_EVENT_DIGIT_2 },
354                       { "3", TP_DTMF_EVENT_DIGIT_3 },
355                       { "4", TP_DTMF_EVENT_DIGIT_4 },
356                       { "5", TP_DTMF_EVENT_DIGIT_5 },
357                       { "6", TP_DTMF_EVENT_DIGIT_6 },
358                       { "7", TP_DTMF_EVENT_DIGIT_7 },
359                       { "8", TP_DTMF_EVENT_DIGIT_8 },
360                       { "9", TP_DTMF_EVENT_DIGIT_9 },
361                       { "#", TP_DTMF_EVENT_HASH },
362                       { "0", TP_DTMF_EVENT_DIGIT_0 },
363                       { "*", TP_DTMF_EVENT_ASTERISK },
364                       { NULL, } };
365
366   button_quark = g_quark_from_static_string (BUTTON_ID);
367
368   table = gtk_table_new (4, 3, TRUE);
369
370   for (i = 0; dtmfbuttons[i].label != NULL; i++)
371     {
372       GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
373       gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
374         i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
375
376       g_object_set_qdata (G_OBJECT (button), button_quark,
377         GUINT_TO_POINTER (dtmfbuttons[i].event));
378
379       g_signal_connect (G_OBJECT (button), "pressed",
380         G_CALLBACK (dtmf_button_pressed_cb), self);
381       g_signal_connect (G_OBJECT (button), "released",
382         G_CALLBACK (dtmf_button_released_cb), self);
383     }
384
385   return table;
386 }
387
388 static GtkWidget *
389 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
390   gchar *label_text, GtkWidget *bin)
391 {
392    GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
393    GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
394    GtkWidget *label = gtk_label_new (label_text);
395
396    gtk_widget_set_sensitive (scale, FALSE);
397
398    gtk_container_add (GTK_CONTAINER (bin), vbox);
399
400    gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
401    gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
402    gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
403
404    return scale;
405 }
406
407 static void
408 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
409   EmpathyCallWindow *self)
410
411 {
412   EmpathyCallWindowPriv *priv = GET_PRIV (self);
413
414   empathy_video_src_set_channel (priv->video_input,
415     EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
416 }
417
418 static void
419 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
420   EmpathyCallWindow *self)
421
422 {
423   EmpathyCallWindowPriv *priv = GET_PRIV (self);
424
425   empathy_video_src_set_channel (priv->video_input,
426     EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
427 }
428
429 static void
430 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
431   EmpathyCallWindow *self)
432
433 {
434   EmpathyCallWindowPriv *priv = GET_PRIV (self);
435
436   empathy_video_src_set_channel (priv->video_input,
437     EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
438 }
439
440
441 static GtkWidget *
442 empathy_call_window_create_video_input (EmpathyCallWindow *self)
443 {
444   EmpathyCallWindowPriv *priv = GET_PRIV (self);
445   GtkWidget *hbox;
446
447   hbox = gtk_hbox_new (TRUE, 3);
448
449   priv->video_contrast = empathy_call_window_create_video_input_add_slider (
450     self,  _("Contrast"), hbox);
451
452   priv->video_brightness = empathy_call_window_create_video_input_add_slider (
453     self,  _("Brightness"), hbox);
454
455   priv->video_gamma = empathy_call_window_create_video_input_add_slider (
456     self,  _("Gamma"), hbox);
457
458   return hbox;
459 }
460
461 static void
462 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
463 {
464   EmpathyCallWindowPriv *priv = GET_PRIV (self);
465   guint supported;
466   GtkAdjustment *adj;
467
468   supported = empathy_video_src_get_supported_channels (priv->video_input);
469
470   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
471     {
472       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
473
474       gtk_adjustment_set_value (adj,
475         empathy_video_src_get_channel (priv->video_input,
476           EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
477
478       g_signal_connect (G_OBJECT (adj), "value-changed",
479         G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
480
481       gtk_widget_set_sensitive (priv->video_contrast, TRUE);
482     }
483
484   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
485     {
486       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
487
488       gtk_adjustment_set_value (adj,
489         empathy_video_src_get_channel (priv->video_input,
490           EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
491
492       g_signal_connect (G_OBJECT (adj), "value-changed",
493         G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
494       gtk_widget_set_sensitive (priv->video_brightness, TRUE);
495     }
496
497   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
498     {
499       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
500
501       gtk_adjustment_set_value (adj,
502         empathy_video_src_get_channel (priv->video_input,
503           EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
504
505       g_signal_connect (G_OBJECT (adj), "value-changed",
506         G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
507       gtk_widget_set_sensitive (priv->video_gamma, TRUE);
508     }
509 }
510
511 static void
512 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
513   EmpathyCallWindow *self)
514 {
515   EmpathyCallWindowPriv *priv = GET_PRIV (self);
516   gdouble volume;
517
518   if (priv->audio_input == NULL)
519     return;
520
521   volume = gtk_adjustment_get_value (adj)/100.0;
522
523   /* Don't store the volume because of muting */
524   if (volume > 0 || gtk_toggle_tool_button_get_active (
525         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
526     priv->volume = volume;
527
528   /* Ensure that the toggle button is active if the volume is > 0 and inactive
529    * if it's smaller than 0 */
530   if ((volume > 0) != gtk_toggle_tool_button_get_active (
531         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
532     gtk_toggle_tool_button_set_active (
533       GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
534
535   empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
536     volume);
537 }
538
539 static void
540 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
541   gdouble level, EmpathyCallWindow *window)
542 {
543   gdouble value;
544   EmpathyCallWindowPriv *priv = GET_PRIV (window);
545
546   value = CLAMP (pow (10, level / 20), 0.0, 1.0);
547   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
548       value);
549 }
550
551 static GtkWidget *
552 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
553 {
554   EmpathyCallWindowPriv *priv = GET_PRIV (self);
555   GtkWidget *hbox, *vbox, *label;
556
557   hbox = gtk_hbox_new (TRUE, 3);
558
559   vbox = gtk_vbox_new (FALSE, 3);
560   gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
561
562   priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
563   gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
564   label = gtk_label_new (_("Volume"));
565
566   priv->audio_input_adj = gtk_range_get_adjustment (
567     GTK_RANGE (priv->volume_scale));
568   priv->volume =  empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
569     (priv->audio_input));
570   gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
571
572   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
573     G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
574
575   gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
576   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
577
578   priv->volume_progress_bar = gtk_progress_bar_new ();
579   gtk_progress_bar_set_orientation (
580       GTK_PROGRESS_BAR (priv->volume_progress_bar),
581       GTK_PROGRESS_BOTTOM_TO_TOP);
582   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
583       0);
584
585   gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
586       3);
587
588   return hbox;
589 }
590
591 static void
592 empathy_call_window_setup_remote_frame (GstBus *bus, EmpathyCallWindow *self)
593 {
594   EmpathyCallWindowPriv *priv = GET_PRIV (self);
595
596   /* Initializing all the content (UI and output gst elements) related to the
597      remote contact */
598   priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
599
600   priv->remote_user_avatar_widget = gtk_image_new ();
601   gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
602       priv->remote_user_avatar_widget, TRUE, TRUE, 0);
603
604   priv->video_output = empathy_video_widget_new (bus);
605   gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
606       priv->video_output, TRUE, TRUE, 0);
607
608   gtk_widget_add_events (priv->video_output,
609       GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
610   g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
611       G_CALLBACK (empathy_call_window_video_button_press_cb), self);
612
613   gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
614       priv->remote_user_output_hbox);
615
616   priv->audio_output = empathy_audio_sink_new ();
617   gst_object_ref (priv->audio_output);
618   gst_object_sink (priv->audio_output);
619 }
620
621 static void
622 empathy_call_window_setup_self_frame (GstBus *bus, EmpathyCallWindow *self)
623 {
624   EmpathyCallWindowPriv *priv = GET_PRIV (self);
625
626   /* Initializing all the content (UI and input gst elements) related to the
627      self contact, except for the video preview widget. This widget is only
628      initialized when the "show video preview" option is activated */
629   priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
630
631   priv->self_user_avatar_widget = gtk_image_new ();
632   gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
633       priv->self_user_avatar_widget, TRUE, TRUE, 0);
634
635   gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
636       priv->self_user_output_hbox);
637
638   priv->video_input = empathy_video_src_new ();
639   gst_object_ref (priv->video_input);
640   gst_object_sink (priv->video_input);
641
642   priv->audio_input = empathy_audio_src_new ();
643   gst_object_ref (priv->audio_input);
644   gst_object_sink (priv->audio_input);
645
646   empathy_signal_connect_weak (priv->audio_input, "peak-level-changed",
647     G_CALLBACK (empathy_call_window_audio_input_level_changed_cb),
648     G_OBJECT (self));
649 }
650
651 static void
652 empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
653 {
654   EmpathyCallWindowPriv *priv = GET_PRIV (window);
655   GstElement *preview;
656   GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
657
658   if (priv->video_preview != NULL)
659     {
660       /* Since the video preview and the video tee are initialized and freed
661          at the same time, if one is initialized, then the other one should
662          be too. */
663       g_assert (priv->video_tee != NULL);
664       return;
665     }
666
667   DEBUG ("Create video preview");
668   g_assert (priv->video_tee == NULL);
669
670   priv->video_tee = gst_element_factory_make ("tee", NULL);
671   gst_object_ref (priv->video_tee);
672   gst_object_sink (priv->video_tee);
673
674   priv->video_preview = empathy_video_widget_new_with_size (bus,
675       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
676   g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
677   gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
678       priv->video_preview, TRUE, TRUE, 0);
679
680   preview = empathy_video_widget_get_element (
681       EMPATHY_VIDEO_WIDGET (priv->video_preview));
682   gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
683       priv->video_tee, preview, NULL);
684   gst_element_link_many (priv->video_input, priv->video_tee,
685       preview, NULL);
686
687   g_object_unref (bus);
688
689   gst_element_set_state (preview, GST_STATE_PLAYING);
690   gst_element_set_state (priv->video_input, GST_STATE_PLAYING);
691   gst_element_set_state (priv->video_tee, GST_STATE_PLAYING);
692 }
693
694 static void
695 display_video_preview (EmpathyCallWindow *self,
696     gboolean display)
697 {
698   EmpathyCallWindowPriv *priv = GET_PRIV (self);
699
700   if (display)
701     {
702       /* Display the preview and hide the self avatar */
703       DEBUG ("Show video preview");
704
705       if (priv->video_preview == NULL)
706         empathy_call_window_setup_video_preview (self);
707       gtk_widget_show (priv->video_preview);
708       gtk_widget_hide (priv->self_user_avatar_widget);
709     }
710   else
711     {
712       /* Display the self avatar and hide the preview */
713       DEBUG ("Show self avatar");
714
715       if (priv->video_preview != NULL)
716         gtk_widget_hide (priv->video_preview);
717       gtk_widget_show (priv->self_user_avatar_widget);
718     }
719 }
720
721 static void
722 empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
723 {
724   EmpathyCallWindowPriv *priv = GET_PRIV (window);
725
726   empathy_call_window_status_message (window, _("Connecting..."));
727   priv->call_state = CONNECTING;
728
729   if (priv->outgoing)
730     empathy_sound_start_playing (GTK_WIDGET (window),
731         EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
732 }
733
734 static void
735 empathy_call_window_init (EmpathyCallWindow *self)
736 {
737   EmpathyCallWindowPriv *priv = GET_PRIV (self);
738   GtkBuilder *gui;
739   GtkWidget *top_vbox;
740   GtkWidget *h;
741   GtkWidget *arrow;
742   GtkWidget *page;
743   GstBus *bus;
744   gchar *filename;
745   GKeyFile *keyfile;
746   GError *error = NULL;
747
748   filename = empathy_file_lookup ("empathy-call-window.ui", "src");
749   gui = empathy_builder_get_file (filename,
750     "call_window_vbox", &top_vbox,
751     "errors_vbox", &priv->errors_vbox,
752     "pane", &priv->pane,
753     "statusbar", &priv->statusbar,
754     "redial", &priv->redial_button,
755     "microphone", &priv->mic_button,
756     "camera", &priv->camera_button,
757     "toolbar", &priv->toolbar,
758     "send_video", &priv->send_video,
759     "menuredial", &priv->redial,
760     "always_show_preview", &priv->always_show_preview,
761     "ui_manager", &priv->ui_manager,
762     "menufullscreen", &priv->menu_fullscreen,
763     "camera_off", &priv->tool_button_camera_off,
764     NULL);
765   g_free (filename);
766
767   empathy_builder_connect (gui, self,
768     "menuhangup", "activate", empathy_call_window_hangup_cb,
769     "hangup", "clicked", empathy_call_window_hangup_cb,
770     "menuredial", "activate", empathy_call_window_redial_cb,
771     "redial", "clicked", empathy_call_window_redial_cb,
772     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
773     "camera", "toggled", empathy_call_window_camera_toggled_cb,
774     "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
775     "always_show_preview", "toggled",
776         empathy_call_window_always_show_preview_toggled_cb,
777     "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
778     NULL);
779
780   priv->lock = g_mutex_new ();
781
782   gtk_container_add (GTK_CONTAINER (self), top_vbox);
783
784   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
785   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
786                                   CONTENT_HBOX_BORDER_WIDTH);
787   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
788
789   priv->pipeline = gst_pipeline_new (NULL);
790   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
791   priv->bus_message_source_id = gst_bus_add_watch (bus,
792       empathy_call_window_bus_message, self);
793
794   priv->fsnotifier = fs_element_added_notifier_new ();
795   fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
796
797   keyfile = g_key_file_new ();
798   filename = empathy_file_lookup ("element-properties", "data");
799   if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
800     {
801       fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
802           keyfile);
803     }
804   else
805     {
806       g_warning ("Could not load element-properties file: %s", error->message);
807       g_key_file_free (keyfile);
808       g_clear_error (&error);
809     }
810   g_free (filename);
811
812
813   priv->remote_user_output_frame = gtk_frame_new (NULL);
814   gtk_widget_set_size_request (priv->remote_user_output_frame,
815       EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
816   gtk_box_pack_start (GTK_BOX (priv->content_hbox),
817       priv->remote_user_output_frame, TRUE, TRUE,
818       CONTENT_HBOX_CHILDREN_PACKING_PADDING);
819   empathy_call_window_setup_remote_frame (bus, self);
820
821   priv->self_user_output_frame = gtk_frame_new (NULL);
822   gtk_widget_set_size_request (priv->self_user_output_frame,
823       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
824
825   priv->vbox = gtk_vbox_new (FALSE, 3);
826   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
827       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
828   gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
829       FALSE, FALSE, 0);
830   empathy_call_window_setup_self_frame (bus, self);
831
832   empathy_call_window_setup_toolbar (self);
833
834   g_object_unref (bus);
835
836   priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
837   arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
838   g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
839     G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
840
841   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
842
843   h = gtk_hbox_new (FALSE, 3);
844   gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
845   gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
846
847   priv->sidebar = empathy_sidebar_new ();
848   g_signal_connect (G_OBJECT (priv->sidebar),
849     "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
850   g_signal_connect (G_OBJECT (priv->sidebar),
851     "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
852   gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
853
854   priv->dtmf_panel = empathy_call_window_create_dtmf (self);
855   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
856     priv->dtmf_panel);
857
858   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
859
860   page = empathy_call_window_create_audio_input (self);
861   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
862     page);
863
864   page = empathy_call_window_create_video_input (self);
865   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
866     page);
867
868   gtk_widget_show_all (top_vbox);
869
870   gtk_widget_hide (priv->sidebar);
871
872   priv->fullscreen = empathy_call_window_fullscreen_new (self);
873   empathy_call_window_fullscreen_set_video_widget (priv->fullscreen,
874       priv->video_output);
875   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
876       "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
877
878   g_signal_connect (G_OBJECT (self), "realize",
879     G_CALLBACK (empathy_call_window_realized_cb), self);
880
881   g_signal_connect (G_OBJECT (self), "delete-event",
882     G_CALLBACK (empathy_call_window_delete_cb), self);
883
884   g_signal_connect (G_OBJECT (self), "window-state-event",
885     G_CALLBACK (empathy_call_window_state_event_cb), self);
886
887   g_signal_connect (G_OBJECT (self), "key-press-event",
888       G_CALLBACK (empathy_call_window_key_press_cb), self);
889
890   priv->timer = g_timer_new ();
891
892   g_object_ref (priv->ui_manager);
893   g_object_unref (gui);
894 }
895
896 /* Instead of specifying a width and a height, we specify only one size. That's
897    because we want a square avatar icon.  */
898 static void
899 init_contact_avatar_with_size (EmpathyContact *contact,
900     GtkWidget *image_widget,
901     gint size)
902 {
903   GdkPixbuf *pixbuf_avatar = NULL;
904
905   if (contact != NULL)
906     {
907       pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
908         size, size);
909     }
910
911   if (pixbuf_avatar == NULL)
912     {
913       pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
914           size);
915     }
916
917   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
918 }
919
920 static void
921 set_window_title (EmpathyCallWindow *self)
922 {
923   EmpathyCallWindowPriv *priv = GET_PRIV (self);
924   gchar *tmp;
925
926   /* translators: Call is a noun and %s is the contact name. This string
927    * is used in the window title */
928   tmp = g_strdup_printf (_("Call with %s"),
929       empathy_contact_get_name (priv->contact));
930   gtk_window_set_title (GTK_WINDOW (self), tmp);
931   g_free (tmp);
932 }
933
934 static void
935 contact_name_changed_cb (EmpathyContact *contact,
936     GParamSpec *pspec, EmpathyCallWindow *self)
937 {
938   set_window_title (self);
939 }
940
941 static void
942 contact_avatar_changed_cb (EmpathyContact *contact,
943     GParamSpec *pspec, GtkWidget *avatar_widget)
944 {
945   int size;
946
947   size = avatar_widget->allocation.height;
948
949   if (size == 0)
950     {
951       /* the widget is not allocated yet, set a default size */
952       size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
953           REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
954     }
955
956   init_contact_avatar_with_size (contact, avatar_widget, size);
957 }
958
959 static void
960 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
961     EmpathyContact *contact, const GError *error, gpointer user_data,
962     GObject *weak_object)
963 {
964   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
965   EmpathyCallWindowPriv *priv = GET_PRIV (self);
966
967   init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
968       MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
969
970   g_signal_connect (contact, "notify::avatar",
971       G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
972 }
973
974 static void
975 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
976     EmpathyCallHandler *handler)
977 {
978   EmpathyCallWindowPriv *priv = GET_PRIV (self);
979
980   g_object_get (handler, "contact", &(priv->contact), NULL);
981
982   if (priv->contact != NULL)
983     {
984       TpConnection *connection;
985       EmpathyTpContactFactory *factory;
986
987       set_window_title (self);
988
989       g_signal_connect (priv->contact, "notify::name",
990           G_CALLBACK (contact_name_changed_cb), self);
991       g_signal_connect (priv->contact, "notify::avatar",
992           G_CALLBACK (contact_avatar_changed_cb),
993           priv->remote_user_avatar_widget);
994
995       /* Retreiving the self avatar */
996       connection = empathy_contact_get_connection (priv->contact);
997       factory = empathy_tp_contact_factory_dup_singleton (connection);
998       empathy_tp_contact_factory_get_from_handle (factory,
999           tp_connection_get_self_handle (connection),
1000           empathy_call_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1001
1002       g_object_unref (factory);
1003     }
1004   else
1005     {
1006       g_warning ("call handler doesn't have a contact");
1007       /* translators: Call is a noun. This string is used in the window
1008        * title */
1009       gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1010
1011       /* Since we can't access the remote contact, we can't get a connection
1012          to it and can't get the self contact (and its avatar). This means
1013          that we have to manually set the self avatar. */
1014       init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1015           MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1016     }
1017
1018   init_contact_avatar_with_size (priv->contact,
1019       priv->remote_user_avatar_widget,
1020       MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1021           REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1022
1023   /* The remote avatar is shown by default and will be hidden when we receive
1024      video from the remote side. */
1025   gtk_widget_hide (priv->video_output);
1026   gtk_widget_show (priv->remote_user_avatar_widget);
1027 }
1028
1029 static void
1030 empathy_call_window_constructed (GObject *object)
1031 {
1032   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1033   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1034   EmpathyTpCall *call;
1035
1036   g_assert (priv->handler != NULL);
1037
1038   g_object_get (priv->handler, "tp-call", &call, NULL);
1039   priv->outgoing = (call == NULL);
1040   if (call != NULL)
1041     g_object_unref (call);
1042
1043   empathy_call_window_setup_avatars (self, priv->handler);
1044   empathy_call_window_set_state_connecting (self);
1045
1046   if (empathy_call_handler_has_initial_video (priv->handler))
1047     {
1048       /* Enable 'send video' buttons and display the preview */
1049       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), TRUE);
1050       gtk_toggle_tool_button_set_active (
1051           GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), TRUE);
1052
1053       display_video_preview (self, TRUE);
1054     }
1055 }
1056
1057 static void empathy_call_window_dispose (GObject *object);
1058 static void empathy_call_window_finalize (GObject *object);
1059
1060 static void
1061 empathy_call_window_set_property (GObject *object,
1062   guint property_id, const GValue *value, GParamSpec *pspec)
1063 {
1064   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1065
1066   switch (property_id)
1067     {
1068       case PROP_CALL_HANDLER:
1069         priv->handler = g_value_dup_object (value);
1070         break;
1071       default:
1072         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1073     }
1074 }
1075
1076 static void
1077 empathy_call_window_get_property (GObject *object,
1078   guint property_id, GValue *value, GParamSpec *pspec)
1079 {
1080   EmpathyCallWindowPriv *priv = GET_PRIV (object);
1081
1082   switch (property_id)
1083     {
1084       case PROP_CALL_HANDLER:
1085         g_value_set_object (value, priv->handler);
1086         break;
1087       default:
1088         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1089     }
1090 }
1091
1092 static void
1093 empathy_call_window_class_init (
1094   EmpathyCallWindowClass *empathy_call_window_class)
1095 {
1096   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
1097   GParamSpec *param_spec;
1098
1099   g_type_class_add_private (empathy_call_window_class,
1100     sizeof (EmpathyCallWindowPriv));
1101
1102   object_class->constructed = empathy_call_window_constructed;
1103   object_class->set_property = empathy_call_window_set_property;
1104   object_class->get_property = empathy_call_window_get_property;
1105
1106   object_class->dispose = empathy_call_window_dispose;
1107   object_class->finalize = empathy_call_window_finalize;
1108
1109   param_spec = g_param_spec_object ("handler",
1110     "handler", "The call handler",
1111     EMPATHY_TYPE_CALL_HANDLER,
1112     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1113   g_object_class_install_property (object_class,
1114     PROP_CALL_HANDLER, param_spec);
1115 }
1116
1117 static void
1118 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1119     GParamSpec *property, EmpathyCallWindow *self)
1120 {
1121   DEBUG ("video stream changed");
1122   empathy_call_window_update_avatars_visibility (call, self);
1123 }
1124
1125 void
1126 empathy_call_window_dispose (GObject *object)
1127 {
1128   EmpathyTpCall *call;
1129   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1130   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1131
1132   if (priv->dispose_has_run)
1133     return;
1134
1135   priv->dispose_has_run = TRUE;
1136
1137   g_object_get (priv->handler, "tp-call", &call, NULL);
1138
1139   if (call != NULL)
1140     {
1141       g_signal_handlers_disconnect_by_func (call,
1142         empathy_call_window_video_stream_changed_cb, object);
1143       g_object_unref (call);
1144     }
1145
1146   if (priv->handler != NULL)
1147     g_object_unref (priv->handler);
1148   priv->handler = NULL;
1149
1150   if (priv->pipeline != NULL)
1151     g_object_unref (priv->pipeline);
1152   priv->pipeline = NULL;
1153
1154   if (priv->video_input != NULL)
1155     g_object_unref (priv->video_input);
1156   priv->video_input = NULL;
1157
1158   if (priv->audio_input != NULL)
1159     g_object_unref (priv->audio_input);
1160   priv->audio_input = NULL;
1161
1162   if (priv->audio_output != NULL)
1163     g_object_unref (priv->audio_output);
1164   priv->audio_output = NULL;
1165
1166   if (priv->video_tee != NULL)
1167     g_object_unref (priv->video_tee);
1168   priv->video_tee = NULL;
1169
1170   if (priv->fsnotifier != NULL)
1171     g_object_unref (priv->fsnotifier);
1172   priv->fsnotifier = NULL;
1173
1174   if (priv->timer_id != 0)
1175     g_source_remove (priv->timer_id);
1176   priv->timer_id = 0;
1177
1178   if (priv->ui_manager != NULL)
1179     g_object_unref (priv->ui_manager);
1180   priv->ui_manager = NULL;
1181
1182   if (priv->contact != NULL)
1183     {
1184       g_signal_handlers_disconnect_by_func (priv->contact,
1185           contact_name_changed_cb, self);
1186       g_object_unref (priv->contact);
1187       priv->contact = NULL;
1188     }
1189
1190   /* release any references held by the object here */
1191   if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1192     G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1193 }
1194
1195 void
1196 empathy_call_window_finalize (GObject *object)
1197 {
1198   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1199   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1200
1201   if (priv->video_output_motion_handler_id != 0)
1202     {
1203       g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1204           priv->video_output_motion_handler_id);
1205       priv->video_output_motion_handler_id = 0;
1206     }
1207
1208   if (priv->bus_message_source_id != 0)
1209     {
1210       g_source_remove (priv->bus_message_source_id);
1211       priv->bus_message_source_id = 0;
1212     }
1213
1214   /* free any data held directly by the object here */
1215   g_mutex_free (priv->lock);
1216
1217   g_timer_destroy (priv->timer);
1218
1219   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1220 }
1221
1222
1223 EmpathyCallWindow *
1224 empathy_call_window_new (EmpathyCallHandler *handler)
1225 {
1226   return EMPATHY_CALL_WINDOW (
1227     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1228 }
1229
1230 static void
1231 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1232   GstElement *conference, gpointer user_data)
1233 {
1234   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1235   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1236
1237   gst_bin_add (GST_BIN (priv->pipeline), conference);
1238
1239   gst_element_set_state (conference, GST_STATE_PLAYING);
1240 }
1241
1242 static gboolean
1243 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1244   FsMediaType type, FsStreamDirection direction, gpointer user_data)
1245 {
1246   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1247   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1248
1249   if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1250     return TRUE;
1251
1252   if (direction == FS_DIRECTION_RECV)
1253     return TRUE;
1254
1255   /* video and direction is send */
1256   return priv->video_input != NULL;
1257 }
1258
1259 static gboolean
1260 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1261 {
1262   GstStateChangeReturn state_change_return;
1263   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1264
1265   if (priv->pipeline == NULL)
1266     return TRUE;
1267
1268   if (priv->bus_message_source_id != 0)
1269     {
1270       g_source_remove (priv->bus_message_source_id);
1271       priv->bus_message_source_id = 0;
1272     }
1273
1274   state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1275
1276   if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1277         state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1278     {
1279       if (priv->pipeline != NULL)
1280         g_object_unref (priv->pipeline);
1281       priv->pipeline = NULL;
1282
1283       if (priv->video_input != NULL)
1284         g_object_unref (priv->video_input);
1285       priv->video_input = NULL;
1286
1287       if (priv->audio_input != NULL)
1288         g_object_unref (priv->audio_input);
1289       priv->audio_input = NULL;
1290
1291       g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1292           empathy_call_window_mic_volume_changed_cb, self);
1293
1294       if (priv->audio_output != NULL)
1295         g_object_unref (priv->audio_output);
1296       priv->audio_output = NULL;
1297
1298       if (priv->video_tee != NULL)
1299         g_object_unref (priv->video_tee);
1300       priv->video_tee = NULL;
1301
1302       if (priv->video_preview != NULL)
1303         gtk_widget_destroy (priv->video_preview);
1304       priv->video_preview = NULL;
1305
1306       priv->liveadder = NULL;
1307       priv->funnel = NULL;
1308
1309       return TRUE;
1310     }
1311   else
1312     {
1313       g_message ("Error: could not destroy pipeline. Closing call window");
1314       gtk_widget_destroy (GTK_WIDGET (self));
1315
1316       return FALSE;
1317     }
1318 }
1319
1320 static gboolean
1321 empathy_call_window_disconnected (EmpathyCallWindow *self)
1322 {
1323   gboolean could_disconnect = FALSE;
1324   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1325   gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1326
1327   if (priv->call_state == CONNECTING)
1328       empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1329
1330   if (priv->call_state != REDIALING)
1331     priv->call_state = DISCONNECTED;
1332
1333   if (could_reset_pipeline)
1334     {
1335       gboolean initial_video = empathy_call_handler_has_initial_video (
1336           priv->handler);
1337       g_mutex_lock (priv->lock);
1338
1339       g_timer_stop (priv->timer);
1340
1341       if (priv->timer_id != 0)
1342         g_source_remove (priv->timer_id);
1343       priv->timer_id = 0;
1344
1345       g_mutex_unlock (priv->lock);
1346
1347       empathy_call_window_status_message (self, _("Disconnected"));
1348
1349       gtk_action_set_sensitive (priv->redial, TRUE);
1350       gtk_widget_set_sensitive (priv->redial_button, TRUE);
1351
1352       /* Reseting the send_video, camera_buton and mic_button to their
1353          initial state */
1354       gtk_widget_set_sensitive (priv->camera_button, FALSE);
1355       gtk_widget_set_sensitive (priv->mic_button, FALSE);
1356       gtk_action_set_sensitive (priv->send_video, FALSE);
1357       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1358           initial_video);
1359       gtk_toggle_tool_button_set_active (
1360           GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), initial_video);
1361       gtk_toggle_tool_button_set_active (
1362           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1363
1364       gtk_progress_bar_set_fraction (
1365           GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1366
1367       gtk_widget_hide (priv->video_output);
1368       gtk_widget_show (priv->remote_user_avatar_widget);
1369
1370       priv->sending_video = FALSE;
1371       priv->call_started = FALSE;
1372
1373       could_disconnect = TRUE;
1374
1375       /* TODO: display the self avatar of the preview (depends if the "Always
1376        * Show Video Preview" is enabled or not) */
1377     }
1378
1379   return could_disconnect;
1380 }
1381
1382
1383 static void
1384 empathy_call_window_channel_closed_cb (EmpathyCallHandler *handler,
1385     gpointer user_data)
1386 {
1387   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1388   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1389
1390   if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1391       empathy_call_window_restart_call (self);
1392 }
1393
1394
1395 static void
1396 empathy_call_window_channel_stream_closed_cb (EmpathyCallHandler *handler,
1397     TfStream *stream, gpointer user_data)
1398 {
1399   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1400   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1401   guint media_type;
1402
1403   g_object_get (stream, "media-type", &media_type, NULL);
1404
1405   /*
1406    * This assumes that there is only one video stream per channel...
1407    */
1408
1409   if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1410     {
1411       if (priv->funnel != NULL)
1412         {
1413           GstElement *output;
1414
1415           output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1416               (priv->video_output));
1417
1418           gst_element_set_state (output, GST_STATE_NULL);
1419           gst_element_set_state (priv->funnel, GST_STATE_NULL);
1420
1421           gst_bin_remove (GST_BIN (priv->pipeline), output);
1422           gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1423           priv->funnel = NULL;
1424         }
1425     }
1426   else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1427     {
1428       if (priv->liveadder != NULL)
1429         {
1430           gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1431           gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1432
1433           gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1434           gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1435           priv->liveadder = NULL;
1436         }
1437     }
1438 }
1439
1440 /* Called with global lock held */
1441 static GstPad *
1442 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1443 {
1444   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1445   GstPad *pad;
1446
1447   if (priv->funnel == NULL)
1448     {
1449       GstElement *output;
1450
1451       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1452         (priv->video_output));
1453
1454       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1455
1456       gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1457       gst_bin_add (GST_BIN (priv->pipeline), output);
1458
1459       gst_element_link (priv->funnel, output);
1460
1461       gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1462       gst_element_set_state (output, GST_STATE_PLAYING);
1463     }
1464
1465   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1466
1467   return pad;
1468 }
1469
1470 /* Called with global lock held */
1471 static GstPad *
1472 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1473 {
1474   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1475   GstPad *pad;
1476
1477   if (priv->liveadder == NULL)
1478     {
1479       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1480
1481       gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1482       gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1483
1484       gst_element_link (priv->liveadder, priv->audio_output);
1485
1486       gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1487       gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1488     }
1489
1490   pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1491
1492   return pad;
1493 }
1494
1495 static gboolean
1496 empathy_call_window_update_timer (gpointer user_data)
1497 {
1498   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1499   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1500   gchar *str;
1501   gdouble time_;
1502
1503   time_ = g_timer_elapsed (priv->timer, NULL);
1504
1505   /* Translators: number of minutes:seconds the caller has been connected */
1506   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time_ / 60,
1507     (int) time_ % 60);
1508   empathy_call_window_status_message (self, str);
1509   g_free (str);
1510
1511   return TRUE;
1512 }
1513
1514 static void
1515 display_error (EmpathyCallWindow *self,
1516     EmpathyTpCall *call,
1517     const gchar *img,
1518     const gchar *title,
1519     const gchar *desc,
1520     const gchar *details)
1521 {
1522   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1523   GtkWidget *info_bar;
1524   GtkWidget *content_area;
1525   GtkWidget *hbox;
1526   GtkWidget *vbox;
1527   GtkWidget *image;
1528   GtkWidget *label;
1529   gchar *txt;
1530
1531   /* Create info bar */
1532   info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1533       NULL);
1534
1535   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
1536
1537   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
1538
1539   /* hbox containing the image and the messages vbox */
1540   hbox = gtk_hbox_new (FALSE, 3);
1541   gtk_container_add (GTK_CONTAINER (content_area), hbox);
1542
1543   /* Add image */
1544   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
1545   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
1546
1547   /* vbox containing the main message and the details expander */
1548   vbox = gtk_vbox_new (FALSE, 3);
1549   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1550
1551   /* Add text */
1552   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
1553
1554   label = gtk_label_new (NULL);
1555   gtk_label_set_markup (GTK_LABEL (label), txt);
1556   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1557   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1558   g_free (txt);
1559
1560   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
1561
1562   /* Add details */
1563   if (details != NULL)
1564     {
1565       GtkWidget *expander;
1566
1567       expander = gtk_expander_new (_("Technical Details"));
1568
1569       txt = g_strdup_printf ("<i>%s</i>", details);
1570
1571       label = gtk_label_new (NULL);
1572       gtk_label_set_markup (GTK_LABEL (label), txt);
1573       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1574       gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1575       g_free (txt);
1576
1577       gtk_container_add (GTK_CONTAINER (expander), label);
1578       gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
1579     }
1580
1581   g_signal_connect (info_bar, "response",
1582       G_CALLBACK (gtk_widget_destroy), NULL);
1583
1584   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
1585       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1586   gtk_widget_show_all (info_bar);
1587 }
1588
1589 static gchar *
1590 media_stream_error_to_txt (EmpathyCallWindow *self,
1591     EmpathyTpCall *call,
1592     gboolean audio,
1593     TpMediaStreamError error)
1594 {
1595   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1596   const gchar *cm;
1597   gchar *url;
1598   gchar *result;
1599
1600   switch (error)
1601     {
1602       case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
1603         if (audio)
1604           return g_strdup_printf (
1605               _("%s's software does not understand any of the audio formats "
1606                 "supported by your computer"),
1607             empathy_contact_get_name (priv->contact));
1608         else
1609           return g_strdup_printf (
1610               _("%s's software does not understand any of the video formats "
1611                 "supported by your computer"),
1612             empathy_contact_get_name (priv->contact));
1613
1614       case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
1615         return g_strdup_printf (
1616             _("Can't establish a connection to %s. "
1617               "One of you might be on a network that does not allow "
1618               "direct connections."),
1619           empathy_contact_get_name (priv->contact));
1620
1621       case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
1622           return g_strdup (_("There was a failure on the network"));
1623
1624       case TP_MEDIA_STREAM_ERROR_NO_CODECS:
1625         if (audio)
1626           return g_strdup (_("The audio formats necessary for this call "
1627                 "are not installed on your computer"));
1628         else
1629           return g_strdup (_("The video formats necessary for this call "
1630                 "are not installed on your computer"));
1631
1632       case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
1633         cm = empathy_tp_call_get_connection_manager (call);
1634
1635         url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
1636             "product=Telepathy&amp;component=%s", cm);
1637
1638         result = g_strdup_printf (
1639             _("Something not expected happened in a Telepathy component. "
1640               "Please <a href=\"%s\">report this bug</a> and attach "
1641               "logs gathered from the 'Debug' window in the Help menu."), url);
1642
1643         g_free (url);
1644         return result;
1645
1646       case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
1647         return g_strdup (_("There was a failure in the call engine"));
1648
1649       default:
1650         return NULL;
1651     }
1652 }
1653
1654 static void
1655 empathy_call_window_stream_error (EmpathyCallWindow *self,
1656     EmpathyTpCall *call,
1657     gboolean audio,
1658     guint code,
1659     const gchar *msg,
1660     const gchar *icon,
1661     const gchar *title)
1662 {
1663   gchar *desc;
1664
1665   desc = media_stream_error_to_txt (self, call, audio, code);
1666   if (desc == NULL)
1667     {
1668       /* No description, use the error message. That's not great as it's not
1669        * localized but it's better than nothing. */
1670       display_error (self, call, icon, title, msg, NULL);
1671     }
1672   else
1673     {
1674       display_error (self, call, icon, title, desc, msg);
1675       g_free (desc);
1676     }
1677 }
1678
1679 static void
1680 empathy_call_window_audio_stream_error (EmpathyTpCall *call,
1681     guint code,
1682     const gchar *msg,
1683     EmpathyCallWindow *self)
1684 {
1685   empathy_call_window_stream_error (self, call, TRUE, code, msg,
1686       "gnome-stock-mic", _("Can't establish audio stream"));
1687 }
1688
1689 static void
1690 empathy_call_window_video_stream_error (EmpathyTpCall *call,
1691     guint code,
1692     const gchar *msg,
1693     EmpathyCallWindow *self)
1694 {
1695   empathy_call_window_stream_error (self, call, FALSE, code, msg,
1696       "camera-web", _("Can't establish video stream"));
1697 }
1698
1699 static gboolean
1700 empathy_call_window_connected (gpointer user_data)
1701 {
1702   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1703   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1704   EmpathyTpCall *call;
1705   gboolean can_send_video;
1706
1707   empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1708
1709   can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1710     empathy_contact_can_voip_video (priv->contact);
1711
1712   g_object_get (priv->handler, "tp-call", &call, NULL);
1713
1714   g_signal_connect (call, "notify::video-stream",
1715     G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1716
1717   if (empathy_tp_call_has_dtmf (call))
1718     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1719
1720   if (priv->video_input == NULL)
1721     empathy_call_window_set_send_video (self, FALSE);
1722
1723   priv->sending_video = can_send_video ?
1724     empathy_tp_call_is_sending_video (call) : FALSE;
1725
1726   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1727       priv->sending_video && priv->video_input != NULL);
1728   gtk_toggle_tool_button_set_active (
1729       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1730       priv->sending_video && priv->video_input != NULL);
1731   gtk_widget_set_sensitive (priv->camera_button, can_send_video);
1732   gtk_action_set_sensitive (priv->send_video, can_send_video);
1733
1734   gtk_action_set_sensitive (priv->redial, FALSE);
1735   gtk_widget_set_sensitive (priv->redial_button, FALSE);
1736
1737   gtk_widget_set_sensitive (priv->mic_button, TRUE);
1738
1739   empathy_call_window_update_avatars_visibility (call, self);
1740
1741   g_object_unref (call);
1742
1743   g_mutex_lock (priv->lock);
1744
1745   priv->timer_id = g_timeout_add_seconds (1,
1746     empathy_call_window_update_timer, self);
1747
1748   g_mutex_unlock (priv->lock);
1749
1750   empathy_call_window_update_timer (self);
1751
1752   return FALSE;
1753 }
1754
1755
1756 /* Called from the streaming thread */
1757 static void
1758 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1759   GstPad *src, guint media_type, gpointer user_data)
1760 {
1761   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1762   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1763
1764   GstPad *pad;
1765
1766   g_mutex_lock (priv->lock);
1767
1768   if (priv->call_state != CONNECTED)
1769     {
1770       g_timer_start (priv->timer);
1771       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
1772       priv->call_state = CONNECTED;
1773     }
1774
1775   switch (media_type)
1776     {
1777       case TP_MEDIA_STREAM_TYPE_AUDIO:
1778         pad = empathy_call_window_get_audio_sink_pad (self);
1779         break;
1780       case TP_MEDIA_STREAM_TYPE_VIDEO:
1781         gtk_widget_hide (priv->remote_user_avatar_widget);
1782         gtk_widget_show (priv->video_output);
1783         pad = empathy_call_window_get_video_sink_pad (self);
1784         break;
1785       default:
1786         g_assert_not_reached ();
1787     }
1788
1789   gst_pad_link (src, pad);
1790   gst_object_unref (pad);
1791
1792   g_mutex_unlock (priv->lock);
1793 }
1794
1795 /* Called from the streaming thread */
1796 static void
1797 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1798   GstPad *sink, guint media_type, gpointer user_data)
1799 {
1800   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1801   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1802   GstPad *pad;
1803
1804   switch (media_type)
1805     {
1806       case TP_MEDIA_STREAM_TYPE_AUDIO:
1807         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1808
1809         pad = gst_element_get_static_pad (priv->audio_input, "src");
1810         gst_pad_link (pad, sink);
1811
1812         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1813         break;
1814       case TP_MEDIA_STREAM_TYPE_VIDEO:
1815         if (priv->video_input != NULL)
1816           {
1817             if (priv->video_tee != NULL)
1818               {
1819                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1820                 gst_pad_link (pad, sink);
1821               }
1822           }
1823         break;
1824       default:
1825         g_assert_not_reached ();
1826     }
1827
1828 }
1829
1830 static void
1831 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1832 {
1833   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1834   GstElement *preview;
1835
1836   DEBUG ("remove video input");
1837   preview = empathy_video_widget_get_element (
1838     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1839
1840   gst_element_set_state (priv->video_input, GST_STATE_NULL);
1841   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1842   gst_element_set_state (preview, GST_STATE_NULL);
1843
1844   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1845     priv->video_tee, preview, NULL);
1846
1847   g_object_unref (priv->video_input);
1848   priv->video_input = NULL;
1849   g_object_unref (priv->video_tee);
1850   priv->video_tee = NULL;
1851   gtk_widget_destroy (priv->video_preview);
1852   priv->video_preview = NULL;
1853
1854   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1855   gtk_toggle_tool_button_set_active (
1856       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1857   gtk_widget_set_sensitive (priv->camera_button, FALSE);
1858   gtk_action_set_sensitive (priv->send_video, FALSE);
1859
1860   gtk_widget_show (priv->self_user_avatar_widget);
1861 }
1862
1863
1864 static gboolean
1865 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1866   gpointer user_data)
1867 {
1868   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1869   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1870   GstState newstate;
1871
1872   empathy_call_handler_bus_message (priv->handler, bus, message);
1873
1874   switch (GST_MESSAGE_TYPE (message))
1875     {
1876       case GST_MESSAGE_STATE_CHANGED:
1877         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1878           {
1879             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1880             if (newstate == GST_STATE_PAUSED)
1881                 empathy_call_window_setup_video_input (self);
1882           }
1883         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1884             !priv->call_started)
1885           {
1886             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1887             if (newstate == GST_STATE_PAUSED)
1888               {
1889                 priv->call_started = TRUE;
1890                 empathy_call_handler_start_call (priv->handler);
1891                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1892               }
1893           }
1894         break;
1895       case GST_MESSAGE_ERROR:
1896         {
1897           GError *error = NULL;
1898           GstElement *gst_error;
1899           gchar *debug;
1900
1901           gst_message_parse_error (message, &error, &debug);
1902           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1903
1904           g_message ("Element error: %s -- %s\n", error->message, debug);
1905
1906           if (g_str_has_prefix (gst_element_get_name (gst_error),
1907                 VIDEO_INPUT_ERROR_PREFIX))
1908             {
1909               /* Remove the video input and continue */
1910               if (priv->video_input != NULL)
1911                 empathy_call_window_remove_video_input (self);
1912               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1913             }
1914           else
1915             {
1916               empathy_call_window_disconnected (self);
1917             }
1918           g_error_free (error);
1919           g_free (debug);
1920         }
1921       default:
1922         break;
1923     }
1924
1925   return TRUE;
1926 }
1927
1928 static void
1929 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1930     EmpathyCallWindow *window)
1931 {
1932   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1933
1934   if (empathy_tp_call_is_receiving_video (call))
1935     {
1936       gtk_widget_hide (priv->remote_user_avatar_widget);
1937       gtk_widget_show (priv->video_output);
1938     }
1939   else
1940     {
1941       gtk_widget_hide (priv->video_output);
1942       gtk_widget_show (priv->remote_user_avatar_widget);
1943     }
1944 }
1945
1946 static void
1947 call_handler_notify_tp_call_cb (EmpathyCallHandler *handler,
1948     GParamSpec *spec,
1949     EmpathyCallWindow *self)
1950 {
1951   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1952   EmpathyTpCall *call;
1953
1954   g_object_get (priv->handler, "tp-call", &call, NULL);
1955   if (call == NULL)
1956     return;
1957
1958   empathy_signal_connect_weak (call, "audio-stream-error",
1959       G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (self));
1960   empathy_signal_connect_weak (call, "video-stream-error",
1961       G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (self));
1962
1963   g_object_unref (call);
1964 }
1965
1966 static void
1967 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1968 {
1969   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1970   EmpathyTpCall *call;
1971
1972   g_signal_connect (priv->handler, "conference-added",
1973     G_CALLBACK (empathy_call_window_conference_added_cb), window);
1974   g_signal_connect (priv->handler, "request-resource",
1975     G_CALLBACK (empathy_call_window_request_resource_cb), window);
1976   g_signal_connect (priv->handler, "closed",
1977     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1978   g_signal_connect (priv->handler, "src-pad-added",
1979     G_CALLBACK (empathy_call_window_src_added_cb), window);
1980   g_signal_connect (priv->handler, "sink-pad-added",
1981     G_CALLBACK (empathy_call_window_sink_added_cb), window);
1982   g_signal_connect (priv->handler, "stream-closed",
1983     G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
1984
1985   g_object_get (priv->handler, "tp-call", &call, NULL);
1986   if (call != NULL)
1987     {
1988       empathy_signal_connect_weak (call, "audio-stream-error",
1989         G_CALLBACK (empathy_call_window_audio_stream_error), G_OBJECT (window));
1990       empathy_signal_connect_weak (call, "video-stream-error",
1991         G_CALLBACK (empathy_call_window_video_stream_error), G_OBJECT (window));
1992
1993       g_object_unref (call);
1994     }
1995   else
1996     {
1997       /* tp-call doesn't exist yet, we'll connect signals once it has been
1998        * set */
1999       g_signal_connect (priv->handler, "notify::tp-call",
2000         G_CALLBACK (call_handler_notify_tp_call_cb), window);
2001     }
2002
2003   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2004 }
2005
2006 static gboolean
2007 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2008   EmpathyCallWindow *window)
2009 {
2010   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2011
2012   if (priv->pipeline != NULL)
2013     {
2014       if (priv->bus_message_source_id != 0)
2015         {
2016           g_source_remove (priv->bus_message_source_id);
2017           priv->bus_message_source_id = 0;
2018         }
2019
2020       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2021     }
2022
2023   if (priv->call_state == CONNECTING)
2024     empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
2025
2026   return FALSE;
2027 }
2028
2029 static void
2030 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
2031 {
2032   GtkWidget *menu;
2033   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2034
2035   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2036             "/menubar1");
2037
2038   if (set_fullscreen)
2039     {
2040       gtk_widget_hide (priv->sidebar);
2041       gtk_widget_hide (menu);
2042       gtk_widget_hide (priv->vbox);
2043       gtk_widget_hide (priv->statusbar);
2044       gtk_widget_hide (priv->toolbar);
2045     }
2046   else
2047     {
2048       if (priv->sidebar_was_visible_before_fs)
2049         gtk_widget_show (priv->sidebar);
2050
2051       gtk_widget_show (menu);
2052       gtk_widget_show (priv->vbox);
2053       gtk_widget_show (priv->statusbar);
2054       gtk_widget_show (priv->toolbar);
2055
2056       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2057           priv->original_height_before_fs);
2058     }
2059 }
2060
2061 static void
2062 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
2063 {
2064   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2065
2066   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2067       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2068   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2069       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2070   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2071       priv->video_output, TRUE, TRUE,
2072       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2073       GTK_PACK_START);
2074   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2075       priv->vbox, TRUE, TRUE,
2076       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2077       GTK_PACK_START);
2078 }
2079
2080 static gboolean
2081 empathy_call_window_state_event_cb (GtkWidget *widget,
2082   GdkEventWindowState *event, EmpathyCallWindow *window)
2083 {
2084   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2085     {
2086       EmpathyCallWindowPriv *priv = GET_PRIV (window);
2087       gboolean set_fullscreen = event->new_window_state &
2088         GDK_WINDOW_STATE_FULLSCREEN;
2089
2090       if (set_fullscreen)
2091         {
2092           gboolean sidebar_was_visible;
2093           GtkAllocation allocation;
2094           gint original_width, original_height;
2095
2096           gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2097           original_width = allocation.width;
2098           original_height = allocation.height;
2099
2100           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2101
2102           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2103           priv->original_width_before_fs = original_width;
2104           priv->original_height_before_fs = original_height;
2105
2106           if (priv->video_output_motion_handler_id == 0 &&
2107                 priv->video_output != NULL)
2108             {
2109               priv->video_output_motion_handler_id = g_signal_connect (
2110                   G_OBJECT (priv->video_output), "motion-notify-event",
2111                   G_CALLBACK (empathy_call_window_video_output_motion_notify),
2112                   window);
2113             }
2114         }
2115       else
2116         {
2117           if (priv->video_output_motion_handler_id != 0)
2118             {
2119               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
2120                   priv->video_output_motion_handler_id);
2121               priv->video_output_motion_handler_id = 0;
2122             }
2123         }
2124
2125       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
2126           set_fullscreen);
2127       show_controls (window, set_fullscreen);
2128       show_borders (window, set_fullscreen);
2129       gtk_action_set_stock_id (priv->menu_fullscreen,
2130           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2131       priv->is_fullscreen = set_fullscreen;
2132   }
2133
2134   return FALSE;
2135 }
2136
2137 static void
2138 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2139   EmpathyCallWindow *window)
2140 {
2141   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2142   GtkWidget *arrow;
2143   int w, h, handle_size;
2144   GtkAllocation allocation, sidebar_allocation;
2145
2146   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2147   w = allocation.width;
2148   h = allocation.height;
2149
2150   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2151
2152   gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2153   if (gtk_toggle_button_get_active (toggle))
2154     {
2155       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2156       gtk_widget_show (priv->sidebar);
2157       w += sidebar_allocation.width + handle_size;
2158     }
2159   else
2160     {
2161       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2162       w -= sidebar_allocation.width + handle_size;
2163       gtk_widget_hide (priv->sidebar);
2164     }
2165
2166   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2167
2168   if (w > 0 && h > 0)
2169     gtk_window_resize (GTK_WINDOW (window), w, h);
2170 }
2171
2172 static void
2173 empathy_call_window_set_send_video (EmpathyCallWindow *window,
2174   gboolean send)
2175 {
2176   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2177   EmpathyTpCall *call;
2178
2179   priv->sending_video = send;
2180
2181   /* When we start sending video, we want to show the video preview by
2182      default. */
2183   display_video_preview (window, send);
2184
2185   g_object_get (priv->handler, "tp-call", &call, NULL);
2186   empathy_tp_call_request_video_stream_direction (call, send);
2187   g_object_unref (call);
2188 }
2189
2190 static void
2191 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
2192   EmpathyCallWindow *window)
2193 {
2194   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2195   gboolean active;
2196
2197   if (priv->call_state != CONNECTED)
2198     return;
2199
2200   active = (gtk_toggle_tool_button_get_active (toggle));
2201
2202   if (priv->sending_video == active)
2203     return;
2204
2205   empathy_call_window_set_send_video (window, active);
2206   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
2207 }
2208
2209 static void
2210 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
2211   EmpathyCallWindow *window)
2212 {
2213   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2214   gboolean active;
2215
2216   if (priv->call_state != CONNECTED)
2217     return;
2218
2219   active = (gtk_toggle_action_get_active (toggle));
2220
2221   if (priv->sending_video == active)
2222     return;
2223
2224   empathy_call_window_set_send_video (window, active);
2225   gtk_toggle_tool_button_set_active (
2226       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
2227 }
2228
2229 static void
2230 empathy_call_window_always_show_preview_toggled_cb (GtkToggleAction *toggle,
2231   EmpathyCallWindow *window)
2232 {
2233   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2234
2235   if (gtk_toggle_action_get_active (toggle))
2236     {
2237       display_video_preview (window, TRUE);
2238     }
2239   else
2240     {
2241       /* disable preview if we are not sending */
2242       if (!priv->sending_video)
2243         display_video_preview (window, FALSE);
2244     }
2245 }
2246
2247 static void
2248 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2249   EmpathyCallWindow *window)
2250 {
2251   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2252   gboolean active;
2253
2254   if (priv->audio_input == NULL)
2255     return;
2256
2257   active = (gtk_toggle_tool_button_get_active (toggle));
2258
2259   if (active)
2260     {
2261       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2262         priv->volume);
2263       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2264     }
2265   else
2266     {
2267       /* TODO, Instead of setting the input volume to 0 we should probably
2268        * stop sending but this would cause the audio call to drop if both
2269        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2270        * in the future. GNOME #574574
2271        */
2272       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2273         0);
2274       gtk_adjustment_set_value (priv->audio_input_adj, 0);
2275     }
2276 }
2277
2278 static void
2279 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2280   EmpathyCallWindow *window)
2281 {
2282   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2283
2284   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2285     FALSE);
2286 }
2287
2288 static void
2289 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2290   EmpathyCallWindow *window)
2291 {
2292   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2293
2294   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2295     TRUE);
2296 }
2297
2298 static void
2299 empathy_call_window_hangup_cb (gpointer object,
2300                                EmpathyCallWindow *window)
2301 {
2302   if (empathy_call_window_disconnected (window))
2303     gtk_widget_destroy (GTK_WIDGET (window));
2304 }
2305
2306 static void
2307 empathy_call_window_restart_call (EmpathyCallWindow *window)
2308 {
2309   GstBus *bus;
2310   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2311
2312   gtk_widget_destroy (priv->remote_user_output_hbox);
2313   gtk_widget_destroy (priv->self_user_output_hbox);
2314
2315   priv->pipeline = gst_pipeline_new (NULL);
2316   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2317   priv->bus_message_source_id = gst_bus_add_watch (bus,
2318       empathy_call_window_bus_message, window);
2319
2320   empathy_call_window_setup_remote_frame (bus, window);
2321   empathy_call_window_setup_self_frame (bus, window);
2322
2323   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2324       G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2325
2326   /* While the call was disconnected, the input volume might have changed.
2327    * However, since the audio_input source was destroyed, its volume has not
2328    * been updated during that time. That's why we manually update it here */
2329   empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2330
2331   g_object_unref (bus);
2332
2333   gtk_widget_show_all (priv->content_hbox);
2334
2335   priv->outgoing = TRUE;
2336   empathy_call_window_set_state_connecting (window);
2337
2338   priv->call_started = TRUE;
2339   empathy_call_handler_start_call (priv->handler);
2340   empathy_call_window_setup_avatars (window, priv->handler);
2341   gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2342
2343   gtk_action_set_sensitive (priv->redial, FALSE);
2344   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2345 }
2346
2347 static void
2348 empathy_call_window_redial_cb (gpointer object,
2349     EmpathyCallWindow *window)
2350 {
2351   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2352
2353   if (priv->call_state == CONNECTED)
2354     priv->call_state = REDIALING;
2355
2356   empathy_call_handler_stop_call (priv->handler);
2357
2358   if (priv->call_state != CONNECTED)
2359     empathy_call_window_restart_call (window);
2360 }
2361
2362 static void
2363 empathy_call_window_fullscreen_cb (gpointer object,
2364                                    EmpathyCallWindow *window)
2365 {
2366   empathy_call_window_fullscreen_toggle (window);
2367 }
2368
2369 static void
2370 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2371 {
2372   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2373
2374   if (priv->is_fullscreen)
2375     gtk_window_unfullscreen (GTK_WINDOW (window));
2376   else
2377     gtk_window_fullscreen (GTK_WINDOW (window));
2378 }
2379
2380 static gboolean
2381 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2382   GdkEventButton *event, EmpathyCallWindow *window)
2383 {
2384   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2385     {
2386       empathy_call_window_video_menu_popup (window, event->button);
2387       return TRUE;
2388     }
2389
2390   return FALSE;
2391 }
2392
2393 static gboolean
2394 empathy_call_window_key_press_cb (GtkWidget *video_output,
2395   GdkEventKey *event, EmpathyCallWindow *window)
2396 {
2397   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2398
2399   if (priv->is_fullscreen && event->keyval == GDK_Escape)
2400     {
2401       /* Since we are in fullscreen mode, toggling will bring us back to
2402          normal mode. */
2403       empathy_call_window_fullscreen_toggle (window);
2404       return TRUE;
2405     }
2406
2407   return FALSE;
2408 }
2409
2410 static gboolean
2411 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2412     GdkEventMotion *event, EmpathyCallWindow *window)
2413 {
2414   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2415
2416   if (priv->is_fullscreen)
2417     {
2418       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2419       return TRUE;
2420     }
2421   return FALSE;
2422 }
2423
2424 static void
2425 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2426   guint button)
2427 {
2428   GtkWidget *menu;
2429   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2430
2431   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2432             "/video-popup");
2433   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2434       button, gtk_get_current_event_time ());
2435   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2436 }
2437
2438 static void
2439 empathy_call_window_status_message (EmpathyCallWindow *window,
2440   gchar *message)
2441 {
2442   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2443
2444   if (priv->context_id == 0)
2445     {
2446       priv->context_id = gtk_statusbar_get_context_id (
2447         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2448     }
2449   else
2450     {
2451       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2452     }
2453
2454   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2455     message);
2456 }
2457
2458 static void
2459 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2460   gdouble value, EmpathyCallWindow *window)
2461 {
2462   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2463
2464   if (priv->audio_output == NULL)
2465     return;
2466
2467   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2468     value);
2469 }