]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
Setting priv->bus_message_source_id to 0 to make sure that
[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     {
1137       g_source_remove (priv->bus_message_source_id);
1138       priv->bus_message_source_id = 0;
1139     }
1140
1141   /* free any data held directly by the object here */
1142   g_mutex_free (priv->lock);
1143
1144   g_timer_destroy (priv->timer);
1145
1146   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1147 }
1148
1149
1150 EmpathyCallWindow *
1151 empathy_call_window_new (EmpathyCallHandler *handler)
1152 {
1153   return EMPATHY_CALL_WINDOW (
1154     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1155 }
1156
1157 static void
1158 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1159   GstElement *conference, gpointer user_data)
1160 {
1161   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1162   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1163
1164   gst_bin_add (GST_BIN (priv->pipeline), conference);
1165
1166   gst_element_set_state (conference, GST_STATE_PLAYING);
1167 }
1168
1169 static gboolean
1170 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1171   FsMediaType type, FsStreamDirection direction, gpointer user_data)
1172 {
1173   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1174   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1175
1176   if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1177     return TRUE;
1178
1179   if (direction == FS_DIRECTION_RECV)
1180     return TRUE;
1181
1182   /* video and direction is send */
1183   return priv->video_input != NULL;
1184 }
1185
1186 static gboolean
1187 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1188 {
1189   GstStateChangeReturn state_change_return;
1190   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1191
1192   if (priv->pipeline == NULL)
1193     return TRUE;
1194
1195   if (priv->bus_message_source_id != 0)
1196     {
1197       g_source_remove (priv->bus_message_source_id);
1198       priv->bus_message_source_id = 0;
1199     }
1200
1201   state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1202
1203   if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1204         state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1205     {
1206       if (priv->pipeline != NULL)
1207         g_object_unref (priv->pipeline);
1208       priv->pipeline = NULL;
1209
1210       if (priv->video_input != NULL)
1211         g_object_unref (priv->video_input);
1212       priv->video_input = NULL;
1213
1214       if (priv->audio_input != NULL)
1215         g_object_unref (priv->audio_input);
1216       priv->audio_input = NULL;
1217
1218       g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1219           empathy_call_window_mic_volume_changed_cb, self);
1220
1221       if (priv->audio_output != NULL)
1222         g_object_unref (priv->audio_output);
1223       priv->audio_output = NULL;
1224
1225       if (priv->video_tee != NULL)
1226         g_object_unref (priv->video_tee);
1227       priv->video_tee = NULL;
1228
1229       if (priv->video_preview != NULL)
1230         gtk_widget_destroy (priv->video_preview);
1231       priv->video_preview = NULL;
1232
1233       priv->liveadder = NULL;
1234       priv->funnel = NULL;
1235
1236       return TRUE;
1237     }
1238   else
1239     {
1240       g_message ("Error: could not destroy pipeline. Closing call window");
1241       gtk_widget_destroy (GTK_WIDGET (self));
1242
1243       return FALSE;
1244     }
1245 }
1246
1247 static gboolean
1248 empathy_call_window_disconnected (EmpathyCallWindow *self)
1249 {
1250   gboolean could_disconnect = FALSE;
1251   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1252   gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1253
1254   if (priv->call_state == CONNECTING)
1255       empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1256
1257   if (priv->call_state != REDIALING)
1258     priv->call_state = DISCONNECTED;
1259
1260   if (could_reset_pipeline)
1261     {
1262       gboolean initial_video = empathy_call_handler_has_initial_video (
1263           priv->handler);
1264       g_mutex_lock (priv->lock);
1265
1266       g_timer_stop (priv->timer);
1267
1268       if (priv->timer_id != 0)
1269         g_source_remove (priv->timer_id);
1270       priv->timer_id = 0;
1271
1272       g_mutex_unlock (priv->lock);
1273
1274       empathy_call_window_status_message (self, _("Disconnected"));
1275
1276       gtk_action_set_sensitive (priv->redial, TRUE);
1277       gtk_widget_set_sensitive (priv->redial_button, TRUE);
1278
1279       /* Reseting the send_video, camera_buton and mic_button to their
1280          initial state */
1281       gtk_widget_set_sensitive (priv->camera_button, FALSE);
1282       gtk_widget_set_sensitive (priv->mic_button, FALSE);
1283       gtk_action_set_sensitive (priv->send_video, FALSE);
1284       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1285           initial_video);
1286       gtk_toggle_tool_button_set_active (
1287           GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), initial_video);
1288       gtk_toggle_tool_button_set_active (
1289           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1290
1291       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1292           FALSE);
1293       gtk_action_set_sensitive (priv->show_preview, FALSE);
1294
1295       gtk_progress_bar_set_fraction (
1296           GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1297
1298       gtk_widget_hide (priv->video_output);
1299       gtk_widget_show (priv->remote_user_avatar_widget);
1300
1301       priv->sending_video = FALSE;
1302       priv->call_started = FALSE;
1303
1304       could_disconnect = TRUE;
1305     }
1306
1307   return could_disconnect;
1308 }
1309
1310
1311 static void
1312 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
1313 {
1314   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1315   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1316
1317   if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1318       empathy_call_window_restart_call (self);
1319 }
1320
1321 /* Called with global lock held */
1322 static GstPad *
1323 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1324 {
1325   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1326   GstPad *pad;
1327
1328   if (priv->funnel == NULL)
1329     {
1330       GstElement *output;
1331
1332       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1333         (priv->video_output));
1334
1335       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1336
1337       gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1338       gst_bin_add (GST_BIN (priv->pipeline), output);
1339
1340       gst_element_link (priv->funnel, output);
1341
1342       gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1343       gst_element_set_state (output, GST_STATE_PLAYING);
1344     }
1345
1346   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1347
1348   return pad;
1349 }
1350
1351 /* Called with global lock held */
1352 static GstPad *
1353 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1354 {
1355   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1356   GstPad *pad;
1357
1358   if (priv->liveadder == NULL)
1359     {
1360       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1361
1362       gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1363       gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1364
1365       gst_element_link (priv->liveadder, priv->audio_output);
1366
1367       gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1368       gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1369     }
1370
1371   pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1372
1373   return pad;
1374 }
1375
1376 static gboolean
1377 empathy_call_window_update_timer (gpointer user_data)
1378 {
1379   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1380   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1381   gchar *str;
1382   gdouble time;
1383
1384   time = g_timer_elapsed (priv->timer, NULL);
1385
1386   /* Translators: number of minutes:seconds the caller has been connected */
1387   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time / 60,
1388     (int) time % 60);
1389   empathy_call_window_status_message (self, str);
1390   g_free (str);
1391
1392   return TRUE;
1393 }
1394
1395 static gboolean
1396 empathy_call_window_connected (gpointer user_data)
1397 {
1398   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1399   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1400   EmpathyTpCall *call;
1401   gboolean can_send_video;
1402
1403   can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1404     empathy_contact_can_voip_video (priv->contact);
1405
1406   g_object_get (priv->handler, "tp-call", &call, NULL);
1407
1408   g_signal_connect (call, "notify::video-stream",
1409     G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1410
1411   if (empathy_tp_call_has_dtmf (call))
1412     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1413
1414   if (priv->video_input == NULL)
1415     empathy_call_window_set_send_video (self, FALSE);
1416
1417   priv->sending_video = can_send_video ?
1418     empathy_tp_call_is_sending_video (call) : FALSE;
1419
1420   gtk_action_set_sensitive (priv->show_preview, TRUE);
1421   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1422       priv->sending_video
1423       || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
1424   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1425       priv->sending_video && priv->video_input != NULL);
1426   gtk_toggle_tool_button_set_active (
1427       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1428       priv->sending_video && priv->video_input != NULL);
1429   gtk_widget_set_sensitive (priv->camera_button, can_send_video);
1430   gtk_action_set_sensitive (priv->send_video, can_send_video);
1431
1432   gtk_action_set_sensitive (priv->redial, FALSE);
1433   gtk_widget_set_sensitive (priv->redial_button, FALSE);
1434
1435   gtk_widget_set_sensitive (priv->mic_button, TRUE);
1436
1437   empathy_call_window_update_avatars_visibility (call, self);
1438
1439   g_object_unref (call);
1440
1441   g_mutex_lock (priv->lock);
1442
1443   priv->timer_id = g_timeout_add_seconds (1,
1444     empathy_call_window_update_timer, self);
1445
1446   g_mutex_unlock (priv->lock);
1447
1448   empathy_call_window_update_timer (self);
1449
1450   return FALSE;
1451 }
1452
1453
1454 /* Called from the streaming thread */
1455 static void
1456 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1457   GstPad *src, guint media_type, gpointer user_data)
1458 {
1459   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1460   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1461
1462   GstPad *pad;
1463
1464   g_mutex_lock (priv->lock);
1465
1466   if (priv->call_state != CONNECTED)
1467     {
1468       g_timer_start (priv->timer);
1469       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
1470       priv->call_state = CONNECTED;
1471       empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1472     }
1473
1474   switch (media_type)
1475     {
1476       case TP_MEDIA_STREAM_TYPE_AUDIO:
1477         pad = empathy_call_window_get_audio_sink_pad (self);
1478         break;
1479       case TP_MEDIA_STREAM_TYPE_VIDEO:
1480         gtk_widget_hide (priv->remote_user_avatar_widget);
1481         gtk_widget_show (priv->video_output);
1482         pad = empathy_call_window_get_video_sink_pad (self);
1483         break;
1484       default:
1485         g_assert_not_reached ();
1486     }
1487
1488   gst_pad_link (src, pad);
1489   gst_object_unref (pad);
1490
1491   g_mutex_unlock (priv->lock);
1492 }
1493
1494 /* Called from the streaming thread */
1495 static void
1496 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1497   GstPad *sink, guint media_type, gpointer user_data)
1498 {
1499   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1500   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1501   GstPad *pad;
1502
1503   switch (media_type)
1504     {
1505       case TP_MEDIA_STREAM_TYPE_AUDIO:
1506         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1507
1508         pad = gst_element_get_static_pad (priv->audio_input, "src");
1509         gst_pad_link (pad, sink);
1510
1511         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1512         break;
1513       case TP_MEDIA_STREAM_TYPE_VIDEO:
1514         if (priv->video_input != NULL)
1515           {
1516             EmpathyTpCall *call;
1517             g_object_get (priv->handler, "tp-call", &call, NULL);
1518
1519             if (empathy_tp_call_is_sending_video (call))
1520               {
1521                 empathy_call_window_setup_video_preview (self);
1522
1523                 gtk_toggle_action_set_active (
1524                     GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1525
1526                 if (priv->video_preview != NULL)
1527                   gtk_widget_show (priv->video_preview);
1528                 gtk_widget_hide (priv->self_user_avatar_widget);
1529               }
1530
1531             g_object_unref (call);
1532
1533             if (priv->video_tee != NULL)
1534               {
1535                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1536                 gst_pad_link (pad, sink);
1537               }
1538           }
1539         break;
1540       default:
1541         g_assert_not_reached ();
1542     }
1543
1544 }
1545
1546 static void
1547 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1548 {
1549   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1550   GstElement *preview;
1551
1552   preview = empathy_video_widget_get_element (
1553     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1554
1555   gst_element_set_state (priv->video_input, GST_STATE_NULL);
1556   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1557   gst_element_set_state (preview, GST_STATE_NULL);
1558
1559   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1560     priv->video_tee, preview, NULL);
1561
1562   g_object_unref (priv->video_input);
1563   priv->video_input = NULL;
1564   g_object_unref (priv->video_tee);
1565   priv->video_tee = NULL;
1566   gtk_widget_destroy (priv->video_preview);
1567   priv->video_preview = NULL;
1568
1569   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1570   gtk_toggle_tool_button_set_active (
1571       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1572   gtk_widget_set_sensitive (priv->camera_button, FALSE);
1573   gtk_action_set_sensitive (priv->send_video, FALSE);
1574
1575   gtk_widget_show (priv->self_user_avatar_widget);
1576 }
1577
1578
1579 static gboolean
1580 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1581   gpointer user_data)
1582 {
1583   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1584   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1585   GstState newstate;
1586
1587   empathy_call_handler_bus_message (priv->handler, bus, message);
1588
1589   switch (GST_MESSAGE_TYPE (message))
1590     {
1591       case GST_MESSAGE_STATE_CHANGED:
1592         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1593           {
1594             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1595             if (newstate == GST_STATE_PAUSED)
1596                 empathy_call_window_setup_video_input (self);
1597           }
1598         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1599             !priv->call_started)
1600           {
1601             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1602             if (newstate == GST_STATE_PAUSED)
1603               {
1604                 priv->call_started = TRUE;
1605                 empathy_call_handler_start_call (priv->handler);
1606                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1607               }
1608           }
1609         break;
1610       case GST_MESSAGE_ERROR:
1611         {
1612           GError *error = NULL;
1613           GstElement *gst_error;
1614           gchar *debug;
1615
1616           gst_message_parse_error (message, &error, &debug);
1617           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1618
1619           g_message ("Element error: %s -- %s\n", error->message, debug);
1620
1621           if (g_str_has_prefix (gst_element_get_name (gst_error),
1622                 VIDEO_INPUT_ERROR_PREFIX))
1623             {
1624               /* Remove the video input and continue */
1625               if (priv->video_input != NULL)
1626                 empathy_call_window_remove_video_input (self);
1627               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1628             }
1629           else
1630             {
1631               empathy_call_window_disconnected (self);
1632             }
1633           g_error_free (error);
1634           g_free (debug);
1635         }
1636       default:
1637         break;
1638     }
1639
1640   return TRUE;
1641 }
1642
1643 static void
1644 empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
1645 {
1646   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1647
1648   if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1649     {
1650       if (priv->video_preview != NULL)
1651         {
1652           gtk_widget_hide (priv->self_user_avatar_widget);
1653           gtk_widget_show (priv->video_preview);
1654         }
1655       else
1656         {
1657           if (priv->video_preview != NULL)
1658             gtk_widget_hide (priv->video_preview);
1659
1660           gtk_widget_show (priv->self_user_avatar_widget);
1661         }
1662     }
1663 }
1664
1665 static void
1666 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1667     EmpathyCallWindow *window)
1668 {
1669   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1670
1671   if (empathy_tp_call_is_receiving_video (call))
1672     {
1673       gtk_widget_hide (priv->remote_user_avatar_widget);
1674       gtk_widget_show (priv->video_output);
1675     }
1676   else
1677     {
1678       gtk_widget_hide (priv->video_output);
1679       gtk_widget_show (priv->remote_user_avatar_widget);
1680     }
1681
1682   empathy_call_window_update_self_avatar_visibility (window);
1683 }
1684
1685 static void
1686 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1687 {
1688   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1689
1690   g_signal_connect (priv->handler, "conference-added",
1691     G_CALLBACK (empathy_call_window_conference_added_cb), window);
1692   g_signal_connect (priv->handler, "request-resource",
1693     G_CALLBACK (empathy_call_window_request_resource_cb), window);
1694   g_signal_connect (priv->handler, "closed",
1695     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1696   g_signal_connect (priv->handler, "src-pad-added",
1697     G_CALLBACK (empathy_call_window_src_added_cb), window);
1698   g_signal_connect (priv->handler, "sink-pad-added",
1699     G_CALLBACK (empathy_call_window_sink_added_cb), window);
1700
1701   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1702 }
1703
1704 static gboolean
1705 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1706   EmpathyCallWindow *window)
1707 {
1708   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1709
1710   if (priv->pipeline != NULL)
1711     {
1712       if (priv->bus_message_source_id != 0)
1713         {
1714           g_source_remove (priv->bus_message_source_id);
1715           priv->bus_message_source_id = 0;
1716         }
1717
1718       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1719     }
1720
1721   if (priv->call_state == CONNECTING)
1722     empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1723
1724   return FALSE;
1725 }
1726
1727 static void
1728 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1729 {
1730   GtkWidget *menu;
1731   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1732
1733   menu = gtk_ui_manager_get_widget (priv->ui_manager,
1734             "/menubar1");
1735
1736   if (set_fullscreen)
1737     {
1738       gtk_widget_hide (priv->sidebar);
1739       gtk_widget_hide (menu);
1740       gtk_widget_hide (priv->vbox);
1741       gtk_widget_hide (priv->statusbar);
1742       gtk_widget_hide (priv->toolbar);
1743     }
1744   else
1745     {
1746       if (priv->sidebar_was_visible_before_fs)
1747         gtk_widget_show (priv->sidebar);
1748
1749       gtk_widget_show (menu);
1750       gtk_widget_show (priv->vbox);
1751       gtk_widget_show (priv->statusbar);
1752       gtk_widget_show (priv->toolbar);
1753
1754       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1755           priv->original_height_before_fs);
1756     }
1757 }
1758
1759 static void
1760 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1761 {
1762   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1763
1764   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1765       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1766   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1767       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1768   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1769       priv->video_output, TRUE, TRUE,
1770       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1771       GTK_PACK_START);
1772   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1773       priv->vbox, TRUE, TRUE,
1774       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1775       GTK_PACK_START);
1776 }
1777
1778 static gboolean
1779 empathy_call_window_state_event_cb (GtkWidget *widget,
1780   GdkEventWindowState *event, EmpathyCallWindow *window)
1781 {
1782   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1783     {
1784       EmpathyCallWindowPriv *priv = GET_PRIV (window);
1785       gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1786
1787       if (set_fullscreen)
1788         {
1789           gboolean sidebar_was_visible;
1790           gint original_width = GTK_WIDGET (window)->allocation.width;
1791           gint original_height = GTK_WIDGET (window)->allocation.height;
1792
1793           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1794
1795           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1796           priv->original_width_before_fs = original_width;
1797           priv->original_height_before_fs = original_height;
1798
1799           if (priv->video_output_motion_handler_id == 0 &&
1800                 priv->video_output != NULL)
1801             {
1802               priv->video_output_motion_handler_id = g_signal_connect (
1803                   G_OBJECT (priv->video_output), "motion-notify-event",
1804                   G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1805             }
1806         }
1807       else
1808         {
1809           if (priv->video_output_motion_handler_id != 0)
1810             {
1811               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1812                   priv->video_output_motion_handler_id);
1813               priv->video_output_motion_handler_id = 0;
1814             }
1815         }
1816
1817       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1818           set_fullscreen);
1819       show_controls (window, set_fullscreen);
1820       show_borders (window, set_fullscreen);
1821       gtk_action_set_stock_id (priv->menu_fullscreen,
1822           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1823       priv->is_fullscreen = set_fullscreen;
1824   }
1825
1826   return FALSE;
1827 }
1828
1829 static void
1830 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1831   EmpathyCallWindow *window)
1832 {
1833   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1834   GtkWidget *arrow;
1835   int w,h, handle_size;
1836
1837   w = GTK_WIDGET (window)->allocation.width;
1838   h = GTK_WIDGET (window)->allocation.height;
1839
1840   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1841
1842   if (gtk_toggle_button_get_active (toggle))
1843     {
1844       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1845       gtk_widget_show (priv->sidebar);
1846       w += priv->sidebar->allocation.width + handle_size;
1847     }
1848   else
1849     {
1850       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1851       w -= priv->sidebar->allocation.width + handle_size;
1852       gtk_widget_hide (priv->sidebar);
1853     }
1854
1855   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1856
1857   if (w > 0 && h > 0)
1858     gtk_window_resize (GTK_WINDOW (window), w, h);
1859 }
1860
1861 static void
1862 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1863   gboolean send)
1864 {
1865   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1866   EmpathyTpCall *call;
1867
1868   priv->sending_video = send;
1869
1870   /* When we start sending video, we want to show the video preview by
1871      default. */
1872   if (send)
1873     {
1874       empathy_call_window_setup_video_preview (window);
1875       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1876           TRUE);
1877     }
1878
1879   g_object_get (priv->handler, "tp-call", &call, NULL);
1880   empathy_tp_call_request_video_stream_direction (call, send);
1881   g_object_unref (call);
1882 }
1883
1884 static void
1885 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1886   EmpathyCallWindow *window)
1887 {
1888   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1889   gboolean active;
1890
1891   if (priv->call_state != CONNECTED)
1892     return;
1893
1894   active = (gtk_toggle_tool_button_get_active (toggle));
1895
1896   if (priv->sending_video == active)
1897     return;
1898
1899   empathy_call_window_set_send_video (window, active);
1900   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1901 }
1902
1903 static void
1904 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1905   EmpathyCallWindow *window)
1906 {
1907   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1908   gboolean active;
1909
1910   if (priv->call_state != CONNECTED)
1911     return;
1912
1913   active = (gtk_toggle_action_get_active (toggle));
1914
1915   if (priv->sending_video == active)
1916     return;
1917
1918   empathy_call_window_set_send_video (window, active);
1919   gtk_toggle_tool_button_set_active (
1920       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1921 }
1922
1923 static void
1924 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1925   EmpathyCallWindow *window)
1926 {
1927   gboolean show_preview_toggled;
1928   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1929
1930   show_preview_toggled = gtk_toggle_action_get_active (toggle);
1931
1932   if (show_preview_toggled)
1933     {
1934       empathy_call_window_setup_video_preview (window);
1935       gtk_widget_show (priv->self_user_output_frame);
1936       empathy_call_window_update_self_avatar_visibility (window);
1937     }
1938   else
1939     {
1940       gtk_widget_hide (priv->self_user_output_frame);
1941     }
1942 }
1943
1944 static void
1945 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1946   EmpathyCallWindow *window)
1947 {
1948   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1949   gboolean active;
1950
1951   if (priv->audio_input == NULL)
1952     return;
1953
1954   active = (gtk_toggle_tool_button_get_active (toggle));
1955
1956   if (active)
1957     {
1958       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1959         priv->volume);
1960       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1961     }
1962   else
1963     {
1964       /* TODO, Instead of setting the input volume to 0 we should probably
1965        * stop sending but this would cause the audio call to drop if both
1966        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1967        * in the future. GNOME #574574
1968        */
1969       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1970         0);
1971       gtk_adjustment_set_value (priv->audio_input_adj, 0);
1972     }
1973 }
1974
1975 static void
1976 empathy_call_window_sidebar_hidden_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     FALSE);
1983 }
1984
1985 static void
1986 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1987   EmpathyCallWindow *window)
1988 {
1989   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1990
1991   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1992     TRUE);
1993 }
1994
1995 static void
1996 empathy_call_window_hangup_cb (gpointer object,
1997                                EmpathyCallWindow *window)
1998 {
1999   if (empathy_call_window_disconnected (window))
2000     gtk_widget_destroy (GTK_WIDGET (window));
2001 }
2002
2003 static void
2004 empathy_call_window_restart_call (EmpathyCallWindow *window)
2005 {
2006   GstBus *bus;
2007   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2008
2009   gtk_widget_destroy (priv->remote_user_output_hbox);
2010   gtk_widget_destroy (priv->self_user_output_hbox);
2011
2012   priv->pipeline = gst_pipeline_new (NULL);
2013   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2014   priv->bus_message_source_id = gst_bus_add_watch (bus,
2015       empathy_call_window_bus_message, window);
2016
2017   empathy_call_window_setup_remote_frame (bus, window);
2018   empathy_call_window_setup_self_frame (bus, window);
2019
2020   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2021       G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2022
2023   /* While the call was disconnected, the input volume might have changed.
2024    * However, since the audio_input source was destroyed, its volume has not
2025    * been updated during that time. That's why we manually update it here */
2026   empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2027
2028   g_object_unref (bus);
2029
2030   gtk_widget_show_all (priv->content_hbox);
2031
2032   if (!empathy_call_handler_has_initial_video (priv->handler))
2033     gtk_widget_hide (priv->self_user_output_frame);
2034
2035   priv->outgoing = TRUE;
2036   empathy_call_window_set_state_connecting (window);
2037
2038   priv->call_started = TRUE;
2039   empathy_call_handler_start_call (priv->handler);
2040   empathy_call_window_setup_avatars (window, priv->handler);
2041   gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2042
2043   gtk_action_set_sensitive (priv->redial, FALSE);
2044   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2045 }
2046
2047 static void
2048 empathy_call_window_redial_cb (gpointer object,
2049     EmpathyCallWindow *window)
2050 {
2051   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2052
2053   if (priv->call_state == CONNECTED)
2054     priv->call_state = REDIALING;
2055
2056   empathy_call_handler_stop_call (priv->handler);
2057
2058   if (priv->call_state != CONNECTED)
2059     empathy_call_window_restart_call (window);
2060 }
2061
2062 static void
2063 empathy_call_window_fullscreen_cb (gpointer object,
2064                                    EmpathyCallWindow *window)
2065 {
2066   empathy_call_window_fullscreen_toggle (window);
2067 }
2068
2069 static void
2070 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2071 {
2072   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2073
2074   if (priv->is_fullscreen)
2075     gtk_window_unfullscreen (GTK_WINDOW (window));
2076   else
2077     gtk_window_fullscreen (GTK_WINDOW (window));
2078 }
2079
2080 static gboolean
2081 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2082   GdkEventButton *event, EmpathyCallWindow *window)
2083 {
2084   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2085     {
2086       empathy_call_window_video_menu_popup (window, event->button);
2087       return TRUE;
2088     }
2089
2090   return FALSE;
2091 }
2092
2093 static gboolean
2094 empathy_call_window_key_press_cb (GtkWidget *video_output,
2095   GdkEventKey *event, EmpathyCallWindow *window)
2096 {
2097   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2098
2099   if (priv->is_fullscreen && event->keyval == GDK_Escape)
2100     {
2101       /* Since we are in fullscreen mode, toggling will bring us back to
2102          normal mode. */
2103       empathy_call_window_fullscreen_toggle (window);
2104       return TRUE;
2105     }
2106
2107   return FALSE;
2108 }
2109
2110 static gboolean
2111 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2112     GdkEventMotion *event, EmpathyCallWindow *window)
2113 {
2114   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2115
2116   if (priv->is_fullscreen)
2117     {
2118       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2119       return TRUE;
2120     }
2121   return FALSE;
2122 }
2123
2124 static void
2125 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2126   guint button)
2127 {
2128   GtkWidget *menu;
2129   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2130
2131   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2132             "/video-popup");
2133   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2134       button, gtk_get_current_event_time ());
2135   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2136 }
2137
2138 static void
2139 empathy_call_window_status_message (EmpathyCallWindow *window,
2140   gchar *message)
2141 {
2142   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2143
2144   if (priv->context_id == 0)
2145     {
2146       priv->context_id = gtk_statusbar_get_context_id (
2147         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2148     }
2149   else
2150     {
2151       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2152     }
2153
2154   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2155     message);
2156 }
2157
2158 static void
2159 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2160   gdouble value, EmpathyCallWindow *window)
2161 {
2162   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2163
2164   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2165     value);
2166 }