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