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