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