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