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