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