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