]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
The video preview is no longer hidden when we get connected to a audio
[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   GstElement *preview;
615   GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
616
617   if (priv->video_preview != NULL)
618     {
619       /* Since the video preview and the video tee are initialized and freed
620          at the same time, if one is initialized, then the other one should
621          be too. */
622       g_assert (priv->video_tee != NULL);
623       return;
624     }
625
626   g_assert (priv->video_tee == NULL);
627
628   priv->video_tee = gst_element_factory_make ("tee", NULL);
629   gst_object_ref (priv->video_tee);
630   gst_object_sink (priv->video_tee);
631
632   priv->video_preview = empathy_video_widget_new_with_size (bus,
633       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
634   g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
635   gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
636       priv->video_preview, TRUE, TRUE, 0);
637
638   preview = empathy_video_widget_get_element (
639       EMPATHY_VIDEO_WIDGET (priv->video_preview));
640   gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
641       priv->video_tee, preview, NULL);
642   gst_element_link_many (priv->video_input, priv->video_tee,
643       preview, NULL);
644
645   g_object_unref (bus);
646
647   gst_element_set_state (preview, GST_STATE_PLAYING);
648   gst_element_set_state (priv->video_input, GST_STATE_PLAYING);
649   gst_element_set_state (priv->video_tee, GST_STATE_PLAYING);
650 }
651
652 static void
653 empathy_call_window_init (EmpathyCallWindow *self)
654 {
655   EmpathyCallWindowPriv *priv = GET_PRIV (self);
656   GtkBuilder *gui;
657   GtkWidget *top_vbox;
658   GtkWidget *h;
659   GtkWidget *arrow;
660   GtkWidget *page;
661   GstBus *bus;
662   gchar *filename;
663
664   filename = empathy_file_lookup ("empathy-call-window.ui", "src");
665   gui = empathy_builder_get_file (filename,
666     "call_window_vbox", &top_vbox,
667     "pane", &priv->pane,
668     "statusbar", &priv->statusbar,
669     "redial", &priv->redial_button,
670     "microphone", &priv->mic_button,
671     "camera", &priv->camera_button,
672     "toolbar", &priv->toolbar,
673     "send_video", &priv->send_video,
674     "menuredial", &priv->redial,
675     "show_preview", &priv->show_preview,
676     "ui_manager", &priv->ui_manager,
677     "menufullscreen", &priv->menu_fullscreen,
678     NULL);
679
680   empathy_builder_connect (gui, self,
681     "menuhangup", "activate", empathy_call_window_hangup_cb,
682     "hangup", "clicked", empathy_call_window_hangup_cb,
683     "menuredial", "activate", empathy_call_window_redial_cb,
684     "redial", "clicked", empathy_call_window_redial_cb,
685     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
686     "camera", "toggled", empathy_call_window_camera_toggled_cb,
687     "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
688     "show_preview", "toggled", empathy_call_window_show_preview_toggled_cb,
689     "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
690     NULL);
691
692   priv->lock = g_mutex_new ();
693
694   gtk_container_add (GTK_CONTAINER (self), top_vbox);
695
696   empathy_call_window_setup_toolbar (self);
697
698   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
699   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
700                                   CONTENT_HBOX_BORDER_WIDTH);
701   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
702
703   priv->pipeline = gst_pipeline_new (NULL);
704   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
705   gst_bus_add_watch (bus, empathy_call_window_bus_message, self);
706
707   priv->remote_user_output_frame = gtk_frame_new (NULL);
708   gtk_widget_set_size_request (priv->remote_user_output_frame,
709       EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
710   gtk_box_pack_start (GTK_BOX (priv->content_hbox),
711       priv->remote_user_output_frame, TRUE, TRUE,
712       CONTENT_HBOX_CHILDREN_PACKING_PADDING);
713   empathy_call_window_setup_remote_frame (bus, self);
714
715   priv->self_user_output_frame = gtk_frame_new (NULL);
716   gtk_widget_set_size_request (priv->self_user_output_frame,
717       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
718
719   priv->vbox = gtk_vbox_new (FALSE, 3);
720   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
721       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
722   gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame, FALSE,
723       FALSE, 0);
724   empathy_call_window_setup_self_frame (bus, self);
725
726   g_object_unref (bus);
727
728   priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
729   arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
730   g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
731     G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
732
733   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
734
735   h = gtk_hbox_new (FALSE, 3);
736   gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
737   gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
738
739   priv->sidebar = empathy_sidebar_new ();
740   g_signal_connect (G_OBJECT (priv->sidebar),
741     "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
742   g_signal_connect (G_OBJECT (priv->sidebar),
743     "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
744   gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
745
746   priv->dtmf_panel = empathy_call_window_create_dtmf (self);
747   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
748     priv->dtmf_panel);
749
750   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
751
752   page = empathy_call_window_create_audio_input (self);
753   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
754     page);
755
756   page = empathy_call_window_create_video_input (self);
757   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
758     page);
759
760   gtk_widget_show_all (top_vbox);
761
762   gtk_widget_hide (priv->sidebar);
763
764   priv->fullscreen = empathy_call_window_fullscreen_new (self);
765   empathy_call_window_fullscreen_set_video_widget (priv->fullscreen, priv->video_output);
766   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
767       "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
768
769   g_signal_connect (G_OBJECT (self), "realize",
770     G_CALLBACK (empathy_call_window_realized_cb), self);
771
772   g_signal_connect (G_OBJECT (self), "delete-event",
773     G_CALLBACK (empathy_call_window_delete_cb), self);
774
775   g_signal_connect (G_OBJECT (self), "window-state-event",
776     G_CALLBACK (empathy_call_window_state_event_cb), self);
777
778   g_signal_connect (G_OBJECT (self), "key-press-event",
779       G_CALLBACK (empathy_call_window_key_press_cb), self);
780
781   empathy_call_window_status_message (self, CONNECTING_STATUS_TEXT);
782
783   priv->timer = g_timer_new ();
784
785   g_object_ref (priv->ui_manager);
786   g_object_unref (gui);
787   g_free (filename);
788 }
789
790 /* Instead of specifying a width and a height, we specify only one size. That's
791    because we want a square avatar icon.  */
792 static void
793 init_contact_avatar_with_size (EmpathyContact *contact, GtkWidget *image_widget,
794     gint size)
795 {
796
797   GdkPixbuf *pixbuf_avatar = NULL;
798
799   if (contact != NULL)
800     {
801       pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
802         size, size);
803     }
804
805   if (pixbuf_avatar == NULL)
806     {
807       pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
808           size);
809     }
810
811   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
812 }
813
814 static void
815 set_window_title (EmpathyCallWindow *self)
816 {
817   EmpathyCallWindowPriv *priv = GET_PRIV (self);
818   gchar *tmp;
819
820   tmp = g_strdup_printf (_("Call with %s"),
821       empathy_contact_get_name (priv->contact));
822   gtk_window_set_title (GTK_WINDOW (self), tmp);
823   g_free (tmp);
824 }
825
826 static void
827 contact_name_changed_cb (EmpathyContact *contact,
828     GParamSpec *pspec, EmpathyCallWindow *self)
829 {
830   set_window_title (self);
831 }
832
833 static void
834 contact_avatar_changed_cb (EmpathyContact *contact,
835     GParamSpec *pspec, GtkWidget *avatar_widget)
836 {
837   init_contact_avatar_with_size (contact, avatar_widget,
838       avatar_widget->allocation.height);
839 }
840
841 static void
842 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
843     EmpathyContact *contact, const GError *error, gpointer user_data,
844     GObject *weak_object)
845 {
846   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
847   EmpathyCallWindowPriv *priv = GET_PRIV (self);
848
849   init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
850       MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
851
852   g_signal_connect (contact, "notify::avatar",
853       G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
854 }
855
856 static void
857 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
858     EmpathyCallHandler *handler)
859 {
860   EmpathyCallWindowPriv *priv = GET_PRIV (self);
861
862   g_object_get (handler, "contact", &(priv->contact), NULL);
863
864   if (priv->contact != NULL)
865     {
866       TpConnection *connection;
867       EmpathyTpContactFactory *factory;
868
869       set_window_title (self);
870
871       g_signal_connect (priv->contact, "notify::name",
872           G_CALLBACK (contact_name_changed_cb), self);
873       g_signal_connect (priv->contact, "notify::avatar",
874           G_CALLBACK (contact_avatar_changed_cb),
875           priv->remote_user_avatar_widget);
876
877       /* Retreiving the self avatar */
878       connection = empathy_contact_get_connection (priv->contact);
879       factory = empathy_tp_contact_factory_dup_singleton (connection);
880       empathy_tp_contact_factory_get_from_handle (factory,
881           tp_connection_get_self_handle (connection),
882           empathy_call_window_got_self_contact_cb, self, NULL, NULL);
883
884       g_object_unref (factory);
885     }
886   else
887     {
888       g_warning ("call handler doesn't have a contact");
889       gtk_window_set_title (GTK_WINDOW (self), _("Call"));
890
891       /* Since we can't access the remote contact, we can't get a connection
892          to it and can't get the self contact (and its avatar). This means
893          that we have to manually set the self avatar. */
894       init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
895           MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
896     }
897
898   init_contact_avatar_with_size (priv->contact,
899       priv->remote_user_avatar_widget, MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
900       REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
901
902   /* The remote avatar is shown by default and will be hidden when we receive
903      video from the remote side. */
904   gtk_widget_hide (priv->video_output);
905   gtk_widget_show (priv->remote_user_avatar_widget);
906 }
907
908 static void
909 empathy_call_window_setup_video_preview_visibility (EmpathyCallWindow *self,
910     EmpathyCallHandler *handler)
911 {
912   EmpathyCallWindowPriv *priv = GET_PRIV (self);
913   gboolean initial_video = empathy_call_handler_has_initial_video (priv->handler);
914
915   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
916       initial_video);
917 }
918
919 static void
920 empathy_call_window_constructed (GObject *object)
921 {
922   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
923   EmpathyCallWindowPriv *priv = GET_PRIV (self);
924
925   g_assert (priv->handler != NULL);
926   empathy_call_window_setup_avatars (self, priv->handler);
927   empathy_call_window_setup_video_preview_visibility (self, priv->handler);
928 }
929
930 static void empathy_call_window_dispose (GObject *object);
931 static void empathy_call_window_finalize (GObject *object);
932
933 static void
934 empathy_call_window_set_property (GObject *object,
935   guint property_id, const GValue *value, GParamSpec *pspec)
936 {
937   EmpathyCallWindowPriv *priv = GET_PRIV (object);
938
939   switch (property_id)
940     {
941       case PROP_CALL_HANDLER:
942         priv->handler = g_value_dup_object (value);
943         break;
944       default:
945         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
946     }
947 }
948
949 static void
950 empathy_call_window_get_property (GObject *object,
951   guint property_id, GValue *value, GParamSpec *pspec)
952 {
953   EmpathyCallWindowPriv *priv = GET_PRIV (object);
954
955   switch (property_id)
956     {
957       case PROP_CALL_HANDLER:
958         g_value_set_object (value, priv->handler);
959         break;
960       default:
961         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
962     }
963 }
964
965 static void
966 empathy_call_window_class_init (
967   EmpathyCallWindowClass *empathy_call_window_class)
968 {
969   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
970   GParamSpec *param_spec;
971
972   g_type_class_add_private (empathy_call_window_class,
973     sizeof (EmpathyCallWindowPriv));
974
975   object_class->constructed = empathy_call_window_constructed;
976   object_class->set_property = empathy_call_window_set_property;
977   object_class->get_property = empathy_call_window_get_property;
978
979   object_class->dispose = empathy_call_window_dispose;
980   object_class->finalize = empathy_call_window_finalize;
981
982   param_spec = g_param_spec_object ("handler",
983     "handler", "The call handler",
984     EMPATHY_TYPE_CALL_HANDLER,
985     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
986   g_object_class_install_property (object_class,
987     PROP_CALL_HANDLER, param_spec);
988
989 }
990
991 static void
992 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
993     GParamSpec *property, EmpathyCallWindow *self)
994 {
995   empathy_call_window_update_avatars_visibility (call, self);
996 }
997
998 void
999 empathy_call_window_dispose (GObject *object)
1000 {
1001   EmpathyTpCall *call;
1002   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1003   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1004
1005   if (priv->dispose_has_run)
1006     return;
1007
1008   priv->dispose_has_run = TRUE;
1009
1010   g_object_get (priv->handler, "tp-call", &call, NULL);
1011
1012   if (call != NULL)
1013     {
1014       g_signal_handlers_disconnect_by_func (call,
1015         empathy_call_window_video_stream_changed_cb, object);
1016     }
1017
1018   g_object_unref (call);
1019
1020   if (priv->handler != NULL)
1021     g_object_unref (priv->handler);
1022
1023   priv->handler = NULL;
1024
1025   if (priv->pipeline != NULL)
1026     g_object_unref (priv->pipeline);
1027   priv->pipeline = NULL;
1028
1029   if (priv->video_input != NULL)
1030     g_object_unref (priv->video_input);
1031   priv->video_input = NULL;
1032
1033   if (priv->audio_input != NULL)
1034     g_object_unref (priv->audio_input);
1035   priv->audio_input = NULL;
1036
1037   if (priv->audio_output != NULL)
1038     g_object_unref (priv->audio_output);
1039   priv->audio_output = NULL;
1040
1041   if (priv->video_tee != NULL)
1042     g_object_unref (priv->video_tee);
1043   priv->video_tee = NULL;
1044
1045   if (priv->timer_id != 0)
1046     g_source_remove (priv->timer_id);
1047   priv->timer_id = 0;
1048
1049   if (priv->ui_manager != NULL)
1050     g_object_unref (priv->ui_manager);
1051   priv->ui_manager = NULL;
1052
1053   if (priv->contact != NULL)
1054     {
1055       g_signal_handlers_disconnect_by_func (priv->contact,
1056           contact_name_changed_cb, self);
1057       g_object_unref (priv->contact);
1058       priv->contact = NULL;
1059     }
1060
1061   /* release any references held by the object here */
1062   if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1063     G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1064 }
1065
1066 void
1067 empathy_call_window_finalize (GObject *object)
1068 {
1069   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1070   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1071
1072   if (priv->video_output_motion_handler_id != 0)
1073     {
1074       g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1075           priv->video_output_motion_handler_id);
1076       priv->video_output_motion_handler_id = 0;
1077     }
1078
1079   /* free any data held directly by the object here */
1080   g_mutex_free (priv->lock);
1081
1082   g_timer_destroy (priv->timer);
1083
1084   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1085 }
1086
1087
1088 EmpathyCallWindow *
1089 empathy_call_window_new (EmpathyCallHandler *handler)
1090 {
1091   return EMPATHY_CALL_WINDOW (
1092     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1093 }
1094
1095 static void
1096 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1097   GstElement *conference, gpointer user_data)
1098 {
1099   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1100   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1101
1102   gst_bin_add (GST_BIN (priv->pipeline), conference);
1103
1104   gst_element_set_state (conference, GST_STATE_PLAYING);
1105 }
1106
1107 static gboolean
1108 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1109   FsMediaType type, FsStreamDirection direction, gpointer user_data)
1110 {
1111   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1112   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1113
1114   if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1115     return TRUE;
1116
1117   if (direction == FS_DIRECTION_RECV)
1118     return TRUE;
1119
1120   /* video and direction is send */
1121   return priv->video_input != NULL;
1122 }
1123
1124 static gboolean
1125 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1126 {
1127   GstStateChangeReturn state_change_return;
1128   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1129
1130   if (priv->pipeline == NULL)
1131     return TRUE;
1132
1133   state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1134
1135   if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1136         state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1137     {
1138       if (priv->pipeline != NULL)
1139         g_object_unref (priv->pipeline);
1140       priv->pipeline = NULL;
1141
1142       if (priv->video_input != NULL)
1143         g_object_unref (priv->video_input);
1144       priv->video_input = NULL;
1145
1146       if (priv->audio_input != NULL)
1147         g_object_unref (priv->audio_input);
1148       priv->audio_input = NULL;
1149
1150       if (priv->audio_output != NULL)
1151         g_object_unref (priv->audio_output);
1152       priv->audio_output = NULL;
1153
1154       if (priv->video_tee != NULL)
1155         g_object_unref (priv->video_tee);
1156       priv->video_tee = NULL;
1157
1158       if (priv->video_preview != NULL)
1159         gtk_widget_destroy (priv->video_preview);
1160       priv->video_preview = NULL;
1161
1162       priv->liveadder = NULL;
1163       priv->funnel = NULL;
1164
1165       return TRUE;
1166     }
1167   else
1168     {
1169       g_message ("Error: could not destroy pipeline. Closing call window");
1170       gtk_widget_destroy (GTK_WIDGET (self));
1171
1172       return FALSE;
1173     }
1174 }
1175
1176 static gboolean
1177 empathy_call_window_disconnected (EmpathyCallWindow *self)
1178 {
1179   gboolean could_disconnect = FALSE;
1180   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1181   gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1182
1183   if (could_reset_pipeline)
1184     {
1185       g_mutex_lock (priv->lock);
1186
1187       g_timer_stop (priv->timer);
1188
1189       if (priv->timer_id != 0)
1190         g_source_remove (priv->timer_id);
1191       priv->timer_id = 0;
1192
1193       g_mutex_unlock (priv->lock);
1194
1195       empathy_call_window_status_message (self, _("Disconnected"));
1196
1197       gtk_action_set_sensitive (priv->redial, TRUE);
1198       gtk_widget_set_sensitive (priv->redial_button, TRUE);
1199       gtk_widget_set_sensitive (priv->camera_button, FALSE);
1200       gtk_action_set_sensitive (priv->send_video, FALSE);
1201       priv->sending_video = FALSE;
1202       priv->connected = FALSE;
1203       priv->call_started = FALSE;
1204
1205       could_disconnect = TRUE;
1206     }
1207
1208   return could_disconnect;
1209 }
1210
1211
1212 static void
1213 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
1214 {
1215   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1216   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1217
1218   if (empathy_call_window_disconnected (self) && priv->redialing)
1219     {
1220       empathy_call_window_restart_call (self);
1221       priv->redialing = FALSE;
1222     }
1223 }
1224
1225 /* Called with global lock held */
1226 static GstPad *
1227 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1228 {
1229   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1230   GstPad *pad;
1231
1232   if (priv->funnel == NULL)
1233     {
1234       GstElement *output;
1235
1236       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1237         (priv->video_output));
1238
1239       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1240
1241       gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1242       gst_bin_add (GST_BIN (priv->pipeline), output);
1243
1244       gst_element_link (priv->funnel, output);
1245
1246       gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1247       gst_element_set_state (output, GST_STATE_PLAYING);
1248     }
1249
1250   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1251
1252   return pad;
1253 }
1254
1255 /* Called with global lock held */
1256 static GstPad *
1257 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1258 {
1259   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1260   GstPad *pad;
1261
1262   if (priv->liveadder == NULL)
1263     {
1264       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1265
1266       gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1267       gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1268
1269       gst_element_link (priv->liveadder, priv->audio_output);
1270
1271       gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1272       gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1273     }
1274
1275   pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1276
1277   return pad;
1278 }
1279
1280 static gboolean
1281 empathy_call_window_update_timer (gpointer user_data)
1282 {
1283   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1284   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1285   gchar *str;
1286   gdouble time;
1287
1288   time = g_timer_elapsed (priv->timer, NULL);
1289
1290   /* Translators: number of minutes:seconds the caller has been connected */
1291   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time / 60,
1292     (int) time % 60);
1293   empathy_call_window_status_message (self, str);
1294   g_free (str);
1295
1296   return TRUE;
1297 }
1298
1299 static gboolean
1300 empathy_call_window_connected (gpointer user_data)
1301 {
1302   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1303   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1304   EmpathyTpCall *call;
1305
1306   g_object_get (priv->handler, "tp-call", &call, NULL);
1307
1308   g_signal_connect (call, "notify::video-stream",
1309     G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1310
1311   if (empathy_tp_call_has_dtmf (call))
1312     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1313
1314   if (priv->video_input == NULL)
1315       empathy_call_window_set_send_video (self, FALSE);
1316
1317   priv->sending_video = empathy_tp_call_is_sending_video (call);
1318
1319   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1320       priv->sending_video
1321       || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
1322   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1323       priv->sending_video && priv->video_input != NULL);
1324   gtk_toggle_tool_button_set_active (
1325       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1326       priv->sending_video && priv->video_input != NULL);
1327   gtk_widget_set_sensitive (priv->camera_button, priv->video_input != NULL);
1328   gtk_action_set_sensitive (priv->send_video, priv->video_input != NULL);
1329
1330   gtk_action_set_sensitive (priv->redial, FALSE);
1331   gtk_widget_set_sensitive (priv->redial_button, FALSE);
1332
1333   empathy_call_window_update_avatars_visibility (call, self);
1334
1335   g_object_unref (call);
1336
1337   g_mutex_lock (priv->lock);
1338
1339   priv->timer_id = g_timeout_add_seconds (1,
1340     empathy_call_window_update_timer, self);
1341
1342   g_mutex_unlock (priv->lock);
1343
1344   empathy_call_window_update_timer (self);
1345
1346   return FALSE;
1347 }
1348
1349
1350 /* Called from the streaming thread */
1351 static void
1352 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1353   GstPad *src, guint media_type, gpointer user_data)
1354 {
1355   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1356   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1357
1358   GstPad *pad;
1359
1360   g_mutex_lock (priv->lock);
1361
1362   if (priv->connected == FALSE)
1363     {
1364       g_timer_start (priv->timer);
1365       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
1366       priv->connected = TRUE;
1367     }
1368
1369   switch (media_type)
1370     {
1371       case TP_MEDIA_STREAM_TYPE_AUDIO:
1372         pad = empathy_call_window_get_audio_sink_pad (self);
1373         break;
1374       case TP_MEDIA_STREAM_TYPE_VIDEO:
1375         gtk_widget_hide (priv->remote_user_avatar_widget);
1376         gtk_widget_show (priv->video_output);
1377         pad = empathy_call_window_get_video_sink_pad (self);
1378         break;
1379       default:
1380         g_assert_not_reached ();
1381     }
1382
1383   gst_pad_link (src, pad);
1384   gst_object_unref (pad);
1385
1386   g_mutex_unlock (priv->lock);
1387 }
1388
1389 /* Called from the streaming thread */
1390 static void
1391 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1392   GstPad *sink, guint media_type, gpointer user_data)
1393 {
1394   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1395   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1396   GstPad *pad;
1397
1398   switch (media_type)
1399     {
1400       case TP_MEDIA_STREAM_TYPE_AUDIO:
1401         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1402
1403         pad = gst_element_get_static_pad (priv->audio_input, "src");
1404         gst_pad_link (pad, sink);
1405
1406         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1407         break;
1408       case TP_MEDIA_STREAM_TYPE_VIDEO:
1409         if (priv->video_input != NULL)
1410           {
1411             EmpathyTpCall *call;
1412             g_object_get (priv->handler, "tp-call", &call, NULL);
1413
1414             if (empathy_tp_call_is_sending_video (call))
1415               {
1416                 empathy_call_window_setup_video_preview (self);
1417
1418                 gtk_toggle_action_set_active (
1419                     GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1420
1421                 if (priv->video_preview != NULL)
1422                   gtk_widget_show (priv->video_preview);
1423                 gtk_widget_hide (priv->self_user_avatar_widget);
1424               }
1425
1426             g_object_unref (call);
1427
1428             if (priv->video_tee != NULL)
1429               {
1430                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1431                 gst_pad_link (pad, sink);
1432               }
1433           }
1434         break;
1435       default:
1436         g_assert_not_reached ();
1437     }
1438
1439 }
1440
1441 static void
1442 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1443 {
1444   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1445   GstElement *preview;
1446
1447   preview = empathy_video_widget_get_element (
1448     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1449
1450   gst_element_set_state (priv->video_input, GST_STATE_NULL);
1451   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1452   gst_element_set_state (preview, GST_STATE_NULL);
1453
1454   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1455     priv->video_tee, preview, NULL);
1456
1457   g_object_unref (priv->video_input);
1458   priv->video_input = NULL;
1459   g_object_unref (priv->video_tee);
1460   priv->video_tee = NULL;
1461   gtk_widget_destroy (priv->video_preview);
1462   priv->video_preview = NULL;
1463
1464   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1465   gtk_toggle_tool_button_set_active (
1466       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1467   gtk_widget_set_sensitive (priv->camera_button, FALSE);
1468   gtk_action_set_sensitive (priv->send_video, FALSE);
1469
1470   gtk_widget_show (priv->self_user_avatar_widget);
1471 }
1472
1473
1474 static gboolean
1475 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1476   gpointer user_data)
1477 {
1478   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1479   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1480   GstState newstate;
1481
1482   empathy_call_handler_bus_message (priv->handler, bus, message);
1483
1484   switch (GST_MESSAGE_TYPE (message))
1485     {
1486       case GST_MESSAGE_STATE_CHANGED:
1487         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1488           {
1489             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1490             if (newstate == GST_STATE_PAUSED)
1491                 empathy_call_window_setup_video_input (self);
1492           }
1493         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1494             !priv->call_started)
1495           {
1496             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1497             if (newstate == GST_STATE_PAUSED)
1498               {
1499                 priv->call_started = TRUE;
1500                 empathy_call_handler_start_call (priv->handler);
1501                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1502               }
1503           }
1504         break;
1505       case GST_MESSAGE_ERROR:
1506         {
1507           GError *error = NULL;
1508           GstElement *gst_error;
1509           gchar *debug;
1510
1511           gst_message_parse_error (message, &error, &debug);
1512           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1513
1514           g_message ("Element error: %s -- %s\n", error->message, debug);
1515
1516           if (g_str_has_prefix (gst_element_get_name (gst_error),
1517                 VIDEO_INPUT_ERROR_PREFIX))
1518             {
1519               /* Remove the video input and continue */
1520               if (priv->video_input != NULL)
1521                 empathy_call_window_remove_video_input (self);
1522               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1523             }
1524           else
1525             {
1526               empathy_call_window_disconnected (self);
1527             }
1528           g_error_free (error);
1529           g_free (debug);
1530         }
1531       default:
1532         break;
1533     }
1534
1535   return TRUE;
1536 }
1537
1538 static void
1539 empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
1540 {
1541   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1542
1543   if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1544     {
1545       if (priv->video_preview != NULL)
1546         {
1547           gtk_widget_hide (priv->self_user_avatar_widget);
1548           gtk_widget_show (priv->video_preview);
1549         }
1550       else
1551         {
1552           if (priv->video_preview != NULL)
1553             gtk_widget_hide (priv->video_preview);
1554
1555           gtk_widget_show (priv->self_user_avatar_widget);
1556         }
1557     }
1558 }
1559
1560 static void
1561 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1562     EmpathyCallWindow *window)
1563 {
1564   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1565
1566   if (empathy_tp_call_is_receiving_video (call))
1567     {
1568       gtk_widget_hide (priv->remote_user_avatar_widget);
1569       gtk_widget_show (priv->video_output);
1570     }
1571   else
1572     {
1573       gtk_widget_hide (priv->video_output);
1574       gtk_widget_show (priv->remote_user_avatar_widget);
1575     }
1576
1577   empathy_call_window_update_self_avatar_visibility (window);
1578 }
1579
1580 static void
1581 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1582 {
1583   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1584
1585   g_signal_connect (priv->handler, "conference-added",
1586     G_CALLBACK (empathy_call_window_conference_added_cb), window);
1587   g_signal_connect (priv->handler, "request-resource",
1588     G_CALLBACK (empathy_call_window_request_resource_cb), window);
1589   g_signal_connect (priv->handler, "closed",
1590     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1591   g_signal_connect (priv->handler, "src-pad-added",
1592     G_CALLBACK (empathy_call_window_src_added_cb), window);
1593   g_signal_connect (priv->handler, "sink-pad-added",
1594     G_CALLBACK (empathy_call_window_sink_added_cb), window);
1595
1596   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1597 }
1598
1599 static gboolean
1600 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1601   EmpathyCallWindow *window)
1602 {
1603   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1604
1605   if (priv->pipeline != NULL)
1606     gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1607
1608   return FALSE;
1609 }
1610
1611 static void
1612 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1613 {
1614   GtkWidget *menu;
1615   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1616
1617   menu = gtk_ui_manager_get_widget (priv->ui_manager,
1618             "/menubar1");
1619
1620   if (set_fullscreen)
1621     {
1622       gtk_widget_hide (priv->sidebar);
1623       gtk_widget_hide (menu);
1624       gtk_widget_hide (priv->vbox);
1625       gtk_widget_hide (priv->statusbar);
1626       gtk_widget_hide (priv->toolbar);
1627     }
1628   else
1629     {
1630       if (priv->sidebar_was_visible_before_fs)
1631         gtk_widget_show (priv->sidebar);
1632
1633       gtk_widget_show (menu);
1634       gtk_widget_show (priv->vbox);
1635       gtk_widget_show (priv->statusbar);
1636       gtk_widget_show (priv->toolbar);
1637
1638       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1639           priv->original_height_before_fs);
1640     }
1641 }
1642
1643 static void
1644 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1645 {
1646   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1647
1648   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1649       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1650   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1651       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1652   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1653       priv->video_output, TRUE, TRUE,
1654       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1655       GTK_PACK_START);
1656   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1657       priv->vbox, TRUE, TRUE,
1658       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1659       GTK_PACK_START);
1660 }
1661
1662 static gboolean
1663 empathy_call_window_state_event_cb (GtkWidget *widget,
1664   GdkEventWindowState *event, EmpathyCallWindow *window)
1665 {
1666   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1667     {
1668       EmpathyCallWindowPriv *priv = GET_PRIV (window);
1669       gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1670
1671       if (set_fullscreen)
1672         {
1673           gboolean sidebar_was_visible;
1674           gint original_width = GTK_WIDGET (window)->allocation.width;
1675           gint original_height = GTK_WIDGET (window)->allocation.height;
1676
1677           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1678
1679           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1680           priv->original_width_before_fs = original_width;
1681           priv->original_height_before_fs = original_height;
1682
1683           if (priv->video_output_motion_handler_id == 0 &&
1684                 priv->video_output != NULL)
1685             {
1686               priv->video_output_motion_handler_id = g_signal_connect (
1687                   G_OBJECT (priv->video_output), "motion-notify-event",
1688                   G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1689             }
1690         }
1691       else
1692         {
1693           if (priv->video_output_motion_handler_id != 0)
1694             {
1695               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1696                   priv->video_output_motion_handler_id);
1697               priv->video_output_motion_handler_id = 0;
1698             }
1699         }
1700
1701       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1702           set_fullscreen);
1703       show_controls (window, set_fullscreen);
1704       show_borders (window, set_fullscreen);
1705       gtk_action_set_stock_id (priv->menu_fullscreen,
1706           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1707       priv->is_fullscreen = set_fullscreen;
1708   }
1709
1710   return FALSE;
1711 }
1712
1713 static void
1714 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1715   EmpathyCallWindow *window)
1716 {
1717   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1718   GtkWidget *arrow;
1719   int w,h, handle_size;
1720
1721   w = GTK_WIDGET (window)->allocation.width;
1722   h = GTK_WIDGET (window)->allocation.height;
1723
1724   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1725
1726   if (gtk_toggle_button_get_active (toggle))
1727     {
1728       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1729       gtk_widget_show (priv->sidebar);
1730       w += priv->sidebar->allocation.width + handle_size;
1731     }
1732   else
1733     {
1734       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1735       w -= priv->sidebar->allocation.width + handle_size;
1736       gtk_widget_hide (priv->sidebar);
1737     }
1738
1739   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1740
1741   if (w > 0 && h > 0)
1742     gtk_window_resize (GTK_WINDOW (window), w, h);
1743 }
1744
1745 static void
1746 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1747   gboolean send)
1748 {
1749   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1750   EmpathyTpCall *call;
1751
1752   priv->sending_video = send;
1753
1754   /* When we start sending video, we want to show the video preview by
1755      default. */
1756   if (send)
1757     {
1758       empathy_call_window_setup_video_preview (window);
1759       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1760           TRUE);
1761     }
1762
1763   g_object_get (priv->handler, "tp-call", &call, NULL);
1764   empathy_tp_call_request_video_stream_direction (call, send);
1765   g_object_unref (call);
1766 }
1767
1768 static void
1769 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1770   EmpathyCallWindow *window)
1771 {
1772   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1773   gboolean active;
1774
1775   active = (gtk_toggle_tool_button_get_active (toggle));
1776
1777   if (priv->sending_video == active)
1778     return;
1779
1780   empathy_call_window_set_send_video (window, active);
1781   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1782 }
1783
1784 static void
1785 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1786   EmpathyCallWindow *window)
1787 {
1788   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1789   gboolean active;
1790
1791   active = (gtk_toggle_action_get_active (toggle));
1792
1793   if (priv->sending_video == active)
1794     return;
1795
1796   empathy_call_window_set_send_video (window, active);
1797   gtk_toggle_tool_button_set_active (
1798       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1799 }
1800
1801 static void
1802 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1803   EmpathyCallWindow *window)
1804 {
1805   gboolean show_preview_toggled;
1806   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1807
1808   show_preview_toggled = gtk_toggle_action_get_active (toggle);
1809
1810   if (show_preview_toggled)
1811     {
1812       empathy_call_window_setup_video_preview (window);
1813       gtk_widget_show (priv->self_user_output_frame);
1814       empathy_call_window_update_self_avatar_visibility (window);
1815     }
1816   else
1817     {
1818       gtk_widget_hide (priv->self_user_output_frame);
1819     }
1820 }
1821
1822 static void
1823 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1824   EmpathyCallWindow *window)
1825 {
1826   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1827   gboolean active;
1828
1829   active = (gtk_toggle_tool_button_get_active (toggle));
1830
1831   if (active)
1832     {
1833       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1834         priv->volume);
1835       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1836     }
1837   else
1838     {
1839       /* TODO, Instead of setting the input volume to 0 we should probably
1840        * stop sending but this would cause the audio call to drop if both
1841        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1842        * in the future. GNOME #574574
1843        */
1844       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1845         0);
1846       gtk_adjustment_set_value (priv->audio_input_adj, 0);
1847     }
1848 }
1849
1850 static void
1851 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1852   EmpathyCallWindow *window)
1853 {
1854   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1855
1856   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1857     FALSE);
1858 }
1859
1860 static void
1861 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1862   EmpathyCallWindow *window)
1863 {
1864   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1865
1866   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1867     TRUE);
1868 }
1869
1870 static void
1871 empathy_call_window_hangup_cb (gpointer object,
1872                                EmpathyCallWindow *window)
1873 {
1874   if (empathy_call_window_disconnected (window))
1875     gtk_widget_destroy (GTK_WIDGET (window));
1876 }
1877
1878 static void
1879 empathy_call_window_restart_call (EmpathyCallWindow *window)
1880 {
1881   GstBus *bus;
1882   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1883
1884   gtk_widget_destroy (priv->remote_user_output_hbox);
1885   gtk_widget_destroy (priv->self_user_output_hbox);
1886
1887   priv->pipeline = gst_pipeline_new (NULL);
1888   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
1889   gst_bus_add_watch (bus, empathy_call_window_bus_message, window);
1890
1891   empathy_call_window_setup_remote_frame (bus, window);
1892   empathy_call_window_setup_self_frame (bus, window);
1893
1894   g_object_unref (bus);
1895
1896   gtk_widget_show_all (priv->content_hbox);
1897
1898   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1899       gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
1900
1901   empathy_call_window_status_message (window, CONNECTING_STATUS_TEXT);
1902   priv->call_started = TRUE;
1903   empathy_call_handler_start_call (priv->handler);
1904   gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1905
1906   gtk_action_set_sensitive (priv->redial, FALSE);
1907   gtk_widget_set_sensitive (priv->redial_button, FALSE);
1908 }
1909
1910 static void
1911 empathy_call_window_redial_cb (gpointer object,
1912     EmpathyCallWindow *window)
1913 {
1914   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1915
1916   if (priv->connected)
1917     priv->redialing = TRUE;
1918
1919   empathy_call_handler_stop_call (priv->handler);
1920
1921   if (!priv->connected)
1922     empathy_call_window_restart_call (window);
1923 }
1924
1925 static void
1926 empathy_call_window_fullscreen_cb (gpointer object,
1927                                    EmpathyCallWindow *window)
1928 {
1929   empathy_call_window_fullscreen_toggle (window);
1930 }
1931
1932 static void
1933 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
1934 {
1935   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1936
1937   if (priv->is_fullscreen)
1938     gtk_window_unfullscreen (GTK_WINDOW (window));
1939   else
1940     gtk_window_fullscreen (GTK_WINDOW (window));
1941 }
1942
1943 static gboolean
1944 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
1945   GdkEventButton *event, EmpathyCallWindow *window)
1946 {
1947   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1948     {
1949       empathy_call_window_video_menu_popup (window, event->button);
1950       return TRUE;
1951     }
1952
1953   return FALSE;
1954 }
1955
1956 static gboolean
1957 empathy_call_window_key_press_cb (GtkWidget *video_output,
1958   GdkEventKey *event, EmpathyCallWindow *window)
1959 {
1960   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1961
1962   if (priv->is_fullscreen && event->keyval == GDK_Escape)
1963     {
1964       /* Since we are in fullscreen mode, toggling will bring us back to
1965          normal mode. */
1966       empathy_call_window_fullscreen_toggle (window);
1967       return TRUE;
1968     }
1969
1970   return FALSE;
1971 }
1972
1973 static gboolean
1974 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
1975     GdkEventMotion *event, EmpathyCallWindow *window)
1976 {
1977   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1978
1979   if (priv->is_fullscreen)
1980     {
1981       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
1982       return TRUE;
1983     }
1984   return FALSE;
1985 }
1986
1987 static void
1988 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
1989   guint button)
1990 {
1991   GtkWidget *menu;
1992   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1993
1994   menu = gtk_ui_manager_get_widget (priv->ui_manager,
1995             "/video-popup");
1996   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1997       button, gtk_get_current_event_time ());
1998   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
1999 }
2000
2001 static void
2002 empathy_call_window_status_message (EmpathyCallWindow *window,
2003   gchar *message)
2004 {
2005   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2006
2007   if (priv->context_id == 0)
2008     {
2009       priv->context_id = gtk_statusbar_get_context_id (
2010         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2011     }
2012   else
2013     {
2014       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2015     }
2016
2017   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2018     message);
2019 }
2020
2021 static void
2022 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2023   gdouble value, EmpathyCallWindow *window)
2024 {
2025   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2026
2027   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2028     value);
2029 }