]> git.0d.be Git - empathy.git/blob - src/empathy-streamed-media-window.c
Let set_send_video do it's job
[empathy.git] / src / empathy-streamed-media-window.c
1 /*
2  * empathy-streamed-media-window.c - Source for EmpathyStreamedMediaWindow
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 #include "config.h"
22
23 #include <stdio.h>
24 #include <stdlib.h>
25
26 #include <math.h>
27
28 #include <gdk/gdkkeysyms.h>
29 #include <gst/gst.h>
30 #include <gtk/gtk.h>
31 #include <glib/gi18n.h>
32
33 #include <telepathy-glib/util.h>
34 #include <telepathy-farsight/channel.h>
35 #include <telepathy-glib/util.h>
36
37 #include <gst/farsight/fs-element-added-notifier.h>
38
39 #include <libempathy/empathy-tp-contact-factory.h>
40 #include <libempathy/empathy-utils.h>
41 #include <libempathy-gtk/empathy-avatar-image.h>
42 #include <libempathy-gtk/empathy-dialpad-widget.h>
43 #include <libempathy-gtk/empathy-ui-utils.h>
44 #include <libempathy-gtk/empathy-sound-manager.h>
45 #include <libempathy-gtk/empathy-geometry.h>
46 #include <libempathy-gtk/empathy-images.h>
47
48 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
49 #include <libempathy/empathy-debug.h>
50
51 #include "empathy-streamed-media-window.h"
52 #include "empathy-streamed-media-window-fullscreen.h"
53 #include "empathy-video-widget.h"
54 #include "empathy-audio-src.h"
55 #include "empathy-audio-sink.h"
56 #include "empathy-video-src.h"
57 #include "ev-sidebar.h"
58
59 #define CONTENT_HBOX_BORDER_WIDTH 6
60 #define CONTENT_HBOX_SPACING 3
61 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
62
63 #define SELF_VIDEO_SECTION_WIDTH 160
64 #define SELF_VIDEO_SECTION_HEIGTH 120
65
66 /* The avatar's default width and height are set to the same value because we
67    want a square icon. */
68 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
69 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
70   EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
71
72 /* If an video input error occurs, the error message will start with "v4l" */
73 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
74
75 /* The time interval in milliseconds between 2 outgoing rings */
76 #define MS_BETWEEN_RING 500
77
78 G_DEFINE_TYPE(EmpathyStreamedMediaWindow, empathy_streamed_media_window, GTK_TYPE_WINDOW)
79
80 /* signal enum */
81 #if 0
82 enum
83 {
84     LAST_SIGNAL
85 };
86
87 static guint signals[LAST_SIGNAL] = {0};
88 #endif
89
90 enum {
91   PROP_STREAMED_MEDIA_HANDLER = 1,
92 };
93
94 typedef enum {
95   CONNECTING,
96   CONNECTED,
97   DISCONNECTED,
98   REDIALING
99 } CallState;
100
101 typedef enum {
102   CAMERA_STATE_OFF = 0,
103   CAMERA_STATE_PREVIEW,
104   CAMERA_STATE_ON,
105 } CameraState;
106
107 /* private structure */
108 typedef struct _EmpathyStreamedMediaWindowPriv EmpathyStreamedMediaWindowPriv;
109
110 struct _EmpathyStreamedMediaWindowPriv
111 {
112   gboolean dispose_has_run;
113   EmpathyStreamedMediaHandler *handler;
114   EmpathyContact *contact;
115
116   guint call_state;
117   gboolean outgoing;
118
119   GtkUIManager *ui_manager;
120   GtkWidget *errors_vbox;
121   /* widget displays the video received from the remote user. This widget is
122    * alive only during call. */
123   GtkWidget *video_output;
124   GtkWidget *video_preview;
125   GtkWidget *remote_user_avatar_widget;
126   GtkWidget *self_user_avatar_widget;
127   GtkWidget *sidebar;
128   GtkWidget *sidebar_button;
129   GtkWidget *statusbar;
130   GtkWidget *volume_button;
131   GtkWidget *redial_button;
132   GtkWidget *mic_button;
133   GtkWidget *toolbar;
134   GtkWidget *pane;
135   GtkAction *redial;
136   GtkAction *menu_fullscreen;
137   GtkAction *action_camera_on;
138   GtkWidget *tool_button_camera_off;
139   GtkWidget *tool_button_camera_preview;
140   GtkWidget *tool_button_camera_on;
141
142   /* The frames and boxes that contain self and remote avatar and video
143      input/output. When we redial, we destroy and re-create the boxes */
144   GtkWidget *remote_user_output_frame;
145   GtkWidget *self_user_output_frame;
146   GtkWidget *remote_user_output_hbox;
147   GtkWidget *self_user_output_hbox;
148
149   /* We keep a reference on the hbox which contains the main content so we can
150      easilly repack everything when toggling fullscreen */
151   GtkWidget *content_hbox;
152
153   /* This vbox is contained in the content_hbox and it contains the
154      self_user_output_frame and the sidebar button. When toggling fullscreen,
155      it needs to be repacked. We keep a reference on it for easier access. */
156   GtkWidget *vbox;
157
158   gulong video_output_motion_handler_id;
159   guint bus_message_source_id;
160
161   gdouble volume;
162   GtkWidget *volume_scale;
163   GtkWidget *volume_progress_bar;
164   GtkAdjustment *audio_input_adj;
165
166   GtkWidget *dtmf_panel;
167
168   /* Details vbox */
169   GtkWidget *details_vbox;
170   GtkWidget *vcodec_encoding_label;
171   GtkWidget *acodec_encoding_label;
172   GtkWidget *vcodec_decoding_label;
173   GtkWidget *acodec_decoding_label;
174
175   GtkWidget *audio_remote_candidate_label;
176   GtkWidget *audio_local_candidate_label;
177   GtkWidget *video_remote_candidate_label;
178   GtkWidget *video_local_candidate_label;
179   GtkWidget *video_remote_candidate_info_img;
180   GtkWidget *video_local_candidate_info_img;
181   GtkWidget *audio_remote_candidate_info_img;
182   GtkWidget *audio_local_candidate_info_img;
183
184   GstElement *video_input;
185   GstElement *audio_input;
186   GstElement *audio_output;
187   GstElement *pipeline;
188   GstElement *video_tee;
189
190   GstElement *funnel;
191
192   FsElementAddedNotifier *fsnotifier;
193
194   guint context_id;
195
196   GTimer *timer;
197   guint timer_id;
198
199   GtkWidget *video_contrast;
200   GtkWidget *video_brightness;
201   GtkWidget *video_gamma;
202
203   GMutex *lock;
204   gboolean call_started;
205   gboolean sending_video;
206   CameraState camera_state;
207
208   EmpathyStreamedMediaWindowFullscreen *fullscreen;
209   gboolean is_fullscreen;
210
211   /* Those fields represent the state of the window before it actually was in
212      fullscreen mode. */
213   gboolean sidebar_was_visible_before_fs;
214   gint original_width_before_fs;
215   gint original_height_before_fs;
216
217   /* TRUE if the call should be started when the pipeline is playing */
218   gboolean start_call_when_playing;
219   /* TRUE if we requested to set the pipeline in the playing state */
220   gboolean pipeline_playing;
221
222   EmpathySoundManager *sound_mgr;
223 };
224
225 #define GET_PRIV(o) \
226   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_STREAMED_MEDIA_WINDOW, \
227     EmpathyStreamedMediaWindowPriv))
228
229 static void empathy_streamed_media_window_realized_cb (GtkWidget *widget,
230   EmpathyStreamedMediaWindow *window);
231
232 static gboolean empathy_streamed_media_window_delete_cb (GtkWidget *widget,
233   GdkEvent *event, EmpathyStreamedMediaWindow *window);
234
235 static gboolean empathy_streamed_media_window_state_event_cb (GtkWidget *widget,
236   GdkEventWindowState *event, EmpathyStreamedMediaWindow *window);
237
238 static void empathy_streamed_media_window_sidebar_toggled_cb (GtkToggleButton *toggle,
239   EmpathyStreamedMediaWindow *window);
240
241 static void empathy_streamed_media_window_set_send_video (EmpathyStreamedMediaWindow *window,
242   CameraState state);
243
244 static void empathy_streamed_media_window_mic_toggled_cb (
245   GtkToggleToolButton *toggle, EmpathyStreamedMediaWindow *window);
246
247 static void empathy_streamed_media_window_sidebar_hidden_cb (EvSidebar *sidebar,
248   EmpathyStreamedMediaWindow *window);
249
250 static void empathy_streamed_media_window_sidebar_shown_cb (EvSidebar *sidebar,
251   EmpathyStreamedMediaWindow *window);
252
253 static void empathy_streamed_media_window_hangup_cb (gpointer object,
254   EmpathyStreamedMediaWindow *window);
255
256 static void empathy_streamed_media_window_fullscreen_cb (gpointer object,
257   EmpathyStreamedMediaWindow *window);
258
259 static void empathy_streamed_media_window_fullscreen_toggle (EmpathyStreamedMediaWindow *window);
260
261 static gboolean empathy_streamed_media_window_video_button_press_cb (
262   GtkWidget *video_output, GdkEventButton *event, EmpathyStreamedMediaWindow *window);
263
264 static gboolean empathy_streamed_media_window_key_press_cb (GtkWidget *video_output,
265   GdkEventKey *event, EmpathyStreamedMediaWindow *window);
266
267 static gboolean empathy_streamed_media_window_video_output_motion_notify (
268   GtkWidget *widget, GdkEventMotion *event, EmpathyStreamedMediaWindow *window);
269
270 static void empathy_streamed_media_window_video_menu_popup (EmpathyStreamedMediaWindow *window,
271   guint button);
272
273 static void empathy_streamed_media_window_redial_cb (gpointer object,
274   EmpathyStreamedMediaWindow *window);
275
276 static void empathy_streamed_media_window_restart_call (EmpathyStreamedMediaWindow *window);
277
278 static void empathy_streamed_media_window_status_message (EmpathyStreamedMediaWindow *window,
279   gchar *message);
280
281 static void empathy_streamed_media_window_update_avatars_visibility (EmpathyTpStreamedMedia *call,
282   EmpathyStreamedMediaWindow *window);
283
284 static gboolean empathy_streamed_media_window_bus_message (GstBus *bus,
285   GstMessage *message, gpointer user_data);
286
287 static void
288 empathy_streamed_media_window_volume_changed_cb (GtkScaleButton *button,
289   gdouble value, EmpathyStreamedMediaWindow *window);
290
291 static void block_camera_control_signals (EmpathyStreamedMediaWindow *self);
292 static void unblock_camera_control_signals (EmpathyStreamedMediaWindow *self);
293
294 static void
295 empathy_streamed_media_window_setup_toolbar (EmpathyStreamedMediaWindow *self)
296 {
297   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
298   GtkToolItem *tool_item;
299   GtkWidget *camera_off_icon;
300   GdkPixbuf *pixbuf, *modded_pixbuf;
301
302   /* set the icon of the 'camera off' button by greying off the webcam icon */
303   pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
304       GTK_ICON_SIZE_SMALL_TOOLBAR);
305
306   modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
307       gdk_pixbuf_get_width (pixbuf),
308       gdk_pixbuf_get_height (pixbuf));
309
310   gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
311   g_object_unref (pixbuf);
312
313   camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
314   g_object_unref (modded_pixbuf);
315   gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
316         priv->tool_button_camera_off), camera_off_icon);
317
318   /* Add an empty expanded GtkToolItem so the volume button is at the end of
319    * the toolbar. */
320   tool_item = gtk_tool_item_new ();
321   gtk_tool_item_set_expand (tool_item, TRUE);
322   gtk_widget_show (GTK_WIDGET (tool_item));
323   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
324
325   priv->volume_button = gtk_volume_button_new ();
326   /* FIXME listen to the audiosinks signals and update the button according to
327    * that, for now starting out at 1.0 and assuming only the app changes the
328    * volume will do */
329   gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
330   g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
331     G_CALLBACK (empathy_streamed_media_window_volume_changed_cb), self);
332
333   tool_item = gtk_tool_item_new ();
334   gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
335   gtk_widget_show_all (GTK_WIDGET (tool_item));
336   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
337 }
338
339 static void
340 dtmf_start_tone_cb (EmpathyDialpadWidget *dialpad,
341     TpDTMFEvent event,
342     EmpathyStreamedMediaWindow *window)
343 {
344   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
345   EmpathyTpStreamedMedia *call;
346
347   g_object_get (priv->handler, "tp-call", &call, NULL);
348
349   empathy_tp_streamed_media_start_tone (call, event);
350
351   g_object_unref (call);
352 }
353
354 static void
355 dtmf_stop_tone_cb (EmpathyDialpadWidget *self,
356     TpDTMFEvent event,
357     EmpathyStreamedMediaWindow *window)
358 {
359   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
360   EmpathyTpStreamedMedia *call;
361
362   g_object_get (priv->handler, "tp-call", &call, NULL);
363
364   empathy_tp_streamed_media_stop_tone (call);
365
366   g_object_unref (call);
367 }
368
369 static GtkWidget *
370 empathy_streamed_media_window_create_video_input_add_slider (EmpathyStreamedMediaWindow *self,
371   gchar *label_text, GtkWidget *bin)
372 {
373    GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
374    GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
375    GtkWidget *label = gtk_label_new (label_text);
376
377    gtk_widget_set_sensitive (scale, FALSE);
378
379    gtk_container_add (GTK_CONTAINER (bin), vbox);
380
381    gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
382    gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
383    gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
384
385    return scale;
386 }
387
388 static void
389 empathy_streamed_media_window_video_contrast_changed_cb (GtkAdjustment *adj,
390   EmpathyStreamedMediaWindow *self)
391
392 {
393   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
394
395   empathy_video_src_set_channel (priv->video_input,
396     EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
397 }
398
399 static void
400 empathy_streamed_media_window_video_brightness_changed_cb (GtkAdjustment *adj,
401   EmpathyStreamedMediaWindow *self)
402
403 {
404   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
405
406   empathy_video_src_set_channel (priv->video_input,
407     EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
408 }
409
410 static void
411 empathy_streamed_media_window_video_gamma_changed_cb (GtkAdjustment *adj,
412   EmpathyStreamedMediaWindow *self)
413
414 {
415   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
416
417   empathy_video_src_set_channel (priv->video_input,
418     EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
419 }
420
421
422 static GtkWidget *
423 empathy_streamed_media_window_create_video_input (EmpathyStreamedMediaWindow *self)
424 {
425   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
426   GtkWidget *hbox;
427
428   hbox = gtk_hbox_new (TRUE, 3);
429
430   priv->video_contrast = empathy_streamed_media_window_create_video_input_add_slider (
431     self,  _("Contrast"), hbox);
432
433   priv->video_brightness = empathy_streamed_media_window_create_video_input_add_slider (
434     self,  _("Brightness"), hbox);
435
436   priv->video_gamma = empathy_streamed_media_window_create_video_input_add_slider (
437     self,  _("Gamma"), hbox);
438
439   return hbox;
440 }
441
442 static void
443 empathy_streamed_media_window_setup_video_input (EmpathyStreamedMediaWindow *self)
444 {
445   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
446   guint supported;
447   GtkAdjustment *adj;
448
449   supported = empathy_video_src_get_supported_channels (priv->video_input);
450
451   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
452     {
453       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
454
455       gtk_adjustment_set_value (adj,
456         empathy_video_src_get_channel (priv->video_input,
457           EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
458
459       g_signal_connect (G_OBJECT (adj), "value-changed",
460         G_CALLBACK (empathy_streamed_media_window_video_contrast_changed_cb), self);
461
462       gtk_widget_set_sensitive (priv->video_contrast, TRUE);
463     }
464
465   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
466     {
467       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
468
469       gtk_adjustment_set_value (adj,
470         empathy_video_src_get_channel (priv->video_input,
471           EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
472
473       g_signal_connect (G_OBJECT (adj), "value-changed",
474         G_CALLBACK (empathy_streamed_media_window_video_brightness_changed_cb), self);
475       gtk_widget_set_sensitive (priv->video_brightness, TRUE);
476     }
477
478   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
479     {
480       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
481
482       gtk_adjustment_set_value (adj,
483         empathy_video_src_get_channel (priv->video_input,
484           EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
485
486       g_signal_connect (G_OBJECT (adj), "value-changed",
487         G_CALLBACK (empathy_streamed_media_window_video_gamma_changed_cb), self);
488       gtk_widget_set_sensitive (priv->video_gamma, TRUE);
489     }
490 }
491
492 static void
493 empathy_streamed_media_window_mic_volume_changed_cb (GtkAdjustment *adj,
494   EmpathyStreamedMediaWindow *self)
495 {
496   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
497   gdouble volume;
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_streamed_media_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
519   gdouble level, EmpathyStreamedMediaWindow *window)
520 {
521   gdouble value;
522   EmpathyStreamedMediaWindowPriv *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_streamed_media_window_create_audio_input (EmpathyStreamedMediaWindow *self)
531 {
532   EmpathyStreamedMediaWindowPriv *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_streamed_media_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
558   gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->volume_progress_bar),
559       GTK_ORIENTATION_VERTICAL);
560
561   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
562       0);
563
564   gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
565       3);
566
567   return hbox;
568 }
569
570 static void
571 create_video_output_widget (EmpathyStreamedMediaWindow *self)
572 {
573   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
574   GstBus *bus;
575
576   g_assert (priv->video_output == NULL);
577   g_assert (priv->pipeline != NULL);
578
579   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
580   priv->video_output = empathy_video_widget_new (bus);
581
582   gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
583       priv->video_output, TRUE, TRUE, 0);
584
585   gtk_widget_add_events (priv->video_output,
586       GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
587   g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
588       G_CALLBACK (empathy_streamed_media_window_video_button_press_cb), self);
589
590   g_object_unref (bus);
591 }
592
593 static void
594 create_video_input (EmpathyStreamedMediaWindow *self)
595 {
596   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
597
598   g_assert (priv->video_input == NULL);
599   priv->video_input = empathy_video_src_new ();
600   gst_object_ref (priv->video_input);
601   gst_object_sink (priv->video_input);
602 }
603
604 static void
605 create_audio_input (EmpathyStreamedMediaWindow *self)
606 {
607   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
608
609   g_assert (priv->audio_input == NULL);
610   priv->audio_input = empathy_audio_src_new ();
611   gst_object_ref (priv->audio_input);
612   gst_object_sink (priv->audio_input);
613
614   tp_g_signal_connect_object (priv->audio_input, "peak-level-changed",
615     G_CALLBACK (empathy_streamed_media_window_audio_input_level_changed_cb),
616     self, 0);
617 }
618
619 static void
620 add_video_preview_to_pipeline (EmpathyStreamedMediaWindow *self)
621 {
622   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
623   GstElement *preview;
624
625   g_assert (priv->video_preview != NULL);
626   g_assert (priv->pipeline != NULL);
627   g_assert (priv->video_input != NULL);
628   g_assert (priv->video_tee != NULL);
629
630   preview = empathy_video_widget_get_element (
631       EMPATHY_VIDEO_WIDGET (priv->video_preview));
632
633   if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_input))
634     {
635       g_warning ("Could not add video input to pipeline");
636       return;
637     }
638
639   if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_tee))
640     {
641       g_warning ("Could not add video tee to pipeline");
642       return;
643     }
644
645   if (!gst_bin_add (GST_BIN (priv->pipeline), preview))
646     {
647       g_warning ("Could not add video preview to pipeline");
648       return;
649     }
650
651   if (!gst_element_link (priv->video_input, priv->video_tee))
652     {
653       g_warning ("Could not link video input to video tee");
654       return;
655     }
656
657   if (!gst_element_link (priv->video_tee, preview))
658     {
659       g_warning ("Could not link video tee to video preview");
660       return;
661     }
662 }
663
664 static void
665 create_video_preview (EmpathyStreamedMediaWindow *self)
666 {
667   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
668   GstBus *bus;
669
670   g_assert (priv->video_preview == NULL);
671   g_assert (priv->video_tee == NULL);
672
673   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
674
675   priv->video_preview = empathy_video_widget_new_with_size (bus,
676       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
677   g_object_set (priv->video_preview,
678       "sync", FALSE,
679       "async", TRUE,
680       "flip-video", TRUE,
681       NULL);
682
683   gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
684       priv->video_preview, TRUE, TRUE, 0);
685
686   priv->video_tee = gst_element_factory_make ("tee", NULL);
687   gst_object_ref (priv->video_tee);
688   gst_object_sink (priv->video_tee);
689
690   g_object_unref (bus);
691 }
692
693 static void
694 play_camera (EmpathyStreamedMediaWindow *window,
695     gboolean play)
696 {
697   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
698   GstElement *preview;
699   GstState state;
700
701   if (priv->video_preview == NULL)
702     {
703       create_video_preview (window);
704       add_video_preview_to_pipeline (window);
705     }
706
707   if (play)
708     state = GST_STATE_PLAYING;
709   else
710     state = GST_STATE_NULL;
711
712   preview = empathy_video_widget_get_element (
713       EMPATHY_VIDEO_WIDGET (priv->video_preview));
714
715   gst_element_set_state (preview, state);
716   gst_element_set_state (priv->video_input, state);
717   gst_element_set_state (priv->video_tee, state);
718 }
719
720 static void
721 display_video_preview (EmpathyStreamedMediaWindow *self,
722     gboolean display)
723 {
724   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
725
726   if (display)
727     {
728       /* Display the preview and hide the self avatar */
729       DEBUG ("Show video preview");
730
731       play_camera (self, TRUE);
732       gtk_widget_show (priv->video_preview);
733       gtk_widget_hide (priv->self_user_avatar_widget);
734     }
735   else
736     {
737       /* Display the self avatar and hide the preview */
738       DEBUG ("Show self avatar");
739
740       if (priv->video_preview != NULL)
741         {
742           gtk_widget_hide (priv->video_preview);
743           play_camera (self, FALSE);
744         }
745       gtk_widget_show (priv->self_user_avatar_widget);
746     }
747 }
748
749 static void
750 empathy_streamed_media_window_set_state_connecting (EmpathyStreamedMediaWindow *window)
751 {
752   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
753
754   empathy_streamed_media_window_status_message (window, _("Connecting…"));
755   priv->call_state = CONNECTING;
756
757   if (priv->outgoing)
758     empathy_sound_manager_start_playing (priv->sound_mgr, GTK_WIDGET (window),
759         EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
760 }
761
762 static void
763 disable_camera (EmpathyStreamedMediaWindow *self)
764 {
765   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
766
767   if (priv->camera_state == CAMERA_STATE_OFF)
768     return;
769
770   DEBUG ("Disable camera");
771
772   display_video_preview (self, FALSE);
773
774   if (priv->camera_state == CAMERA_STATE_ON)
775     empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_OFF);
776
777   block_camera_control_signals (self);
778   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
779         priv->tool_button_camera_on), FALSE);
780   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
781       priv->tool_button_camera_preview), FALSE);
782
783   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
784       priv->tool_button_camera_off), TRUE);
785   gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
786       CAMERA_STATE_OFF);
787   unblock_camera_control_signals (self);
788
789   priv->camera_state = CAMERA_STATE_OFF;
790 }
791
792 static void
793 tool_button_camera_off_toggled_cb (GtkToggleToolButton *toggle,
794   EmpathyStreamedMediaWindow *self)
795 {
796   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
797
798   if (!gtk_toggle_tool_button_get_active (toggle))
799     {
800       if (priv->camera_state == CAMERA_STATE_OFF)
801         {
802           /* We can't change the state by disabling the button */
803           block_camera_control_signals (self);
804           gtk_toggle_tool_button_set_active (toggle, TRUE);
805           unblock_camera_control_signals (self);
806         }
807
808       return;
809     }
810
811   disable_camera (self);
812 }
813
814 static void
815 enable_preview (EmpathyStreamedMediaWindow *self)
816 {
817   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
818
819   if (priv->camera_state == CAMERA_STATE_PREVIEW)
820     return;
821
822   DEBUG ("Enable preview");
823
824   if (priv->camera_state == CAMERA_STATE_ON)
825     {
826       /* preview is already displayed so we just have to stop sending */
827       empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_PREVIEW);
828     }
829   else
830     {
831       display_video_preview (self, TRUE);
832     }
833
834   block_camera_control_signals (self);
835   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
836       priv->tool_button_camera_off), FALSE);
837   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
838         priv->tool_button_camera_on), FALSE);
839
840   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
841         priv->tool_button_camera_preview), TRUE);
842   gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
843       CAMERA_STATE_PREVIEW);
844   unblock_camera_control_signals (self);
845
846   priv->camera_state = CAMERA_STATE_PREVIEW;
847 }
848
849 static void
850 tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
851   EmpathyStreamedMediaWindow *self)
852 {
853   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
854
855   if (!gtk_toggle_tool_button_get_active (toggle))
856     {
857       if (priv->camera_state == CAMERA_STATE_PREVIEW)
858         {
859           /* We can't change the state by disabling the button */
860           block_camera_control_signals (self);
861           gtk_toggle_tool_button_set_active (toggle, TRUE);
862           unblock_camera_control_signals (self);
863         }
864
865       return;
866     }
867
868   enable_preview (self);
869 }
870
871 static void
872 enable_camera (EmpathyStreamedMediaWindow *self)
873 {
874   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
875
876   if (priv->camera_state == CAMERA_STATE_ON)
877     return;
878
879   if (priv->video_input == NULL)
880     {
881       DEBUG ("Can't enable camera, no input");
882       return;
883     }
884
885
886   DEBUG ("Enable camera");
887
888   empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_ON);
889
890   block_camera_control_signals (self);
891   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
892       priv->tool_button_camera_off), FALSE);
893   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
894         priv->tool_button_camera_preview), FALSE);
895
896   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
897       priv->tool_button_camera_on), TRUE);
898   gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
899       CAMERA_STATE_ON);
900   unblock_camera_control_signals (self);
901
902   priv->camera_state = CAMERA_STATE_ON;
903 }
904
905 static void
906 tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
907   EmpathyStreamedMediaWindow *self)
908 {
909   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
910
911   if (!gtk_toggle_tool_button_get_active (toggle))
912     {
913       if (priv->camera_state == CAMERA_STATE_ON)
914         {
915           /* We can't change the state by disabling the button */
916           block_camera_control_signals (self);
917           gtk_toggle_tool_button_set_active (toggle, TRUE);
918           unblock_camera_control_signals (self);
919         }
920
921       return;
922     }
923
924   enable_camera (self);
925 }
926
927 static void
928 action_camera_change_cb (GtkRadioAction *action,
929     GtkRadioAction *current,
930     EmpathyStreamedMediaWindow *self)
931 {
932   CameraState state;
933
934   state = gtk_radio_action_get_current_value (current);
935
936   switch (state)
937     {
938       case CAMERA_STATE_OFF:
939         disable_camera (self);
940         break;
941
942       case CAMERA_STATE_PREVIEW:
943         enable_preview (self);
944         break;
945
946       case CAMERA_STATE_ON:
947         enable_camera (self);
948         break;
949
950       default:
951         g_assert_not_reached ();
952     }
953 }
954
955 static void
956 create_pipeline (EmpathyStreamedMediaWindow *self)
957 {
958   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
959   GstBus *bus;
960
961   g_assert (priv->pipeline == NULL);
962
963   priv->pipeline = gst_pipeline_new (NULL);
964   priv->pipeline_playing = FALSE;
965
966   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
967   priv->bus_message_source_id = gst_bus_add_watch (bus,
968       empathy_streamed_media_window_bus_message, self);
969
970   g_object_unref (bus);
971 }
972
973
974 static void
975 empathy_streamed_media_window_init (EmpathyStreamedMediaWindow *self)
976 {
977   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
978   GtkBuilder *gui;
979   GtkWidget *top_vbox;
980   GtkWidget *h;
981   GtkWidget *arrow;
982   GtkWidget *page;
983   gchar *filename;
984   GKeyFile *keyfile;
985   GError *error = NULL;
986   GtkWidget *scroll;
987
988   filename = empathy_file_lookup ("empathy-streamed-media-window.ui", "src");
989   gui = empathy_builder_get_file (filename,
990     "call_window_vbox", &top_vbox,
991     "errors_vbox", &priv->errors_vbox,
992     "pane", &priv->pane,
993     "statusbar", &priv->statusbar,
994     "redial", &priv->redial_button,
995     "microphone", &priv->mic_button,
996     "toolbar", &priv->toolbar,
997     "menuredial", &priv->redial,
998     "ui_manager", &priv->ui_manager,
999     "menufullscreen", &priv->menu_fullscreen,
1000     "camera_off", &priv->tool_button_camera_off,
1001     "camera_preview", &priv->tool_button_camera_preview,
1002     "camera_on", &priv->tool_button_camera_on,
1003     "action_camera_on",  &priv->action_camera_on,
1004     "details_vbox",  &priv->details_vbox,
1005     "vcodec_encoding_label", &priv->vcodec_encoding_label,
1006     "acodec_encoding_label", &priv->acodec_encoding_label,
1007     "acodec_decoding_label", &priv->acodec_decoding_label,
1008     "vcodec_decoding_label", &priv->vcodec_decoding_label,
1009     "audio_remote_candidate_label", &priv->audio_remote_candidate_label,
1010     "audio_local_candidate_label", &priv->audio_local_candidate_label,
1011     "video_remote_candidate_label", &priv->video_remote_candidate_label,
1012     "video_local_candidate_label", &priv->video_local_candidate_label,
1013     "video_remote_candidate_info_img", &priv->video_remote_candidate_info_img,
1014     "video_local_candidate_info_img", &priv->video_local_candidate_info_img,
1015     "audio_remote_candidate_info_img", &priv->audio_remote_candidate_info_img,
1016     "audio_local_candidate_info_img", &priv->audio_local_candidate_info_img,
1017     NULL);
1018   g_free (filename);
1019
1020   empathy_builder_connect (gui, self,
1021     "menuhangup", "activate", empathy_streamed_media_window_hangup_cb,
1022     "hangup", "clicked", empathy_streamed_media_window_hangup_cb,
1023     "menuredial", "activate", empathy_streamed_media_window_redial_cb,
1024     "redial", "clicked", empathy_streamed_media_window_redial_cb,
1025     "microphone", "toggled", empathy_streamed_media_window_mic_toggled_cb,
1026     "menufullscreen", "activate", empathy_streamed_media_window_fullscreen_cb,
1027     "camera_off", "toggled", tool_button_camera_off_toggled_cb,
1028     "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
1029     "camera_on", "toggled", tool_button_camera_on_toggled_cb,
1030     "action_camera_on", "changed", action_camera_change_cb,
1031     NULL);
1032
1033   gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1034
1035   priv->lock = g_mutex_new ();
1036
1037   gtk_container_add (GTK_CONTAINER (self), top_vbox);
1038
1039   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
1040   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1041                                   CONTENT_HBOX_BORDER_WIDTH);
1042   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
1043
1044   /* remote user output frame */
1045   priv->remote_user_output_frame = gtk_frame_new (NULL);
1046   gtk_widget_set_size_request (priv->remote_user_output_frame,
1047       EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
1048   gtk_box_pack_start (GTK_BOX (priv->content_hbox),
1049       priv->remote_user_output_frame, TRUE, TRUE,
1050       CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1051
1052   priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
1053
1054   priv->remote_user_avatar_widget = gtk_image_new ();
1055
1056   gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
1057       priv->remote_user_avatar_widget, TRUE, TRUE, 0);
1058
1059   gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
1060       priv->remote_user_output_hbox);
1061
1062   /* self user output frame */
1063   priv->self_user_output_frame = gtk_frame_new (NULL);
1064   gtk_widget_set_size_request (priv->self_user_output_frame,
1065       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
1066
1067   priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
1068
1069   priv->self_user_avatar_widget = gtk_image_new ();
1070   gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
1071       priv->self_user_avatar_widget, TRUE, TRUE, 0);
1072
1073   gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
1074       priv->self_user_output_hbox);
1075
1076   create_pipeline (self);
1077   create_video_output_widget (self);
1078   create_audio_input (self);
1079   create_video_input (self);
1080
1081   priv->fsnotifier = fs_element_added_notifier_new ();
1082   fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
1083
1084   /* The call will be started as soon the pipeline is playing */
1085   priv->start_call_when_playing = TRUE;
1086
1087   keyfile = g_key_file_new ();
1088   filename = empathy_file_lookup ("element-properties", "data");
1089   if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
1090     {
1091       fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
1092           keyfile);
1093     }
1094   else
1095     {
1096       g_warning ("Could not load element-properties file: %s", error->message);
1097       g_key_file_free (keyfile);
1098       g_clear_error (&error);
1099     }
1100   g_free (filename);
1101
1102   priv->vbox = gtk_vbox_new (FALSE, 3);
1103   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
1104       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1105   gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
1106       FALSE, FALSE, 0);
1107
1108   empathy_streamed_media_window_setup_toolbar (self);
1109
1110   priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
1111   arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1112   g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
1113     G_CALLBACK (empathy_streamed_media_window_sidebar_toggled_cb), self);
1114
1115   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1116
1117   h = gtk_hbox_new (FALSE, 3);
1118   gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
1119   gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
1120
1121   priv->sidebar = ev_sidebar_new ();
1122   g_signal_connect (G_OBJECT (priv->sidebar),
1123     "hide", G_CALLBACK (empathy_streamed_media_window_sidebar_hidden_cb), self);
1124   g_signal_connect (G_OBJECT (priv->sidebar),
1125     "show", G_CALLBACK (empathy_streamed_media_window_sidebar_shown_cb), self);
1126   gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
1127
1128   page = empathy_streamed_media_window_create_audio_input (self);
1129   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "audio-input",
1130       _("Audio input"), page);
1131
1132   page = empathy_streamed_media_window_create_video_input (self);
1133   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "video-input",
1134       _("Video input"), page);
1135
1136   priv->dtmf_panel = empathy_dialpad_widget_new ();
1137   g_signal_connect (priv->dtmf_panel, "start-tone",
1138       G_CALLBACK (dtmf_start_tone_cb), self);
1139   g_signal_connect (priv->dtmf_panel, "stop-tone",
1140       G_CALLBACK (dtmf_stop_tone_cb), self);
1141   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "dialpad",
1142       _("Dialpad"), priv->dtmf_panel);
1143
1144   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1145
1146   /* Put the details vbox in a scroll window as it can require a lot of
1147    * horizontal space. */
1148   scroll = gtk_scrolled_window_new (NULL, NULL);
1149   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scroll),
1150       priv->details_vbox);
1151
1152   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "details",
1153       _("Details"), scroll);
1154
1155   gtk_widget_show_all (top_vbox);
1156
1157   gtk_widget_hide (priv->sidebar);
1158
1159   priv->fullscreen = empathy_streamed_media_window_fullscreen_new (self);
1160   empathy_streamed_media_window_fullscreen_set_video_widget (priv->fullscreen,
1161       priv->video_output);
1162   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1163       "clicked", G_CALLBACK (empathy_streamed_media_window_fullscreen_cb), self);
1164
1165   g_signal_connect (G_OBJECT (self), "realize",
1166     G_CALLBACK (empathy_streamed_media_window_realized_cb), self);
1167
1168   g_signal_connect (G_OBJECT (self), "delete-event",
1169     G_CALLBACK (empathy_streamed_media_window_delete_cb), self);
1170
1171   g_signal_connect (G_OBJECT (self), "window-state-event",
1172     G_CALLBACK (empathy_streamed_media_window_state_event_cb), self);
1173
1174   g_signal_connect (G_OBJECT (self), "key-press-event",
1175       G_CALLBACK (empathy_streamed_media_window_key_press_cb), self);
1176
1177   priv->timer = g_timer_new ();
1178
1179   g_object_ref (priv->ui_manager);
1180   g_object_unref (gui);
1181
1182   priv->sound_mgr = empathy_sound_manager_dup_singleton ();
1183
1184   empathy_geometry_bind (GTK_WINDOW (self), "av-window");
1185 }
1186
1187 /* Instead of specifying a width and a height, we specify only one size. That's
1188    because we want a square avatar icon.  */
1189 static void
1190 init_contact_avatar_with_size (EmpathyContact *contact,
1191     GtkWidget *image_widget,
1192     gint size)
1193 {
1194   GdkPixbuf *pixbuf_avatar = NULL;
1195
1196   if (contact != NULL)
1197     {
1198       pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1199         size, size);
1200     }
1201
1202   if (pixbuf_avatar == NULL)
1203     {
1204       pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
1205           EMPATHY_IMAGE_AVATAR_DEFAULT, size);
1206     }
1207
1208   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1209
1210   if (pixbuf_avatar != NULL)
1211     g_object_unref (pixbuf_avatar);
1212 }
1213
1214 static void
1215 set_window_title (EmpathyStreamedMediaWindow *self)
1216 {
1217   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1218   gchar *tmp;
1219
1220   /* translators: Call is a noun and %s is the contact name. This string
1221    * is used in the window title */
1222   tmp = g_strdup_printf (_("Call with %s"),
1223       empathy_contact_get_alias (priv->contact));
1224   gtk_window_set_title (GTK_WINDOW (self), tmp);
1225   g_free (tmp);
1226 }
1227
1228 static void
1229 contact_name_changed_cb (EmpathyContact *contact,
1230     GParamSpec *pspec, EmpathyStreamedMediaWindow *self)
1231 {
1232   set_window_title (self);
1233 }
1234
1235 static void
1236 contact_avatar_changed_cb (EmpathyContact *contact,
1237     GParamSpec *pspec, GtkWidget *avatar_widget)
1238 {
1239   int size;
1240   GtkAllocation allocation;
1241
1242   gtk_widget_get_allocation (avatar_widget, &allocation);
1243   size = allocation.height;
1244
1245   if (size == 0)
1246     {
1247       /* the widget is not allocated yet, set a default size */
1248       size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1249           REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1250     }
1251
1252   init_contact_avatar_with_size (contact, avatar_widget, size);
1253 }
1254
1255 static void
1256 empathy_streamed_media_window_got_self_contact_cb (TpConnection *connection,
1257     EmpathyContact *contact, const GError *error, gpointer user_data,
1258     GObject *weak_object)
1259 {
1260   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1261   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1262
1263   init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1264       MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1265
1266   g_signal_connect (contact, "notify::avatar",
1267       G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1268 }
1269
1270 static void
1271 empathy_streamed_media_window_setup_avatars (EmpathyStreamedMediaWindow *self,
1272     EmpathyStreamedMediaHandler *handler)
1273 {
1274   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1275
1276   g_object_get (handler, "contact", &(priv->contact), NULL);
1277
1278   if (priv->contact != NULL)
1279     {
1280       TpConnection *connection;
1281
1282       set_window_title (self);
1283
1284       g_signal_connect (priv->contact, "notify::name",
1285           G_CALLBACK (contact_name_changed_cb), self);
1286       g_signal_connect (priv->contact, "notify::avatar",
1287           G_CALLBACK (contact_avatar_changed_cb),
1288           priv->remote_user_avatar_widget);
1289
1290       /* Retreiving the self avatar */
1291       connection = empathy_contact_get_connection (priv->contact);
1292       empathy_tp_contact_factory_get_from_handle (connection,
1293           tp_connection_get_self_handle (connection),
1294           empathy_streamed_media_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1295     }
1296   else
1297     {
1298       g_warning ("call handler doesn't have a contact");
1299       /* translators: Call is a noun. This string is used in the window
1300        * title */
1301       gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1302
1303       /* Since we can't access the remote contact, we can't get a connection
1304          to it and can't get the self contact (and its avatar). This means
1305          that we have to manually set the self avatar. */
1306       init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1307           MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1308     }
1309
1310   init_contact_avatar_with_size (priv->contact,
1311       priv->remote_user_avatar_widget,
1312       MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1313           REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1314
1315   /* The remote avatar is shown by default and will be hidden when we receive
1316      video from the remote side. */
1317   gtk_widget_hide (priv->video_output);
1318   gtk_widget_show (priv->remote_user_avatar_widget);
1319 }
1320
1321 static void
1322 update_send_codec (EmpathyStreamedMediaWindow *self,
1323     gboolean audio)
1324 {
1325   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1326   FsCodec *codec;
1327   GtkWidget *widget;
1328   gchar *tmp;
1329
1330   if (audio)
1331     {
1332       codec = empathy_streamed_media_handler_get_send_audio_codec (priv->handler);
1333       widget = priv->acodec_encoding_label;
1334     }
1335   else
1336     {
1337       codec = empathy_streamed_media_handler_get_send_video_codec (priv->handler);
1338       widget = priv->vcodec_encoding_label;
1339     }
1340
1341   if (codec == NULL)
1342     return;
1343
1344   tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
1345   gtk_label_set_text (GTK_LABEL (widget), tmp);
1346   g_free (tmp);
1347 }
1348
1349 static void
1350 send_audio_codec_notify_cb (GObject *object,
1351     GParamSpec *pspec,
1352     gpointer user_data)
1353 {
1354   EmpathyStreamedMediaWindow *self = user_data;
1355
1356   update_send_codec (self, TRUE);
1357 }
1358
1359 static void
1360 send_video_codec_notify_cb (GObject *object,
1361     GParamSpec *pspec,
1362     gpointer user_data)
1363 {
1364   EmpathyStreamedMediaWindow *self = user_data;
1365
1366   update_send_codec (self, FALSE);
1367 }
1368
1369 static void
1370 update_recv_codec (EmpathyStreamedMediaWindow *self,
1371     gboolean audio)
1372 {
1373   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1374   GList *codecs, *l;
1375   GtkWidget *widget;
1376   GString *str = NULL;
1377
1378   if (audio)
1379     {
1380       codecs = empathy_streamed_media_handler_get_recv_audio_codecs (priv->handler);
1381       widget = priv->acodec_decoding_label;
1382     }
1383   else
1384     {
1385       codecs = empathy_streamed_media_handler_get_recv_video_codecs (priv->handler);
1386       widget = priv->vcodec_decoding_label;
1387     }
1388
1389   if (codecs == NULL)
1390     return;
1391
1392   for (l = codecs; l != NULL; l = g_list_next (l))
1393     {
1394       FsCodec *codec = l->data;
1395
1396       if (str == NULL)
1397         str = g_string_new (NULL);
1398       else
1399         g_string_append (str, ", ");
1400
1401       g_string_append_printf (str, "%s/%u", codec->encoding_name,
1402           codec->clock_rate);
1403     }
1404
1405   gtk_label_set_text (GTK_LABEL (widget), str->str);
1406   g_string_free (str, TRUE);
1407 }
1408
1409 static void
1410 recv_audio_codecs_notify_cb (GObject *object,
1411     GParamSpec *pspec,
1412     gpointer user_data)
1413 {
1414   EmpathyStreamedMediaWindow *self = user_data;
1415
1416   update_recv_codec (self, TRUE);
1417 }
1418
1419 static void
1420 recv_video_codecs_notify_cb (GObject *object,
1421     GParamSpec *pspec,
1422     gpointer user_data)
1423 {
1424   EmpathyStreamedMediaWindow *self = user_data;
1425
1426   update_recv_codec (self, FALSE);
1427 }
1428
1429 static const gchar *
1430 candidate_type_to_str (FsCandidate *candidate)
1431 {
1432   switch (candidate->type)
1433     {
1434       case FS_CANDIDATE_TYPE_HOST:
1435         return "host";
1436       case FS_CANDIDATE_TYPE_SRFLX:
1437         return "server reflexive";
1438       case FS_CANDIDATE_TYPE_PRFLX:
1439         return "peer reflexive";
1440       case FS_CANDIDATE_TYPE_RELAY:
1441         return "relay";
1442       case FS_CANDIDATE_TYPE_MULTICAST:
1443         return "multicast";
1444     }
1445
1446   return NULL;
1447 }
1448
1449 static const gchar *
1450 candidate_type_to_desc (FsCandidate *candidate)
1451 {
1452   switch (candidate->type)
1453     {
1454       case FS_CANDIDATE_TYPE_HOST:
1455         return _("The IP address as seen by the machine");
1456       case FS_CANDIDATE_TYPE_SRFLX:
1457         return _("The IP address as seen by a server on the Internet");
1458       case FS_CANDIDATE_TYPE_PRFLX:
1459         return _("The IP address of the peer as seen by the other side");
1460       case FS_CANDIDATE_TYPE_RELAY:
1461         return _("The IP address of a relay server");
1462       case FS_CANDIDATE_TYPE_MULTICAST:
1463         return _("The IP address of the multicast group");
1464     }
1465
1466   return NULL;
1467 }
1468
1469 static void
1470 update_candidat_widget (EmpathyStreamedMediaWindow *self,
1471     GtkWidget *label,
1472     GtkWidget *img,
1473     FsCandidate *candidate)
1474 {
1475   gchar *str;
1476
1477   g_assert (candidate != NULL);
1478   str = g_strdup_printf ("%s %u (%s)", candidate->ip,
1479       candidate->port, candidate_type_to_str (candidate));
1480
1481   gtk_label_set_text (GTK_LABEL (label), str);
1482   gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
1483
1484   g_free (str);
1485 }
1486
1487 static void
1488 candidates_changed_cb (GObject *object,
1489     FsMediaType type,
1490     EmpathyStreamedMediaWindow *self)
1491 {
1492   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1493   FsCandidate *candidate = NULL;
1494
1495   if (type == FS_MEDIA_TYPE_VIDEO)
1496     {
1497       /* Update remote candidate */
1498       candidate = empathy_streamed_media_handler_get_video_remote_candidate (
1499           priv->handler);
1500
1501       update_candidat_widget (self, priv->video_remote_candidate_label,
1502           priv->video_remote_candidate_info_img, candidate);
1503
1504       /* Update local candidate */
1505       candidate = empathy_streamed_media_handler_get_video_local_candidate (
1506           priv->handler);
1507
1508       update_candidat_widget (self, priv->video_local_candidate_label,
1509           priv->video_local_candidate_info_img, candidate);
1510     }
1511   else
1512     {
1513       /* Update remote candidate */
1514       candidate = empathy_streamed_media_handler_get_audio_remote_candidate (
1515           priv->handler);
1516
1517       update_candidat_widget (self, priv->audio_remote_candidate_label,
1518           priv->audio_remote_candidate_info_img, candidate);
1519
1520       /* Update local candidate */
1521       candidate = empathy_streamed_media_handler_get_audio_local_candidate (
1522           priv->handler);
1523
1524       update_candidat_widget (self, priv->audio_local_candidate_label,
1525           priv->audio_local_candidate_info_img, candidate);
1526     }
1527 }
1528
1529 static void
1530 empathy_streamed_media_window_constructed (GObject *object)
1531 {
1532   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (object);
1533   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1534   EmpathyTpStreamedMedia *call;
1535
1536   g_assert (priv->handler != NULL);
1537
1538   g_object_get (priv->handler, "tp-call", &call, NULL);
1539   priv->outgoing = (call == NULL);
1540   if (call != NULL)
1541     g_object_unref (call);
1542
1543   empathy_streamed_media_window_setup_avatars (self, priv->handler);
1544   empathy_streamed_media_window_set_state_connecting (self);
1545
1546   if (!empathy_streamed_media_handler_has_initial_video (priv->handler))
1547     {
1548       gtk_toggle_tool_button_set_active (
1549           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1550     }
1551   /* If call has InitialVideo, the preview will be started once the call has
1552    * been started (start_call()). */
1553
1554   update_send_codec (self, TRUE);
1555   update_send_codec (self, FALSE);
1556   update_recv_codec (self, TRUE);
1557   update_recv_codec (self, FALSE);
1558
1559   tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
1560       G_CALLBACK (send_audio_codec_notify_cb), self, 0);
1561   tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
1562       G_CALLBACK (send_video_codec_notify_cb), self, 0);
1563   tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
1564       G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
1565   tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
1566       G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
1567
1568   tp_g_signal_connect_object (priv->handler, "candidates-changed",
1569       G_CALLBACK (candidates_changed_cb), self, 0);
1570 }
1571
1572 static void empathy_streamed_media_window_dispose (GObject *object);
1573 static void empathy_streamed_media_window_finalize (GObject *object);
1574
1575 static void
1576 empathy_streamed_media_window_set_property (GObject *object,
1577   guint property_id, const GValue *value, GParamSpec *pspec)
1578 {
1579   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (object);
1580
1581   switch (property_id)
1582     {
1583       case PROP_STREAMED_MEDIA_HANDLER:
1584         priv->handler = g_value_dup_object (value);
1585         break;
1586       default:
1587         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1588     }
1589 }
1590
1591 static void
1592 empathy_streamed_media_window_get_property (GObject *object,
1593   guint property_id, GValue *value, GParamSpec *pspec)
1594 {
1595   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (object);
1596
1597   switch (property_id)
1598     {
1599       case PROP_STREAMED_MEDIA_HANDLER:
1600         g_value_set_object (value, priv->handler);
1601         break;
1602       default:
1603         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1604     }
1605 }
1606
1607 static void
1608 empathy_streamed_media_window_class_init (
1609   EmpathyStreamedMediaWindowClass *empathy_streamed_media_window_class)
1610 {
1611   GObjectClass *object_class = G_OBJECT_CLASS (empathy_streamed_media_window_class);
1612   GParamSpec *param_spec;
1613
1614   g_type_class_add_private (empathy_streamed_media_window_class,
1615     sizeof (EmpathyStreamedMediaWindowPriv));
1616
1617   object_class->constructed = empathy_streamed_media_window_constructed;
1618   object_class->set_property = empathy_streamed_media_window_set_property;
1619   object_class->get_property = empathy_streamed_media_window_get_property;
1620
1621   object_class->dispose = empathy_streamed_media_window_dispose;
1622   object_class->finalize = empathy_streamed_media_window_finalize;
1623
1624   param_spec = g_param_spec_object ("handler",
1625     "handler", "The call handler",
1626     EMPATHY_TYPE_STREAMED_MEDIA_HANDLER,
1627     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1628   g_object_class_install_property (object_class,
1629     PROP_STREAMED_MEDIA_HANDLER, param_spec);
1630 }
1631
1632 static void
1633 empathy_streamed_media_window_video_stream_changed_cb (EmpathyTpStreamedMedia *call,
1634     GParamSpec *property, EmpathyStreamedMediaWindow *self)
1635 {
1636   DEBUG ("video stream changed");
1637   empathy_streamed_media_window_update_avatars_visibility (call, self);
1638 }
1639
1640 void
1641 empathy_streamed_media_window_dispose (GObject *object)
1642 {
1643   EmpathyTpStreamedMedia *call;
1644   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (object);
1645   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1646
1647   if (priv->dispose_has_run)
1648     return;
1649
1650   priv->dispose_has_run = TRUE;
1651
1652   g_object_get (priv->handler, "tp-call", &call, NULL);
1653
1654   if (call != NULL)
1655     {
1656       g_object_unref (call);
1657     }
1658
1659   if (priv->handler != NULL)
1660     {
1661       empathy_streamed_media_handler_stop_call (priv->handler);
1662       g_object_unref (priv->handler);
1663     }
1664   priv->handler = NULL;
1665
1666   if (priv->bus_message_source_id != 0)
1667     {
1668       g_source_remove (priv->bus_message_source_id);
1669       priv->bus_message_source_id = 0;
1670     }
1671
1672   if (priv->pipeline != NULL)
1673     g_object_unref (priv->pipeline);
1674   priv->pipeline = NULL;
1675
1676   if (priv->video_input != NULL)
1677     g_object_unref (priv->video_input);
1678   priv->video_input = NULL;
1679
1680   if (priv->audio_input != NULL)
1681     g_object_unref (priv->audio_input);
1682   priv->audio_input = NULL;
1683
1684   if (priv->video_tee != NULL)
1685     g_object_unref (priv->video_tee);
1686   priv->video_tee = NULL;
1687
1688   if (priv->fsnotifier != NULL)
1689     g_object_unref (priv->fsnotifier);
1690   priv->fsnotifier = NULL;
1691
1692   if (priv->timer_id != 0)
1693     g_source_remove (priv->timer_id);
1694   priv->timer_id = 0;
1695
1696   if (priv->ui_manager != NULL)
1697     g_object_unref (priv->ui_manager);
1698   priv->ui_manager = NULL;
1699
1700   if (priv->fullscreen != NULL)
1701     g_object_unref (priv->fullscreen);
1702   priv->fullscreen = NULL;
1703
1704   if (priv->contact != NULL)
1705     {
1706       g_signal_handlers_disconnect_by_func (priv->contact,
1707           contact_name_changed_cb, self);
1708       g_object_unref (priv->contact);
1709       priv->contact = NULL;
1710     }
1711
1712   tp_clear_object (&priv->sound_mgr);
1713
1714   /* release any references held by the object here */
1715   if (G_OBJECT_CLASS (empathy_streamed_media_window_parent_class)->dispose)
1716     G_OBJECT_CLASS (empathy_streamed_media_window_parent_class)->dispose (object);
1717 }
1718
1719 static void
1720 disconnect_video_output_motion_handler (EmpathyStreamedMediaWindow *self)
1721 {
1722   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1723
1724   if (priv->video_output_motion_handler_id != 0)
1725     {
1726       g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1727           priv->video_output_motion_handler_id);
1728       priv->video_output_motion_handler_id = 0;
1729     }
1730 }
1731
1732 void
1733 empathy_streamed_media_window_finalize (GObject *object)
1734 {
1735   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (object);
1736   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1737
1738   disconnect_video_output_motion_handler (self);
1739
1740   /* free any data held directly by the object here */
1741   g_mutex_free (priv->lock);
1742
1743   g_timer_destroy (priv->timer);
1744
1745   G_OBJECT_CLASS (empathy_streamed_media_window_parent_class)->finalize (object);
1746 }
1747
1748
1749 EmpathyStreamedMediaWindow *
1750 empathy_streamed_media_window_new (EmpathyStreamedMediaHandler *handler)
1751 {
1752   return EMPATHY_STREAMED_MEDIA_WINDOW (
1753     g_object_new (EMPATHY_TYPE_STREAMED_MEDIA_WINDOW, "handler", handler, NULL));
1754 }
1755
1756 static void
1757 empathy_streamed_media_window_conference_added_cb (EmpathyStreamedMediaHandler *handler,
1758   GstElement *conference, gpointer user_data)
1759 {
1760   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1761   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1762
1763   gst_bin_add (GST_BIN (priv->pipeline), conference);
1764
1765   gst_element_set_state (conference, GST_STATE_PLAYING);
1766 }
1767
1768 static gboolean
1769 empathy_streamed_media_window_request_resource_cb (EmpathyStreamedMediaHandler *handler,
1770   FsMediaType type, FsStreamDirection direction, gpointer user_data)
1771 {
1772   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1773   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1774
1775   if (type != FS_MEDIA_TYPE_VIDEO)
1776     return TRUE;
1777
1778   if (direction == FS_DIRECTION_RECV)
1779     return TRUE;
1780
1781   /* video and direction is send */
1782   return priv->video_input != NULL;
1783 }
1784
1785 static gboolean
1786 empathy_streamed_media_window_reset_pipeline (EmpathyStreamedMediaWindow *self)
1787 {
1788   GstStateChangeReturn state_change_return;
1789   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1790
1791   if (priv->pipeline == NULL)
1792     return TRUE;
1793
1794   if (priv->bus_message_source_id != 0)
1795     {
1796       g_source_remove (priv->bus_message_source_id);
1797       priv->bus_message_source_id = 0;
1798     }
1799
1800   state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1801
1802   if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1803         state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1804     {
1805       if (priv->pipeline != NULL)
1806         g_object_unref (priv->pipeline);
1807       priv->pipeline = NULL;
1808
1809       g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1810           empathy_streamed_media_window_mic_volume_changed_cb, self);
1811
1812       if (priv->video_tee != NULL)
1813         g_object_unref (priv->video_tee);
1814       priv->video_tee = NULL;
1815
1816       if (priv->video_preview != NULL)
1817         gtk_widget_destroy (priv->video_preview);
1818       priv->video_preview = NULL;
1819
1820       priv->funnel = NULL;
1821
1822       create_pipeline (self);
1823       /* Call will be started when user will hit the 'redial' button */
1824       priv->start_call_when_playing = FALSE;
1825       gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1826
1827       return TRUE;
1828     }
1829   else
1830     {
1831       g_message ("Error: could not destroy pipeline. Closing call window");
1832       gtk_widget_destroy (GTK_WIDGET (self));
1833
1834       return FALSE;
1835     }
1836 }
1837
1838 static void
1839 reset_details_pane (EmpathyStreamedMediaWindow *self)
1840 {
1841   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1842
1843   /* translators: encoding video codec is unknown */
1844   gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label),
1845       C_("codec", "Unknown"));
1846   /* translators: encoding audio codec is unknown */
1847   gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label),
1848       C_("codec", "Unknown"));
1849   /* translators: decoding video codec is unknown */
1850   gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label),
1851       C_("codec", "Unknown"));
1852   /* translators: decoding audio codec is unknown */
1853   gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label),
1854       C_("codec", "Unknown"));
1855 }
1856
1857 static gboolean
1858 empathy_streamed_media_window_disconnected (EmpathyStreamedMediaWindow *self,
1859     gboolean restart)
1860 {
1861   gboolean could_disconnect = FALSE;
1862   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1863   gboolean could_reset_pipeline;
1864
1865   /* Leave full screen mode if needed */
1866   gtk_window_unfullscreen (GTK_WINDOW (self));
1867
1868   gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1869
1870   could_reset_pipeline = empathy_streamed_media_window_reset_pipeline (self);
1871
1872   if (priv->call_state == CONNECTING)
1873       empathy_sound_manager_stop (priv->sound_mgr,
1874           EMPATHY_SOUND_PHONE_OUTGOING);
1875
1876   if (priv->call_state != REDIALING)
1877     priv->call_state = DISCONNECTED;
1878
1879   if (could_reset_pipeline)
1880     {
1881       g_mutex_lock (priv->lock);
1882
1883       g_timer_stop (priv->timer);
1884
1885       if (priv->timer_id != 0)
1886         g_source_remove (priv->timer_id);
1887       priv->timer_id = 0;
1888
1889       g_mutex_unlock (priv->lock);
1890
1891       if (!restart)
1892         /* We are about to destroy the window, no need to update it or create
1893          * a video preview */
1894         return TRUE;
1895
1896       empathy_streamed_media_window_status_message (self, _("Disconnected"));
1897
1898       gtk_action_set_sensitive (priv->redial, TRUE);
1899       gtk_widget_set_sensitive (priv->redial_button, TRUE);
1900
1901       /* Unsensitive the camera and mic button */
1902       gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1903       gtk_action_set_sensitive (priv->action_camera_on, FALSE);
1904       gtk_widget_set_sensitive (priv->mic_button, FALSE);
1905
1906       /* Be sure that the mic button is enabled */
1907       gtk_toggle_tool_button_set_active (
1908           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1909
1910       if (priv->camera_state == CAMERA_STATE_ON)
1911         {
1912           /* Enable the 'preview' button as we are not sending atm. */
1913           gtk_toggle_tool_button_set_active (
1914               GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_preview), TRUE);
1915         }
1916       else if (priv->camera_state == CAMERA_STATE_PREVIEW)
1917         {
1918           /* Restart the preview with the new pipeline. */
1919           display_video_preview (self, TRUE);
1920         }
1921
1922       gtk_progress_bar_set_fraction (
1923           GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1924
1925       /* destroy the video output; it will be recreated when we'll redial */
1926       disconnect_video_output_motion_handler (self);
1927       gtk_widget_destroy (priv->video_output);
1928       priv->video_output = NULL;
1929
1930       gtk_widget_show (priv->remote_user_avatar_widget);
1931
1932       reset_details_pane (self);
1933
1934       priv->sending_video = FALSE;
1935       priv->call_started = FALSE;
1936
1937       could_disconnect = TRUE;
1938
1939       /* TODO: display the self avatar of the preview (depends if the "Always
1940        * Show Video Preview" is enabled or not) */
1941     }
1942
1943   return could_disconnect;
1944 }
1945
1946
1947 static void
1948 empathy_streamed_media_window_channel_closed_cb (EmpathyStreamedMediaHandler *handler,
1949     gpointer user_data)
1950 {
1951   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1952   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1953
1954   if (empathy_streamed_media_window_disconnected (self, TRUE) &&
1955       priv->call_state == REDIALING)
1956       empathy_streamed_media_window_restart_call (self);
1957 }
1958
1959
1960 static void
1961 empathy_streamed_media_window_channel_stream_closed_cb (EmpathyStreamedMediaHandler *handler,
1962     TfStream *stream, gpointer user_data)
1963 {
1964   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1965   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1966   guint media_type;
1967
1968   g_object_get (stream, "media-type", &media_type, NULL);
1969
1970   /*
1971    * This assumes that there is only one video stream per channel...
1972    */
1973
1974   if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1975     {
1976       if (priv->funnel != NULL)
1977         {
1978           GstElement *output;
1979
1980           output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1981               (priv->video_output));
1982
1983           gst_element_set_state (output, GST_STATE_NULL);
1984           gst_element_set_state (priv->funnel, GST_STATE_NULL);
1985
1986           gst_bin_remove (GST_BIN (priv->pipeline), output);
1987           gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1988           priv->funnel = NULL;
1989         }
1990     }
1991   else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1992     {
1993       if (priv->audio_output != NULL)
1994         {
1995           gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1996
1997           gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1998           priv->audio_output = NULL;
1999         }
2000     }
2001 }
2002
2003 /* Called with global lock held */
2004 static GstPad *
2005 empathy_streamed_media_window_get_video_sink_pad (EmpathyStreamedMediaWindow *self)
2006 {
2007   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2008   GstPad *pad;
2009   GstElement *output;
2010
2011   if (priv->funnel == NULL)
2012     {
2013       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
2014         (priv->video_output));
2015
2016       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
2017
2018       if (!priv->funnel)
2019         {
2020           g_warning ("Could not create fsfunnel");
2021           return NULL;
2022         }
2023
2024       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
2025         {
2026           gst_object_unref (priv->funnel);
2027           priv->funnel = NULL;
2028           g_warning ("Could  not add funnel to pipeline");
2029           return NULL;
2030         }
2031
2032       if (!gst_bin_add (GST_BIN (priv->pipeline), output))
2033         {
2034           g_warning ("Could not add the video output widget to the pipeline");
2035           goto error;
2036         }
2037
2038       if (!gst_element_link (priv->funnel, output))
2039         {
2040           g_warning ("Could not link output sink to funnel");
2041           goto error_output_added;
2042         }
2043
2044       if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2045         {
2046           g_warning ("Could not start video sink");
2047           goto error_output_added;
2048         }
2049
2050       if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2051         {
2052           g_warning ("Could not start funnel");
2053           goto error_output_added;
2054         }
2055     }
2056
2057   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
2058
2059   if (!pad)
2060     g_warning ("Could not get request pad from funnel");
2061
2062   return pad;
2063
2064
2065  error_output_added:
2066
2067   gst_element_set_locked_state (priv->funnel, TRUE);
2068   gst_element_set_locked_state (output, TRUE);
2069
2070   gst_element_set_state (priv->funnel, GST_STATE_NULL);
2071   gst_element_set_state (output, GST_STATE_NULL);
2072
2073   gst_bin_remove (GST_BIN (priv->pipeline), output);
2074   gst_element_set_locked_state (output, FALSE);
2075
2076  error:
2077
2078   gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
2079   priv->funnel = NULL;
2080
2081   return NULL;
2082 }
2083
2084 /* Called with global lock held */
2085 static GstPad *
2086 empathy_streamed_media_window_get_audio_sink_pad (EmpathyStreamedMediaWindow *self)
2087 {
2088   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2089   GstPad *pad;
2090   GstPadTemplate *template;
2091
2092   if (priv->audio_output == NULL)
2093     {
2094       priv->audio_output = empathy_audio_sink_new ();
2095
2096       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
2097         {
2098           g_warning ("Could not add audio sink to pipeline");
2099           g_object_unref (priv->audio_output);
2100           goto error_add_output;
2101         }
2102
2103       if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2104         {
2105           g_warning ("Could not start audio sink");
2106           goto error;
2107         }
2108     }
2109
2110   template = gst_element_class_get_pad_template (
2111     GST_ELEMENT_GET_CLASS (priv->audio_output), "sink%d");
2112
2113   pad = gst_element_request_pad (priv->audio_output,
2114     template, NULL, NULL);
2115
2116   if (pad == NULL)
2117     {
2118       g_warning ("Could not get sink pad from sink");
2119       return NULL;
2120     }
2121
2122   return pad;
2123
2124 error:
2125   gst_element_set_locked_state (priv->audio_output, TRUE);
2126   gst_element_set_state (priv->audio_output, GST_STATE_NULL);
2127   gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
2128   priv->audio_output = NULL;
2129
2130 error_add_output:
2131
2132   return NULL;
2133 }
2134
2135 static gboolean
2136 empathy_streamed_media_window_update_timer (gpointer user_data)
2137 {
2138   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2139   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2140   gchar *str;
2141   gdouble time_;
2142
2143   time_ = g_timer_elapsed (priv->timer, NULL);
2144
2145   /* Translators: number of minutes:seconds the caller has been connected */
2146   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time_ / 60,
2147     (int) time_ % 60);
2148   empathy_streamed_media_window_status_message (self, str);
2149   g_free (str);
2150
2151   return TRUE;
2152 }
2153
2154 static void
2155 display_error (EmpathyStreamedMediaWindow *self,
2156     EmpathyTpStreamedMedia *call,
2157     const gchar *img,
2158     const gchar *title,
2159     const gchar *desc,
2160     const gchar *details)
2161 {
2162   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2163   GtkWidget *info_bar;
2164   GtkWidget *content_area;
2165   GtkWidget *hbox;
2166   GtkWidget *vbox;
2167   GtkWidget *image;
2168   GtkWidget *label;
2169   gchar *txt;
2170
2171   /* Create info bar */
2172   info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
2173       NULL);
2174
2175   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
2176
2177   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2178
2179   /* hbox containing the image and the messages vbox */
2180   hbox = gtk_hbox_new (FALSE, 3);
2181   gtk_container_add (GTK_CONTAINER (content_area), hbox);
2182
2183   /* Add image */
2184   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
2185   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2186
2187   /* vbox containing the main message and the details expander */
2188   vbox = gtk_vbox_new (FALSE, 3);
2189   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
2190
2191   /* Add text */
2192   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
2193
2194   label = gtk_label_new (NULL);
2195   gtk_label_set_markup (GTK_LABEL (label), txt);
2196   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2197   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2198   g_free (txt);
2199
2200   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
2201
2202   /* Add details */
2203   if (details != NULL)
2204     {
2205       GtkWidget *expander;
2206
2207       expander = gtk_expander_new (_("Technical Details"));
2208
2209       txt = g_strdup_printf ("<i>%s</i>", details);
2210
2211       label = gtk_label_new (NULL);
2212       gtk_label_set_markup (GTK_LABEL (label), txt);
2213       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2214       gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2215       g_free (txt);
2216
2217       gtk_container_add (GTK_CONTAINER (expander), label);
2218       gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
2219     }
2220
2221   g_signal_connect (info_bar, "response",
2222       G_CALLBACK (gtk_widget_destroy), NULL);
2223
2224   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
2225       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
2226   gtk_widget_show_all (info_bar);
2227 }
2228
2229 static gchar *
2230 media_stream_error_to_txt (EmpathyStreamedMediaWindow *self,
2231     EmpathyTpStreamedMedia *call,
2232     gboolean audio,
2233     TpMediaStreamError error)
2234 {
2235   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2236   const gchar *cm;
2237   gchar *url;
2238   gchar *result;
2239
2240   switch (error)
2241     {
2242       case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
2243         if (audio)
2244           return g_strdup_printf (
2245               _("%s's software does not understand any of the audio formats "
2246                 "supported by your computer"),
2247             empathy_contact_get_alias (priv->contact));
2248         else
2249           return g_strdup_printf (
2250               _("%s's software does not understand any of the video formats "
2251                 "supported by your computer"),
2252             empathy_contact_get_alias (priv->contact));
2253
2254       case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
2255         return g_strdup_printf (
2256             _("Can't establish a connection to %s. "
2257               "One of you might be on a network that does not allow "
2258               "direct connections."),
2259           empathy_contact_get_alias (priv->contact));
2260
2261       case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
2262           return g_strdup (_("There was a failure on the network"));
2263
2264       case TP_MEDIA_STREAM_ERROR_NO_CODECS:
2265         if (audio)
2266           return g_strdup (_("The audio formats necessary for this call "
2267                 "are not installed on your computer"));
2268         else
2269           return g_strdup (_("The video formats necessary for this call "
2270                 "are not installed on your computer"));
2271
2272       case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
2273         cm = empathy_tp_streamed_media_get_connection_manager (call);
2274
2275         url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
2276             "product=Telepathy&amp;component=%s", cm);
2277
2278         result = g_strdup_printf (
2279             _("Something unexpected happened in a Telepathy component. "
2280               "Please <a href=\"%s\">report this bug</a> and attach "
2281               "logs gathered from the 'Debug' window in the Help menu."), url);
2282
2283         g_free (url);
2284         return result;
2285
2286       case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
2287         return g_strdup (_("There was a failure in the call engine"));
2288
2289       case TP_MEDIA_STREAM_ERROR_EOS:
2290         return g_strdup (_("The end of the stream was reached"));
2291
2292       case TP_MEDIA_STREAM_ERROR_UNKNOWN:
2293       default:
2294         return NULL;
2295     }
2296 }
2297
2298 static void
2299 empathy_streamed_media_window_stream_error (EmpathyStreamedMediaWindow *self,
2300     EmpathyTpStreamedMedia *call,
2301     gboolean audio,
2302     guint code,
2303     const gchar *msg,
2304     const gchar *icon,
2305     const gchar *title)
2306 {
2307   gchar *desc;
2308
2309   desc = media_stream_error_to_txt (self, call, audio, code);
2310   if (desc == NULL)
2311     {
2312       /* No description, use the error message. That's not great as it's not
2313        * localized but it's better than nothing. */
2314       display_error (self, call, icon, title, msg, NULL);
2315     }
2316   else
2317     {
2318       display_error (self, call, icon, title, desc, msg);
2319       g_free (desc);
2320     }
2321 }
2322
2323 static void
2324 empathy_streamed_media_window_audio_stream_error (EmpathyTpStreamedMedia *call,
2325     guint code,
2326     const gchar *msg,
2327     EmpathyStreamedMediaWindow *self)
2328 {
2329   empathy_streamed_media_window_stream_error (self, call, TRUE, code, msg,
2330       "gnome-stock-mic", _("Can't establish audio stream"));
2331 }
2332
2333 static void
2334 empathy_streamed_media_window_video_stream_error (EmpathyTpStreamedMedia *call,
2335     guint code,
2336     const gchar *msg,
2337     EmpathyStreamedMediaWindow *self)
2338 {
2339   empathy_streamed_media_window_stream_error (self, call, FALSE, code, msg,
2340       "camera-web", _("Can't establish video stream"));
2341 }
2342
2343 static gboolean
2344 empathy_streamed_media_window_connected (gpointer user_data)
2345 {
2346   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2347   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2348   EmpathyTpStreamedMedia *call;
2349   gboolean can_send_video;
2350
2351   empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2352
2353   can_send_video = priv->video_input != NULL && priv->contact != NULL &&
2354     empathy_contact_can_voip_video (priv->contact);
2355
2356   g_object_get (priv->handler, "tp-call", &call, NULL);
2357
2358   tp_g_signal_connect_object (call, "notify::video-stream",
2359     G_CALLBACK (empathy_streamed_media_window_video_stream_changed_cb),
2360     self, 0);
2361
2362   if (empathy_tp_streamed_media_has_dtmf (call))
2363     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2364
2365   if (priv->video_input == NULL)
2366     empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_OFF);
2367
2368   priv->sending_video = can_send_video ?
2369     empathy_tp_streamed_media_is_sending_video (call) : FALSE;
2370
2371   gtk_toggle_tool_button_set_active (
2372       GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
2373       priv->sending_video && priv->video_input != NULL);
2374   gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
2375   gtk_action_set_sensitive (priv->action_camera_on, can_send_video);
2376
2377   gtk_action_set_sensitive (priv->redial, FALSE);
2378   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2379
2380   gtk_widget_set_sensitive (priv->mic_button, TRUE);
2381
2382   empathy_streamed_media_window_update_avatars_visibility (call, self);
2383
2384   g_object_unref (call);
2385
2386   g_mutex_lock (priv->lock);
2387
2388   priv->timer_id = g_timeout_add_seconds (1,
2389     empathy_streamed_media_window_update_timer, self);
2390
2391   g_mutex_unlock (priv->lock);
2392
2393   empathy_streamed_media_window_update_timer (self);
2394
2395   gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
2396
2397   return FALSE;
2398 }
2399
2400
2401 /* Called from the streaming thread */
2402 static gboolean
2403 empathy_streamed_media_window_src_added_cb (EmpathyStreamedMediaHandler *handler,
2404   GstPad *src, guint media_type, gpointer user_data)
2405 {
2406   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2407   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2408   gboolean retval = FALSE;
2409
2410   GstPad *pad;
2411
2412   g_mutex_lock (priv->lock);
2413
2414   if (priv->call_state != CONNECTED)
2415     {
2416       g_timer_start (priv->timer);
2417       priv->timer_id = g_idle_add  (empathy_streamed_media_window_connected, self);
2418       priv->call_state = CONNECTED;
2419     }
2420
2421   switch (media_type)
2422     {
2423       case TP_MEDIA_STREAM_TYPE_AUDIO:
2424         pad = empathy_streamed_media_window_get_audio_sink_pad (self);
2425         break;
2426       case TP_MEDIA_STREAM_TYPE_VIDEO:
2427         gtk_widget_hide (priv->remote_user_avatar_widget);
2428         gtk_widget_show (priv->video_output);
2429         pad = empathy_streamed_media_window_get_video_sink_pad (self);
2430         break;
2431       default:
2432         g_assert_not_reached ();
2433     }
2434
2435   if (pad == NULL)
2436     goto out;
2437
2438   if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2439       g_warning ("Could not link %s sink pad",
2440           media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2441   else
2442       retval = TRUE;
2443
2444   gst_object_unref (pad);
2445
2446  out:
2447
2448   /* If no sink could be linked, try to add fakesink to prevent the whole call
2449    * aborting */
2450
2451   if (!retval)
2452     {
2453       GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2454
2455       if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2456         {
2457           GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2458           if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2459               GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2460             {
2461               gst_element_set_locked_state (fakesink, TRUE);
2462               gst_element_set_state (fakesink, GST_STATE_NULL);
2463               gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2464             }
2465           else
2466             {
2467               DEBUG ("Could not link real sink, linked fakesink instead");
2468             }
2469           gst_object_unref (sinkpad);
2470         }
2471       else
2472         {
2473           gst_object_unref (fakesink);
2474         }
2475     }
2476
2477
2478   g_mutex_unlock (priv->lock);
2479
2480   return TRUE;
2481 }
2482
2483 static gboolean
2484 empathy_streamed_media_window_sink_added_cb (EmpathyStreamedMediaHandler *handler,
2485   GstPad *sink, guint media_type, gpointer user_data)
2486 {
2487   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2488   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2489   GstPad *pad;
2490   gboolean retval = FALSE;
2491
2492   switch (media_type)
2493     {
2494       case TP_MEDIA_STREAM_TYPE_AUDIO:
2495         if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2496           {
2497             g_warning ("Could not add audio source to pipeline");
2498             break;
2499           }
2500
2501         pad = gst_element_get_static_pad (priv->audio_input, "src");
2502         if (!pad)
2503           {
2504             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2505             g_warning ("Could not get source pad from audio source");
2506             break;
2507           }
2508
2509         if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2510           {
2511             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2512             g_warning ("Could not link audio source to farsight");
2513             break;
2514           }
2515
2516         if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2517           {
2518             g_warning ("Could not start audio source");
2519             gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2520             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2521             break;
2522           }
2523
2524         retval = TRUE;
2525         break;
2526       case TP_MEDIA_STREAM_TYPE_VIDEO:
2527         if (priv->video_input != NULL)
2528           {
2529             if (priv->video_tee != NULL)
2530               {
2531                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2532                 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2533                   {
2534                     g_warning ("Could not link videp soure input pipeline");
2535                     break;
2536                   }
2537                 gst_object_unref (pad);
2538               }
2539
2540             retval = TRUE;
2541           }
2542         break;
2543       default:
2544         g_assert_not_reached ();
2545     }
2546
2547   return retval;
2548 }
2549
2550 static void
2551 empathy_streamed_media_window_remove_video_input (EmpathyStreamedMediaWindow *self)
2552 {
2553   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2554   GstElement *preview;
2555
2556   disable_camera (self);
2557
2558   DEBUG ("remove video input");
2559   preview = empathy_video_widget_get_element (
2560     EMPATHY_VIDEO_WIDGET (priv->video_preview));
2561
2562   gst_element_set_state (priv->video_input, GST_STATE_NULL);
2563   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2564   gst_element_set_state (preview, GST_STATE_NULL);
2565
2566   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2567     priv->video_tee, preview, NULL);
2568
2569   g_object_unref (priv->video_input);
2570   priv->video_input = NULL;
2571   g_object_unref (priv->video_tee);
2572   priv->video_tee = NULL;
2573   gtk_widget_destroy (priv->video_preview);
2574   priv->video_preview = NULL;
2575
2576   gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
2577   gtk_action_set_sensitive (priv->action_camera_on, FALSE);
2578   gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
2579 }
2580
2581 static void
2582 start_call (EmpathyStreamedMediaWindow *self)
2583 {
2584   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2585
2586   priv->call_started = TRUE;
2587   empathy_streamed_media_handler_start_call (priv->handler,
2588       empathy_get_current_action_time ());
2589
2590   if (empathy_streamed_media_handler_has_initial_video (priv->handler))
2591     {
2592       /* Enable 'send video' buttons and display the preview */
2593       gtk_toggle_tool_button_set_active (
2594           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
2595     }
2596 }
2597
2598 static gboolean
2599 empathy_streamed_media_window_bus_message (GstBus *bus, GstMessage *message,
2600   gpointer user_data)
2601 {
2602   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2603   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2604   GstState newstate;
2605
2606   empathy_streamed_media_handler_bus_message (priv->handler, bus, message);
2607
2608   switch (GST_MESSAGE_TYPE (message))
2609     {
2610       case GST_MESSAGE_STATE_CHANGED:
2611         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2612           {
2613             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2614             if (newstate == GST_STATE_PAUSED)
2615                 empathy_streamed_media_window_setup_video_input (self);
2616           }
2617         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2618             !priv->call_started)
2619           {
2620             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2621             if (newstate == GST_STATE_PAUSED)
2622               {
2623                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2624                 priv->pipeline_playing = TRUE;
2625
2626                 if (priv->start_call_when_playing)
2627                   start_call (self);
2628               }
2629           }
2630         break;
2631       case GST_MESSAGE_ERROR:
2632         {
2633           GError *error = NULL;
2634           GstElement *gst_error;
2635           gchar *debug;
2636
2637           gst_message_parse_error (message, &error, &debug);
2638           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2639
2640           g_message ("Element error: %s -- %s\n", error->message, debug);
2641
2642           if (g_str_has_prefix (gst_element_get_name (gst_error),
2643                 VIDEO_INPUT_ERROR_PREFIX))
2644             {
2645               /* Remove the video input and continue */
2646               if (priv->video_input != NULL)
2647                 empathy_streamed_media_window_remove_video_input (self);
2648               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2649             }
2650           else
2651             {
2652               empathy_streamed_media_window_disconnected (self, TRUE);
2653             }
2654           g_error_free (error);
2655           g_free (debug);
2656         }
2657       case GST_MESSAGE_UNKNOWN:
2658       case GST_MESSAGE_EOS:
2659       case GST_MESSAGE_WARNING:
2660       case GST_MESSAGE_INFO:
2661       case GST_MESSAGE_TAG:
2662       case GST_MESSAGE_BUFFERING:
2663       case GST_MESSAGE_STATE_DIRTY:
2664       case GST_MESSAGE_STEP_DONE:
2665       case GST_MESSAGE_CLOCK_PROVIDE:
2666       case GST_MESSAGE_CLOCK_LOST:
2667       case GST_MESSAGE_NEW_CLOCK:
2668       case GST_MESSAGE_STRUCTURE_CHANGE:
2669       case GST_MESSAGE_STREAM_STATUS:
2670       case GST_MESSAGE_APPLICATION:
2671       case GST_MESSAGE_ELEMENT:
2672       case GST_MESSAGE_SEGMENT_START:
2673       case GST_MESSAGE_SEGMENT_DONE:
2674       case GST_MESSAGE_DURATION:
2675       case GST_MESSAGE_ANY:
2676       default:
2677         break;
2678     }
2679
2680   return TRUE;
2681 }
2682
2683 static void
2684 empathy_streamed_media_window_update_avatars_visibility (EmpathyTpStreamedMedia *call,
2685     EmpathyStreamedMediaWindow *window)
2686 {
2687   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2688
2689   if (empathy_tp_streamed_media_is_receiving_video (call))
2690     {
2691       gtk_widget_hide (priv->remote_user_avatar_widget);
2692       gtk_widget_show (priv->video_output);
2693     }
2694   else
2695     {
2696       gtk_widget_hide (priv->video_output);
2697       gtk_widget_show (priv->remote_user_avatar_widget);
2698     }
2699 }
2700
2701 static void
2702 call_handler_notify_tp_streamed_media_cb (EmpathyStreamedMediaHandler *handler,
2703     GParamSpec *spec,
2704     EmpathyStreamedMediaWindow *self)
2705 {
2706   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2707   EmpathyTpStreamedMedia *call;
2708
2709   g_object_get (priv->handler, "tp-call", &call, NULL);
2710   if (call == NULL)
2711     return;
2712
2713   tp_g_signal_connect_object (call, "audio-stream-error",
2714       G_CALLBACK (empathy_streamed_media_window_audio_stream_error), self, 0);
2715   tp_g_signal_connect_object (call, "video-stream-error",
2716       G_CALLBACK (empathy_streamed_media_window_video_stream_error), self, 0);
2717
2718   g_object_unref (call);
2719 }
2720
2721 static void
2722 empathy_streamed_media_window_realized_cb (GtkWidget *widget, EmpathyStreamedMediaWindow *window)
2723 {
2724   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2725   EmpathyTpStreamedMedia *call;
2726
2727   g_signal_connect (priv->handler, "conference-added",
2728     G_CALLBACK (empathy_streamed_media_window_conference_added_cb), window);
2729   g_signal_connect (priv->handler, "request-resource",
2730     G_CALLBACK (empathy_streamed_media_window_request_resource_cb), window);
2731   g_signal_connect (priv->handler, "closed",
2732     G_CALLBACK (empathy_streamed_media_window_channel_closed_cb), window);
2733   g_signal_connect (priv->handler, "src-pad-added",
2734     G_CALLBACK (empathy_streamed_media_window_src_added_cb), window);
2735   g_signal_connect (priv->handler, "sink-pad-added",
2736     G_CALLBACK (empathy_streamed_media_window_sink_added_cb), window);
2737   g_signal_connect (priv->handler, "stream-closed",
2738     G_CALLBACK (empathy_streamed_media_window_channel_stream_closed_cb), window);
2739
2740   g_object_get (priv->handler, "tp-call", &call, NULL);
2741   if (call != NULL)
2742     {
2743       tp_g_signal_connect_object (call, "audio-stream-error",
2744         G_CALLBACK (empathy_streamed_media_window_audio_stream_error), window,
2745         0);
2746       tp_g_signal_connect_object (call, "video-stream-error",
2747         G_CALLBACK (empathy_streamed_media_window_video_stream_error), window,
2748         0);
2749
2750       g_object_unref (call);
2751     }
2752   else
2753     {
2754       /* tp-call doesn't exist yet, we'll connect signals once it has been
2755        * set */
2756       g_signal_connect (priv->handler, "notify::tp-call",
2757         G_CALLBACK (call_handler_notify_tp_streamed_media_cb), window);
2758     }
2759
2760   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2761 }
2762
2763 static gboolean
2764 empathy_streamed_media_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2765   EmpathyStreamedMediaWindow *window)
2766 {
2767   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2768
2769   if (priv->pipeline != NULL)
2770     {
2771       if (priv->bus_message_source_id != 0)
2772         {
2773           g_source_remove (priv->bus_message_source_id);
2774           priv->bus_message_source_id = 0;
2775         }
2776
2777       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2778     }
2779
2780   if (priv->call_state == CONNECTING)
2781     empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2782
2783   return FALSE;
2784 }
2785
2786 static void
2787 show_controls (EmpathyStreamedMediaWindow *window, gboolean set_fullscreen)
2788 {
2789   GtkWidget *menu;
2790   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2791
2792   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2793             "/menubar1");
2794
2795   if (set_fullscreen)
2796     {
2797       gtk_widget_hide (priv->sidebar);
2798       gtk_widget_hide (menu);
2799       gtk_widget_hide (priv->vbox);
2800       gtk_widget_hide (priv->statusbar);
2801       gtk_widget_hide (priv->toolbar);
2802     }
2803   else
2804     {
2805       if (priv->sidebar_was_visible_before_fs)
2806         gtk_widget_show (priv->sidebar);
2807
2808       gtk_widget_show (menu);
2809       gtk_widget_show (priv->vbox);
2810       gtk_widget_show (priv->statusbar);
2811       gtk_widget_show (priv->toolbar);
2812
2813       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2814           priv->original_height_before_fs);
2815     }
2816 }
2817
2818 static void
2819 show_borders (EmpathyStreamedMediaWindow *window, gboolean set_fullscreen)
2820 {
2821   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2822
2823   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2824       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2825   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2826       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2827
2828   if (priv->video_output != NULL)
2829     {
2830       gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2831           priv->video_output, TRUE, TRUE,
2832           set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2833           GTK_PACK_START);
2834     }
2835
2836   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2837       priv->vbox, TRUE, TRUE,
2838       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2839       GTK_PACK_START);
2840 }
2841
2842 static gboolean
2843 empathy_streamed_media_window_state_event_cb (GtkWidget *widget,
2844   GdkEventWindowState *event, EmpathyStreamedMediaWindow *window)
2845 {
2846   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2847     {
2848       EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2849       gboolean set_fullscreen = event->new_window_state &
2850         GDK_WINDOW_STATE_FULLSCREEN;
2851
2852       if (set_fullscreen)
2853         {
2854           gboolean sidebar_was_visible;
2855           GtkAllocation allocation;
2856           gint original_width, original_height;
2857
2858           gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2859           original_width = allocation.width;
2860           original_height = allocation.height;
2861
2862           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2863
2864           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2865           priv->original_width_before_fs = original_width;
2866           priv->original_height_before_fs = original_height;
2867
2868           if (priv->video_output_motion_handler_id == 0 &&
2869                 priv->video_output != NULL)
2870             {
2871               priv->video_output_motion_handler_id = g_signal_connect (
2872                   G_OBJECT (priv->video_output), "motion-notify-event",
2873                   G_CALLBACK (empathy_streamed_media_window_video_output_motion_notify),
2874                   window);
2875             }
2876         }
2877       else
2878         {
2879           disconnect_video_output_motion_handler (window);
2880         }
2881
2882       empathy_streamed_media_window_fullscreen_set_fullscreen (priv->fullscreen,
2883           set_fullscreen);
2884       show_controls (window, set_fullscreen);
2885       show_borders (window, set_fullscreen);
2886       gtk_action_set_stock_id (priv->menu_fullscreen,
2887           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2888       priv->is_fullscreen = set_fullscreen;
2889   }
2890
2891   return FALSE;
2892 }
2893
2894 static void
2895 empathy_streamed_media_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2896   EmpathyStreamedMediaWindow *window)
2897 {
2898   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2899   GtkWidget *arrow;
2900   int w, h, handle_size;
2901   GtkAllocation allocation, sidebar_allocation;
2902
2903   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2904   w = allocation.width;
2905   h = allocation.height;
2906
2907   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2908
2909   gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2910   if (gtk_toggle_button_get_active (toggle))
2911     {
2912       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2913       gtk_widget_show (priv->sidebar);
2914       w += sidebar_allocation.width + handle_size;
2915     }
2916   else
2917     {
2918       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2919       w -= sidebar_allocation.width + handle_size;
2920       gtk_widget_hide (priv->sidebar);
2921     }
2922
2923   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2924
2925   if (w > 0 && h > 0)
2926     gtk_window_resize (GTK_WINDOW (window), w, h);
2927 }
2928
2929 static void
2930 empathy_streamed_media_window_set_send_video (EmpathyStreamedMediaWindow *window,
2931   CameraState state)
2932 {
2933   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2934   EmpathyTpStreamedMedia *call;
2935
2936   priv->sending_video = (state == CAMERA_STATE_ON);
2937
2938   if (state == CAMERA_STATE_PREVIEW ||
2939       state == CAMERA_STATE_ON)
2940     {
2941       /* When we start sending video, we want to show the video preview by
2942          default. */
2943       display_video_preview (window, TRUE);
2944     }
2945   else
2946     {
2947       display_video_preview (window, FALSE);
2948     }
2949
2950   if (priv->call_state != CONNECTED)
2951     return;
2952
2953   g_object_get (priv->handler, "tp-call", &call, NULL);
2954   DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
2955   empathy_tp_streamed_media_request_video_stream_direction (call, priv->sending_video);
2956   g_object_unref (call);
2957 }
2958
2959 static void
2960 empathy_streamed_media_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2961   EmpathyStreamedMediaWindow *window)
2962 {
2963   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2964   gboolean active;
2965
2966   active = (gtk_toggle_tool_button_get_active (toggle));
2967
2968   if (active)
2969     {
2970       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2971         priv->volume);
2972       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2973     }
2974   else
2975     {
2976       /* TODO, Instead of setting the input volume to 0 we should probably
2977        * stop sending but this would cause the audio call to drop if both
2978        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2979        * in the future. GNOME #574574
2980        */
2981       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2982         0);
2983       gtk_adjustment_set_value (priv->audio_input_adj, 0);
2984     }
2985 }
2986
2987 static void
2988 empathy_streamed_media_window_sidebar_hidden_cb (EvSidebar *sidebar,
2989   EmpathyStreamedMediaWindow *window)
2990 {
2991   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2992
2993   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2994     FALSE);
2995 }
2996
2997 static void
2998 empathy_streamed_media_window_sidebar_shown_cb (EvSidebar *sidebar,
2999   EmpathyStreamedMediaWindow *window)
3000 {
3001   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3002
3003   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
3004     TRUE);
3005 }
3006
3007 static void
3008 empathy_streamed_media_window_hangup_cb (gpointer object,
3009                                EmpathyStreamedMediaWindow *window)
3010 {
3011   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3012
3013   empathy_streamed_media_handler_stop_call (priv->handler);
3014
3015   if (empathy_streamed_media_window_disconnected (window, FALSE))
3016     gtk_widget_destroy (GTK_WIDGET (window));
3017 }
3018
3019 static void
3020 empathy_streamed_media_window_restart_call (EmpathyStreamedMediaWindow *window)
3021 {
3022   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3023
3024   /* Remove error info bars */
3025   gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
3026       (GtkCallback) gtk_widget_destroy, NULL);
3027
3028   create_video_output_widget (window);
3029
3030   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
3031       G_CALLBACK (empathy_streamed_media_window_mic_volume_changed_cb), window);
3032
3033   /* While the call was disconnected, the input volume might have changed.
3034    * However, since the audio_input source was destroyed, its volume has not
3035    * been updated during that time. That's why we manually update it here */
3036   empathy_streamed_media_window_mic_volume_changed_cb (priv->audio_input_adj, window);
3037
3038   priv->outgoing = TRUE;
3039   empathy_streamed_media_window_set_state_connecting (window);
3040
3041   if (priv->pipeline_playing)
3042     start_call (window);
3043   else
3044     /* call will be started when the pipeline is ready */
3045     priv->start_call_when_playing = TRUE;
3046
3047
3048   empathy_streamed_media_window_setup_avatars (window, priv->handler);
3049
3050   gtk_action_set_sensitive (priv->redial, FALSE);
3051   gtk_widget_set_sensitive (priv->redial_button, FALSE);
3052 }
3053
3054 static void
3055 empathy_streamed_media_window_redial_cb (gpointer object,
3056     EmpathyStreamedMediaWindow *window)
3057 {
3058   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3059
3060   if (priv->call_state == CONNECTED)
3061     priv->call_state = REDIALING;
3062
3063   empathy_streamed_media_handler_stop_call (priv->handler);
3064
3065   if (priv->call_state != CONNECTED)
3066     empathy_streamed_media_window_restart_call (window);
3067 }
3068
3069 static void
3070 empathy_streamed_media_window_fullscreen_cb (gpointer object,
3071                                    EmpathyStreamedMediaWindow *window)
3072 {
3073   empathy_streamed_media_window_fullscreen_toggle (window);
3074 }
3075
3076 static void
3077 empathy_streamed_media_window_fullscreen_toggle (EmpathyStreamedMediaWindow *window)
3078 {
3079   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3080
3081   if (priv->is_fullscreen)
3082     gtk_window_unfullscreen (GTK_WINDOW (window));
3083   else
3084     gtk_window_fullscreen (GTK_WINDOW (window));
3085 }
3086
3087 static gboolean
3088 empathy_streamed_media_window_video_button_press_cb (GtkWidget *video_output,
3089   GdkEventButton *event, EmpathyStreamedMediaWindow *window)
3090 {
3091   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
3092     {
3093       empathy_streamed_media_window_video_menu_popup (window, event->button);
3094       return TRUE;
3095     }
3096
3097   return FALSE;
3098 }
3099
3100 static gboolean
3101 empathy_streamed_media_window_key_press_cb (GtkWidget *video_output,
3102   GdkEventKey *event, EmpathyStreamedMediaWindow *window)
3103 {
3104   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3105
3106   if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
3107     {
3108       /* Since we are in fullscreen mode, toggling will bring us back to
3109          normal mode. */
3110       empathy_streamed_media_window_fullscreen_toggle (window);
3111       return TRUE;
3112     }
3113
3114   return FALSE;
3115 }
3116
3117 static gboolean
3118 empathy_streamed_media_window_video_output_motion_notify (GtkWidget *widget,
3119     GdkEventMotion *event, EmpathyStreamedMediaWindow *window)
3120 {
3121   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3122
3123   if (priv->is_fullscreen)
3124     {
3125       empathy_streamed_media_window_fullscreen_show_popup (priv->fullscreen);
3126       return TRUE;
3127     }
3128   return FALSE;
3129 }
3130
3131 static void
3132 empathy_streamed_media_window_video_menu_popup (EmpathyStreamedMediaWindow *window,
3133   guint button)
3134 {
3135   GtkWidget *menu;
3136   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3137
3138   menu = gtk_ui_manager_get_widget (priv->ui_manager,
3139             "/video-popup");
3140   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
3141       button, gtk_get_current_event_time ());
3142   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
3143 }
3144
3145 static void
3146 empathy_streamed_media_window_status_message (EmpathyStreamedMediaWindow *window,
3147   gchar *message)
3148 {
3149   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3150
3151   if (priv->context_id == 0)
3152     {
3153       priv->context_id = gtk_statusbar_get_context_id (
3154         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
3155     }
3156   else
3157     {
3158       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
3159     }
3160
3161   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
3162     message);
3163 }
3164
3165 static void
3166 empathy_streamed_media_window_volume_changed_cb (GtkScaleButton *button,
3167   gdouble value, EmpathyStreamedMediaWindow *window)
3168 {
3169   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3170
3171   if (priv->audio_output == NULL)
3172     return;
3173
3174   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
3175     value);
3176 }
3177
3178 /* block all the signals related to camera control widgets. This is useful
3179  * when we are manually updating the UI and so don't want to fire the
3180  * callbacks */
3181 static void
3182 block_camera_control_signals (EmpathyStreamedMediaWindow *self)
3183 {
3184   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
3185
3186   g_signal_handlers_block_by_func (priv->tool_button_camera_off,
3187       tool_button_camera_off_toggled_cb, self);
3188   g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
3189       tool_button_camera_preview_toggled_cb, self);
3190   g_signal_handlers_block_by_func (priv->tool_button_camera_on,
3191       tool_button_camera_on_toggled_cb, self);
3192   g_signal_handlers_block_by_func (priv->action_camera_on,
3193       action_camera_change_cb, self);
3194 }
3195
3196 static void
3197 unblock_camera_control_signals (EmpathyStreamedMediaWindow *self)
3198 {
3199   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
3200
3201   g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
3202       tool_button_camera_off_toggled_cb, self);
3203   g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
3204       tool_button_camera_preview_toggled_cb, self);
3205   g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
3206       tool_button_camera_on_toggled_cb, self);
3207   g_signal_handlers_unblock_by_func (priv->action_camera_on,
3208       action_camera_change_cb, self);
3209 }