]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
Move the toolbar setup after we initialize the audio output
[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   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
727   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
728                                   CONTENT_HBOX_BORDER_WIDTH);
729   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
730
731   priv->pipeline = gst_pipeline_new (NULL);
732   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
733   priv->bus_message_source_id = gst_bus_add_watch (bus,
734       empathy_call_window_bus_message, self);
735
736   priv->fsnotifier = fs_element_added_notifier_new ();
737   fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
738
739   keyfile = g_key_file_new ();
740   filename = empathy_file_lookup ("element-properties", "data");
741   if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
742     {
743       fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
744           keyfile);
745     }
746   else
747     {
748       g_warning ("Could not load element-properties file: %s", error->message);
749       g_key_file_free (keyfile);
750       g_clear_error (&error);
751     }
752   g_free (filename);
753
754
755   priv->remote_user_output_frame = gtk_frame_new (NULL);
756   gtk_widget_set_size_request (priv->remote_user_output_frame,
757       EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
758   gtk_box_pack_start (GTK_BOX (priv->content_hbox),
759       priv->remote_user_output_frame, TRUE, TRUE,
760       CONTENT_HBOX_CHILDREN_PACKING_PADDING);
761   empathy_call_window_setup_remote_frame (bus, self);
762
763   priv->self_user_output_frame = gtk_frame_new (NULL);
764   gtk_widget_set_size_request (priv->self_user_output_frame,
765       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
766
767   priv->vbox = gtk_vbox_new (FALSE, 3);
768   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
769       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
770   gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame, FALSE,
771       FALSE, 0);
772   empathy_call_window_setup_self_frame (bus, self);
773
774   empathy_call_window_setup_toolbar (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
1326 static void
1327 empathy_call_window_channel_stream_closed_cb (EmpathyCallHandler *handler,
1328     TfStream *stream, gpointer user_data)
1329 {
1330   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1331   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1332   guint media_type;
1333
1334   g_object_get (stream, "media-type", &media_type, NULL);
1335
1336   /*
1337    * This assumes that there is only one video stream per channel...
1338    */
1339
1340   if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1341     {
1342       if (priv->funnel != NULL)
1343         {
1344           GstElement *output;
1345
1346           output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1347               (priv->video_output));
1348
1349           gst_element_set_state (output, GST_STATE_NULL);
1350           gst_element_set_state (priv->funnel, GST_STATE_NULL);
1351
1352           gst_bin_remove (GST_BIN (priv->pipeline), output);
1353           gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1354           priv->funnel = NULL;
1355         }
1356     }
1357   else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1358     {
1359       if (priv->liveadder != NULL)
1360         {
1361           gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1362           gst_element_set_state (priv->liveadder, GST_STATE_NULL);
1363
1364           gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1365           gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
1366           priv->liveadder = NULL;
1367         }
1368     }
1369 }
1370
1371 /* Called with global lock held */
1372 static GstPad *
1373 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1374 {
1375   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1376   GstPad *pad;
1377
1378   if (priv->funnel == NULL)
1379     {
1380       GstElement *output;
1381
1382       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1383         (priv->video_output));
1384
1385       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1386
1387       gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1388       gst_bin_add (GST_BIN (priv->pipeline), output);
1389
1390       gst_element_link (priv->funnel, output);
1391
1392       gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1393       gst_element_set_state (output, GST_STATE_PLAYING);
1394     }
1395
1396   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1397
1398   return pad;
1399 }
1400
1401 /* Called with global lock held */
1402 static GstPad *
1403 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1404 {
1405   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1406   GstPad *pad;
1407
1408   if (priv->liveadder == NULL)
1409     {
1410       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1411
1412       gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1413       gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1414
1415       gst_element_link (priv->liveadder, priv->audio_output);
1416
1417       gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1418       gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1419     }
1420
1421   pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1422
1423   return pad;
1424 }
1425
1426 static gboolean
1427 empathy_call_window_update_timer (gpointer user_data)
1428 {
1429   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1430   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1431   gchar *str;
1432   gdouble time;
1433
1434   time = g_timer_elapsed (priv->timer, NULL);
1435
1436   /* Translators: number of minutes:seconds the caller has been connected */
1437   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time / 60,
1438     (int) time % 60);
1439   empathy_call_window_status_message (self, str);
1440   g_free (str);
1441
1442   return TRUE;
1443 }
1444
1445 static gboolean
1446 empathy_call_window_connected (gpointer user_data)
1447 {
1448   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1449   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1450   EmpathyTpCall *call;
1451   gboolean can_send_video;
1452
1453   empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1454
1455   can_send_video = priv->video_input != NULL && priv->contact != NULL &&
1456     empathy_contact_can_voip_video (priv->contact);
1457
1458   g_object_get (priv->handler, "tp-call", &call, NULL);
1459
1460   g_signal_connect (call, "notify::video-stream",
1461     G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1462
1463   if (empathy_tp_call_has_dtmf (call))
1464     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1465
1466   if (priv->video_input == NULL)
1467     empathy_call_window_set_send_video (self, FALSE);
1468
1469   priv->sending_video = can_send_video ?
1470     empathy_tp_call_is_sending_video (call) : FALSE;
1471
1472   gtk_action_set_sensitive (priv->show_preview, TRUE);
1473   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1474       priv->sending_video
1475       || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
1476   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1477       priv->sending_video && priv->video_input != NULL);
1478   gtk_toggle_tool_button_set_active (
1479       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1480       priv->sending_video && priv->video_input != NULL);
1481   gtk_widget_set_sensitive (priv->camera_button, can_send_video);
1482   gtk_action_set_sensitive (priv->send_video, can_send_video);
1483
1484   gtk_action_set_sensitive (priv->redial, FALSE);
1485   gtk_widget_set_sensitive (priv->redial_button, FALSE);
1486
1487   gtk_widget_set_sensitive (priv->mic_button, TRUE);
1488
1489   empathy_call_window_update_avatars_visibility (call, self);
1490
1491   g_object_unref (call);
1492
1493   g_mutex_lock (priv->lock);
1494
1495   priv->timer_id = g_timeout_add_seconds (1,
1496     empathy_call_window_update_timer, self);
1497
1498   g_mutex_unlock (priv->lock);
1499
1500   empathy_call_window_update_timer (self);
1501
1502   return FALSE;
1503 }
1504
1505
1506 /* Called from the streaming thread */
1507 static void
1508 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1509   GstPad *src, guint media_type, gpointer user_data)
1510 {
1511   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1512   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1513
1514   GstPad *pad;
1515
1516   g_mutex_lock (priv->lock);
1517
1518   if (priv->call_state != CONNECTED)
1519     {
1520       g_timer_start (priv->timer);
1521       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
1522       priv->call_state = CONNECTED;
1523     }
1524
1525   switch (media_type)
1526     {
1527       case TP_MEDIA_STREAM_TYPE_AUDIO:
1528         pad = empathy_call_window_get_audio_sink_pad (self);
1529         break;
1530       case TP_MEDIA_STREAM_TYPE_VIDEO:
1531         gtk_widget_hide (priv->remote_user_avatar_widget);
1532         gtk_widget_show (priv->video_output);
1533         pad = empathy_call_window_get_video_sink_pad (self);
1534         break;
1535       default:
1536         g_assert_not_reached ();
1537     }
1538
1539   gst_pad_link (src, pad);
1540   gst_object_unref (pad);
1541
1542   g_mutex_unlock (priv->lock);
1543 }
1544
1545 /* Called from the streaming thread */
1546 static void
1547 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1548   GstPad *sink, guint media_type, gpointer user_data)
1549 {
1550   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1551   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1552   GstPad *pad;
1553
1554   switch (media_type)
1555     {
1556       case TP_MEDIA_STREAM_TYPE_AUDIO:
1557         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1558
1559         pad = gst_element_get_static_pad (priv->audio_input, "src");
1560         gst_pad_link (pad, sink);
1561
1562         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1563         break;
1564       case TP_MEDIA_STREAM_TYPE_VIDEO:
1565         if (priv->video_input != NULL)
1566           {
1567             EmpathyTpCall *call;
1568             g_object_get (priv->handler, "tp-call", &call, NULL);
1569
1570             if (empathy_tp_call_is_sending_video (call))
1571               {
1572                 empathy_call_window_setup_video_preview (self);
1573
1574                 gtk_toggle_action_set_active (
1575                     GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1576
1577                 if (priv->video_preview != NULL)
1578                   gtk_widget_show (priv->video_preview);
1579                 gtk_widget_hide (priv->self_user_avatar_widget);
1580               }
1581
1582             g_object_unref (call);
1583
1584             if (priv->video_tee != NULL)
1585               {
1586                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1587                 gst_pad_link (pad, sink);
1588               }
1589           }
1590         break;
1591       default:
1592         g_assert_not_reached ();
1593     }
1594
1595 }
1596
1597 static void
1598 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1599 {
1600   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1601   GstElement *preview;
1602
1603   preview = empathy_video_widget_get_element (
1604     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1605
1606   gst_element_set_state (priv->video_input, GST_STATE_NULL);
1607   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1608   gst_element_set_state (preview, GST_STATE_NULL);
1609
1610   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1611     priv->video_tee, preview, NULL);
1612
1613   g_object_unref (priv->video_input);
1614   priv->video_input = NULL;
1615   g_object_unref (priv->video_tee);
1616   priv->video_tee = NULL;
1617   gtk_widget_destroy (priv->video_preview);
1618   priv->video_preview = NULL;
1619
1620   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1621   gtk_toggle_tool_button_set_active (
1622       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1623   gtk_widget_set_sensitive (priv->camera_button, FALSE);
1624   gtk_action_set_sensitive (priv->send_video, FALSE);
1625
1626   gtk_widget_show (priv->self_user_avatar_widget);
1627 }
1628
1629
1630 static gboolean
1631 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1632   gpointer user_data)
1633 {
1634   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1635   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1636   GstState newstate;
1637
1638   empathy_call_handler_bus_message (priv->handler, bus, message);
1639
1640   switch (GST_MESSAGE_TYPE (message))
1641     {
1642       case GST_MESSAGE_STATE_CHANGED:
1643         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1644           {
1645             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1646             if (newstate == GST_STATE_PAUSED)
1647                 empathy_call_window_setup_video_input (self);
1648           }
1649         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1650             !priv->call_started)
1651           {
1652             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1653             if (newstate == GST_STATE_PAUSED)
1654               {
1655                 priv->call_started = TRUE;
1656                 empathy_call_handler_start_call (priv->handler);
1657                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1658               }
1659           }
1660         break;
1661       case GST_MESSAGE_ERROR:
1662         {
1663           GError *error = NULL;
1664           GstElement *gst_error;
1665           gchar *debug;
1666
1667           gst_message_parse_error (message, &error, &debug);
1668           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1669
1670           g_message ("Element error: %s -- %s\n", error->message, debug);
1671
1672           if (g_str_has_prefix (gst_element_get_name (gst_error),
1673                 VIDEO_INPUT_ERROR_PREFIX))
1674             {
1675               /* Remove the video input and continue */
1676               if (priv->video_input != NULL)
1677                 empathy_call_window_remove_video_input (self);
1678               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1679             }
1680           else
1681             {
1682               empathy_call_window_disconnected (self);
1683             }
1684           g_error_free (error);
1685           g_free (debug);
1686         }
1687       default:
1688         break;
1689     }
1690
1691   return TRUE;
1692 }
1693
1694 static void
1695 empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
1696 {
1697   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1698
1699   if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1700     {
1701       if (priv->video_preview != NULL)
1702         {
1703           gtk_widget_hide (priv->self_user_avatar_widget);
1704           gtk_widget_show (priv->video_preview);
1705         }
1706       else
1707         {
1708           if (priv->video_preview != NULL)
1709             gtk_widget_hide (priv->video_preview);
1710
1711           gtk_widget_show (priv->self_user_avatar_widget);
1712         }
1713     }
1714 }
1715
1716 static void
1717 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1718     EmpathyCallWindow *window)
1719 {
1720   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1721
1722   if (empathy_tp_call_is_receiving_video (call))
1723     {
1724       gtk_widget_hide (priv->remote_user_avatar_widget);
1725       gtk_widget_show (priv->video_output);
1726     }
1727   else
1728     {
1729       gtk_widget_hide (priv->video_output);
1730       gtk_widget_show (priv->remote_user_avatar_widget);
1731     }
1732
1733   empathy_call_window_update_self_avatar_visibility (window);
1734 }
1735
1736 static void
1737 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1738 {
1739   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1740
1741   g_signal_connect (priv->handler, "conference-added",
1742     G_CALLBACK (empathy_call_window_conference_added_cb), window);
1743   g_signal_connect (priv->handler, "request-resource",
1744     G_CALLBACK (empathy_call_window_request_resource_cb), window);
1745   g_signal_connect (priv->handler, "closed",
1746     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1747   g_signal_connect (priv->handler, "src-pad-added",
1748     G_CALLBACK (empathy_call_window_src_added_cb), window);
1749   g_signal_connect (priv->handler, "sink-pad-added",
1750     G_CALLBACK (empathy_call_window_sink_added_cb), window);
1751   g_signal_connect (priv->handler, "stream-closed",
1752     G_CALLBACK (empathy_call_window_channel_stream_closed_cb), window);
1753
1754   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1755 }
1756
1757 static gboolean
1758 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1759   EmpathyCallWindow *window)
1760 {
1761   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1762
1763   if (priv->pipeline != NULL)
1764     {
1765       if (priv->bus_message_source_id != 0)
1766         {
1767           g_source_remove (priv->bus_message_source_id);
1768           priv->bus_message_source_id = 0;
1769         }
1770
1771       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1772     }
1773
1774   if (priv->call_state == CONNECTING)
1775     empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1776
1777   return FALSE;
1778 }
1779
1780 static void
1781 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1782 {
1783   GtkWidget *menu;
1784   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1785
1786   menu = gtk_ui_manager_get_widget (priv->ui_manager,
1787             "/menubar1");
1788
1789   if (set_fullscreen)
1790     {
1791       gtk_widget_hide (priv->sidebar);
1792       gtk_widget_hide (menu);
1793       gtk_widget_hide (priv->vbox);
1794       gtk_widget_hide (priv->statusbar);
1795       gtk_widget_hide (priv->toolbar);
1796     }
1797   else
1798     {
1799       if (priv->sidebar_was_visible_before_fs)
1800         gtk_widget_show (priv->sidebar);
1801
1802       gtk_widget_show (menu);
1803       gtk_widget_show (priv->vbox);
1804       gtk_widget_show (priv->statusbar);
1805       gtk_widget_show (priv->toolbar);
1806
1807       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1808           priv->original_height_before_fs);
1809     }
1810 }
1811
1812 static void
1813 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1814 {
1815   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1816
1817   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1818       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1819   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1820       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1821   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1822       priv->video_output, TRUE, TRUE,
1823       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1824       GTK_PACK_START);
1825   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1826       priv->vbox, TRUE, TRUE,
1827       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1828       GTK_PACK_START);
1829 }
1830
1831 static gboolean
1832 empathy_call_window_state_event_cb (GtkWidget *widget,
1833   GdkEventWindowState *event, EmpathyCallWindow *window)
1834 {
1835   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1836     {
1837       EmpathyCallWindowPriv *priv = GET_PRIV (window);
1838       gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1839
1840       if (set_fullscreen)
1841         {
1842           gboolean sidebar_was_visible;
1843           gint original_width = GTK_WIDGET (window)->allocation.width;
1844           gint original_height = GTK_WIDGET (window)->allocation.height;
1845
1846           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1847
1848           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1849           priv->original_width_before_fs = original_width;
1850           priv->original_height_before_fs = original_height;
1851
1852           if (priv->video_output_motion_handler_id == 0 &&
1853                 priv->video_output != NULL)
1854             {
1855               priv->video_output_motion_handler_id = g_signal_connect (
1856                   G_OBJECT (priv->video_output), "motion-notify-event",
1857                   G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1858             }
1859         }
1860       else
1861         {
1862           if (priv->video_output_motion_handler_id != 0)
1863             {
1864               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1865                   priv->video_output_motion_handler_id);
1866               priv->video_output_motion_handler_id = 0;
1867             }
1868         }
1869
1870       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1871           set_fullscreen);
1872       show_controls (window, set_fullscreen);
1873       show_borders (window, set_fullscreen);
1874       gtk_action_set_stock_id (priv->menu_fullscreen,
1875           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1876       priv->is_fullscreen = set_fullscreen;
1877   }
1878
1879   return FALSE;
1880 }
1881
1882 static void
1883 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1884   EmpathyCallWindow *window)
1885 {
1886   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1887   GtkWidget *arrow;
1888   int w,h, handle_size;
1889
1890   w = GTK_WIDGET (window)->allocation.width;
1891   h = GTK_WIDGET (window)->allocation.height;
1892
1893   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1894
1895   if (gtk_toggle_button_get_active (toggle))
1896     {
1897       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1898       gtk_widget_show (priv->sidebar);
1899       w += priv->sidebar->allocation.width + handle_size;
1900     }
1901   else
1902     {
1903       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1904       w -= priv->sidebar->allocation.width + handle_size;
1905       gtk_widget_hide (priv->sidebar);
1906     }
1907
1908   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1909
1910   if (w > 0 && h > 0)
1911     gtk_window_resize (GTK_WINDOW (window), w, h);
1912 }
1913
1914 static void
1915 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1916   gboolean send)
1917 {
1918   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1919   EmpathyTpCall *call;
1920
1921   priv->sending_video = send;
1922
1923   /* When we start sending video, we want to show the video preview by
1924      default. */
1925   if (send)
1926     {
1927       empathy_call_window_setup_video_preview (window);
1928       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1929           TRUE);
1930     }
1931
1932   g_object_get (priv->handler, "tp-call", &call, NULL);
1933   empathy_tp_call_request_video_stream_direction (call, send);
1934   g_object_unref (call);
1935 }
1936
1937 static void
1938 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1939   EmpathyCallWindow *window)
1940 {
1941   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1942   gboolean active;
1943
1944   if (priv->call_state != CONNECTED)
1945     return;
1946
1947   active = (gtk_toggle_tool_button_get_active (toggle));
1948
1949   if (priv->sending_video == active)
1950     return;
1951
1952   empathy_call_window_set_send_video (window, active);
1953   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1954 }
1955
1956 static void
1957 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1958   EmpathyCallWindow *window)
1959 {
1960   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1961   gboolean active;
1962
1963   if (priv->call_state != CONNECTED)
1964     return;
1965
1966   active = (gtk_toggle_action_get_active (toggle));
1967
1968   if (priv->sending_video == active)
1969     return;
1970
1971   empathy_call_window_set_send_video (window, active);
1972   gtk_toggle_tool_button_set_active (
1973       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1974 }
1975
1976 static void
1977 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1978   EmpathyCallWindow *window)
1979 {
1980   gboolean show_preview_toggled;
1981   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1982
1983   show_preview_toggled = gtk_toggle_action_get_active (toggle);
1984
1985   if (show_preview_toggled)
1986     {
1987       empathy_call_window_setup_video_preview (window);
1988       gtk_widget_show (priv->self_user_output_frame);
1989       empathy_call_window_update_self_avatar_visibility (window);
1990     }
1991   else
1992     {
1993       gtk_widget_hide (priv->self_user_output_frame);
1994     }
1995 }
1996
1997 static void
1998 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1999   EmpathyCallWindow *window)
2000 {
2001   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2002   gboolean active;
2003
2004   if (priv->audio_input == NULL)
2005     return;
2006
2007   active = (gtk_toggle_tool_button_get_active (toggle));
2008
2009   if (active)
2010     {
2011       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2012         priv->volume);
2013       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2014     }
2015   else
2016     {
2017       /* TODO, Instead of setting the input volume to 0 we should probably
2018        * stop sending but this would cause the audio call to drop if both
2019        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2020        * in the future. GNOME #574574
2021        */
2022       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2023         0);
2024       gtk_adjustment_set_value (priv->audio_input_adj, 0);
2025     }
2026 }
2027
2028 static void
2029 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
2030   EmpathyCallWindow *window)
2031 {
2032   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2033
2034   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2035     FALSE);
2036 }
2037
2038 static void
2039 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
2040   EmpathyCallWindow *window)
2041 {
2042   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2043
2044   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2045     TRUE);
2046 }
2047
2048 static void
2049 empathy_call_window_hangup_cb (gpointer object,
2050                                EmpathyCallWindow *window)
2051 {
2052   if (empathy_call_window_disconnected (window))
2053     gtk_widget_destroy (GTK_WIDGET (window));
2054 }
2055
2056 static void
2057 empathy_call_window_restart_call (EmpathyCallWindow *window)
2058 {
2059   GstBus *bus;
2060   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2061
2062   gtk_widget_destroy (priv->remote_user_output_hbox);
2063   gtk_widget_destroy (priv->self_user_output_hbox);
2064
2065   priv->pipeline = gst_pipeline_new (NULL);
2066   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
2067   priv->bus_message_source_id = gst_bus_add_watch (bus,
2068       empathy_call_window_bus_message, window);
2069
2070   empathy_call_window_setup_remote_frame (bus, window);
2071   empathy_call_window_setup_self_frame (bus, window);
2072
2073   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
2074       G_CALLBACK (empathy_call_window_mic_volume_changed_cb), window);
2075
2076   /* While the call was disconnected, the input volume might have changed.
2077    * However, since the audio_input source was destroyed, its volume has not
2078    * been updated during that time. That's why we manually update it here */
2079   empathy_call_window_mic_volume_changed_cb (priv->audio_input_adj, window);
2080
2081   g_object_unref (bus);
2082
2083   gtk_widget_show_all (priv->content_hbox);
2084
2085   if (!empathy_call_handler_has_initial_video (priv->handler))
2086     gtk_widget_hide (priv->self_user_output_frame);
2087
2088   priv->outgoing = TRUE;
2089   empathy_call_window_set_state_connecting (window);
2090
2091   priv->call_started = TRUE;
2092   empathy_call_handler_start_call (priv->handler);
2093   empathy_call_window_setup_avatars (window, priv->handler);
2094   gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2095
2096   gtk_action_set_sensitive (priv->redial, FALSE);
2097   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2098 }
2099
2100 static void
2101 empathy_call_window_redial_cb (gpointer object,
2102     EmpathyCallWindow *window)
2103 {
2104   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2105
2106   if (priv->call_state == CONNECTED)
2107     priv->call_state = REDIALING;
2108
2109   empathy_call_handler_stop_call (priv->handler);
2110
2111   if (priv->call_state != CONNECTED)
2112     empathy_call_window_restart_call (window);
2113 }
2114
2115 static void
2116 empathy_call_window_fullscreen_cb (gpointer object,
2117                                    EmpathyCallWindow *window)
2118 {
2119   empathy_call_window_fullscreen_toggle (window);
2120 }
2121
2122 static void
2123 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
2124 {
2125   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2126
2127   if (priv->is_fullscreen)
2128     gtk_window_unfullscreen (GTK_WINDOW (window));
2129   else
2130     gtk_window_fullscreen (GTK_WINDOW (window));
2131 }
2132
2133 static gboolean
2134 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2135   GdkEventButton *event, EmpathyCallWindow *window)
2136 {
2137   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2138     {
2139       empathy_call_window_video_menu_popup (window, event->button);
2140       return TRUE;
2141     }
2142
2143   return FALSE;
2144 }
2145
2146 static gboolean
2147 empathy_call_window_key_press_cb (GtkWidget *video_output,
2148   GdkEventKey *event, EmpathyCallWindow *window)
2149 {
2150   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2151
2152   if (priv->is_fullscreen && event->keyval == GDK_Escape)
2153     {
2154       /* Since we are in fullscreen mode, toggling will bring us back to
2155          normal mode. */
2156       empathy_call_window_fullscreen_toggle (window);
2157       return TRUE;
2158     }
2159
2160   return FALSE;
2161 }
2162
2163 static gboolean
2164 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2165     GdkEventMotion *event, EmpathyCallWindow *window)
2166 {
2167   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2168
2169   if (priv->is_fullscreen)
2170     {
2171       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2172       return TRUE;
2173     }
2174   return FALSE;
2175 }
2176
2177 static void
2178 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2179   guint button)
2180 {
2181   GtkWidget *menu;
2182   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2183
2184   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2185             "/video-popup");
2186   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2187       button, gtk_get_current_event_time ());
2188   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2189 }
2190
2191 static void
2192 empathy_call_window_status_message (EmpathyCallWindow *window,
2193   gchar *message)
2194 {
2195   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2196
2197   if (priv->context_id == 0)
2198     {
2199       priv->context_id = gtk_statusbar_get_context_id (
2200         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2201     }
2202   else
2203     {
2204       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2205     }
2206
2207   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2208     message);
2209 }
2210
2211 static void
2212 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2213   gdouble value, EmpathyCallWindow *window)
2214 {
2215   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2216
2217   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2218     value);
2219 }