]> git.0d.be Git - empathy.git/blob - src/empathy-streamed-media-window.c
Update Simplified Chinese help translation.
[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
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include <math.h>
26
27 #include <gdk/gdkkeysyms.h>
28 #include <gst/gst.h>
29 #include <gtk/gtk.h>
30 #include <glib/gi18n.h>
31
32 #include <telepathy-glib/util.h>
33 #include <telepathy-farsight/channel.h>
34 #include <telepathy-glib/util.h>
35
36 #include <gst/farsight/fs-element-added-notifier.h>
37
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-utils.h>
40 #include <libempathy-gtk/empathy-avatar-image.h>
41 #include <libempathy-gtk/empathy-ui-utils.h>
42 #include <libempathy-gtk/empathy-sound-manager.h>
43 #include <libempathy-gtk/empathy-geometry.h>
44 #include <libempathy-gtk/empathy-images.h>
45
46 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
47 #include <libempathy/empathy-debug.h>
48
49 #include "empathy-streamed-media-window.h"
50 #include "empathy-streamed-media-window-fullscreen.h"
51 #include "empathy-video-widget.h"
52 #include "empathy-audio-src.h"
53 #include "empathy-audio-sink.h"
54 #include "empathy-video-src.h"
55 #include "ev-sidebar.h"
56
57 #define CONTENT_HBOX_BORDER_WIDTH 6
58 #define CONTENT_HBOX_SPACING 3
59 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
60
61 #define SELF_VIDEO_SECTION_WIDTH 160
62 #define SELF_VIDEO_SECTION_HEIGTH 120
63
64 /* The avatar's default width and height are set to the same value because we
65    want a square icon. */
66 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
67 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
68   EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
69
70 /* If an video input error occurs, the error message will start with "v4l" */
71 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
72
73 /* The time interval in milliseconds between 2 outgoing rings */
74 #define MS_BETWEEN_RING 500
75
76 G_DEFINE_TYPE(EmpathyStreamedMediaWindow, empathy_streamed_media_window, GTK_TYPE_WINDOW)
77
78 /* signal enum */
79 #if 0
80 enum
81 {
82     LAST_SIGNAL
83 };
84
85 static guint signals[LAST_SIGNAL] = {0};
86 #endif
87
88 enum {
89   PROP_STREAMED_MEDIA_HANDLER = 1,
90 };
91
92 typedef enum {
93   CONNECTING,
94   CONNECTED,
95   DISCONNECTED,
96   REDIALING
97 } CallState;
98
99 typedef enum {
100   CAMERA_STATE_OFF = 0,
101   CAMERA_STATE_PREVIEW,
102   CAMERA_STATE_ON,
103 } CameraState;
104
105 /* private structure */
106 typedef struct _EmpathyStreamedMediaWindowPriv EmpathyStreamedMediaWindowPriv;
107
108 struct _EmpathyStreamedMediaWindowPriv
109 {
110   gboolean dispose_has_run;
111   EmpathyStreamedMediaHandler *handler;
112   EmpathyContact *contact;
113
114   guint call_state;
115   gboolean outgoing;
116
117   GtkUIManager *ui_manager;
118   GtkWidget *errors_vbox;
119   /* widget displays the video received from the remote user. This widget is
120    * alive only during call. */
121   GtkWidget *video_output;
122   GtkWidget *video_preview;
123   GtkWidget *remote_user_avatar_widget;
124   GtkWidget *self_user_avatar_widget;
125   GtkWidget *sidebar;
126   GtkWidget *sidebar_button;
127   GtkWidget *statusbar;
128   GtkWidget *volume_button;
129   GtkWidget *redial_button;
130   GtkWidget *mic_button;
131   GtkWidget *toolbar;
132   GtkWidget *pane;
133   GtkAction *redial;
134   GtkAction *menu_fullscreen;
135   GtkAction *action_camera_on;
136   GtkWidget *tool_button_camera_off;
137   GtkWidget *tool_button_camera_preview;
138   GtkWidget *tool_button_camera_on;
139
140   /* The frames and boxes that contain self and remote avatar and video
141      input/output. When we redial, we destroy and re-create the boxes */
142   GtkWidget *remote_user_output_frame;
143   GtkWidget *self_user_output_frame;
144   GtkWidget *remote_user_output_hbox;
145   GtkWidget *self_user_output_hbox;
146
147   /* We keep a reference on the hbox which contains the main content so we can
148      easilly repack everything when toggling fullscreen */
149   GtkWidget *content_hbox;
150
151   /* This vbox is contained in the content_hbox and it contains the
152      self_user_output_frame and the sidebar button. When toggling fullscreen,
153      it needs to be repacked. We keep a reference on it for easier access. */
154   GtkWidget *vbox;
155
156   gulong video_output_motion_handler_id;
157   guint bus_message_source_id;
158
159   gdouble volume;
160   GtkWidget *volume_scale;
161   GtkWidget *volume_progress_bar;
162   GtkAdjustment *audio_input_adj;
163
164   GtkWidget *dtmf_panel;
165
166   /* Details vbox */
167   GtkWidget *details_vbox;
168   GtkWidget *vcodec_encoding_label;
169   GtkWidget *acodec_encoding_label;
170   GtkWidget *vcodec_decoding_label;
171   GtkWidget *acodec_decoding_label;
172
173   GtkWidget *audio_remote_candidate_label;
174   GtkWidget *audio_local_candidate_label;
175   GtkWidget *video_remote_candidate_label;
176   GtkWidget *video_local_candidate_label;
177   GtkWidget *video_remote_candidate_info_img;
178   GtkWidget *video_local_candidate_info_img;
179   GtkWidget *audio_remote_candidate_info_img;
180   GtkWidget *audio_local_candidate_info_img;
181
182   GstElement *video_input;
183   GstElement *audio_input;
184   GstElement *audio_output;
185   GstElement *pipeline;
186   GstElement *video_tee;
187
188   GstElement *funnel;
189
190   FsElementAddedNotifier *fsnotifier;
191
192   guint context_id;
193
194   GTimer *timer;
195   guint timer_id;
196
197   GtkWidget *video_contrast;
198   GtkWidget *video_brightness;
199   GtkWidget *video_gamma;
200
201   GMutex *lock;
202   gboolean call_started;
203   gboolean sending_video;
204   CameraState camera_state;
205
206   EmpathyStreamedMediaWindowFullscreen *fullscreen;
207   gboolean is_fullscreen;
208
209   /* Those fields represent the state of the window before it actually was in
210      fullscreen mode. */
211   gboolean sidebar_was_visible_before_fs;
212   gint original_width_before_fs;
213   gint original_height_before_fs;
214
215   /* TRUE if the call should be started when the pipeline is playing */
216   gboolean start_call_when_playing;
217   /* TRUE if we requested to set the pipeline in the playing state */
218   gboolean pipeline_playing;
219
220   EmpathySoundManager *sound_mgr;
221 };
222
223 #define GET_PRIV(o) \
224   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_STREAMED_MEDIA_WINDOW, \
225     EmpathyStreamedMediaWindowPriv))
226
227 static void empathy_streamed_media_window_realized_cb (GtkWidget *widget,
228   EmpathyStreamedMediaWindow *window);
229
230 static gboolean empathy_streamed_media_window_delete_cb (GtkWidget *widget,
231   GdkEvent *event, EmpathyStreamedMediaWindow *window);
232
233 static gboolean empathy_streamed_media_window_state_event_cb (GtkWidget *widget,
234   GdkEventWindowState *event, EmpathyStreamedMediaWindow *window);
235
236 static void empathy_streamed_media_window_sidebar_toggled_cb (GtkToggleButton *toggle,
237   EmpathyStreamedMediaWindow *window);
238
239 static void empathy_streamed_media_window_set_send_video (EmpathyStreamedMediaWindow *window,
240   CameraState state);
241
242 static void empathy_streamed_media_window_mic_toggled_cb (
243   GtkToggleToolButton *toggle, EmpathyStreamedMediaWindow *window);
244
245 static void empathy_streamed_media_window_sidebar_hidden_cb (EvSidebar *sidebar,
246   EmpathyStreamedMediaWindow *window);
247
248 static void empathy_streamed_media_window_sidebar_shown_cb (EvSidebar *sidebar,
249   EmpathyStreamedMediaWindow *window);
250
251 static void empathy_streamed_media_window_hangup_cb (gpointer object,
252   EmpathyStreamedMediaWindow *window);
253
254 static void empathy_streamed_media_window_fullscreen_cb (gpointer object,
255   EmpathyStreamedMediaWindow *window);
256
257 static void empathy_streamed_media_window_fullscreen_toggle (EmpathyStreamedMediaWindow *window);
258
259 static gboolean empathy_streamed_media_window_video_button_press_cb (
260   GtkWidget *video_output, GdkEventButton *event, EmpathyStreamedMediaWindow *window);
261
262 static gboolean empathy_streamed_media_window_key_press_cb (GtkWidget *video_output,
263   GdkEventKey *event, EmpathyStreamedMediaWindow *window);
264
265 static gboolean empathy_streamed_media_window_video_output_motion_notify (
266   GtkWidget *widget, GdkEventMotion *event, EmpathyStreamedMediaWindow *window);
267
268 static void empathy_streamed_media_window_video_menu_popup (EmpathyStreamedMediaWindow *window,
269   guint button);
270
271 static void empathy_streamed_media_window_redial_cb (gpointer object,
272   EmpathyStreamedMediaWindow *window);
273
274 static void empathy_streamed_media_window_restart_call (EmpathyStreamedMediaWindow *window);
275
276 static void empathy_streamed_media_window_status_message (EmpathyStreamedMediaWindow *window,
277   gchar *message);
278
279 static void empathy_streamed_media_window_update_avatars_visibility (EmpathyTpStreamedMedia *call,
280   EmpathyStreamedMediaWindow *window);
281
282 static gboolean empathy_streamed_media_window_bus_message (GstBus *bus,
283   GstMessage *message, gpointer user_data);
284
285 static void
286 empathy_streamed_media_window_volume_changed_cb (GtkScaleButton *button,
287   gdouble value, EmpathyStreamedMediaWindow *window);
288
289 static void block_camera_control_signals (EmpathyStreamedMediaWindow *self);
290 static void unblock_camera_control_signals (EmpathyStreamedMediaWindow *self);
291
292 static void
293 empathy_streamed_media_window_setup_toolbar (EmpathyStreamedMediaWindow *self)
294 {
295   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
296   GtkToolItem *tool_item;
297   GtkWidget *camera_off_icon;
298   GdkPixbuf *pixbuf, *modded_pixbuf;
299
300   /* set the icon of the 'camera off' button by greying off the webcam icon */
301   pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
302       GTK_ICON_SIZE_SMALL_TOOLBAR);
303
304   modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
305       gdk_pixbuf_get_width (pixbuf),
306       gdk_pixbuf_get_height (pixbuf));
307
308   gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
309   g_object_unref (pixbuf);
310
311   camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
312   g_object_unref (modded_pixbuf);
313   gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
314         priv->tool_button_camera_off), camera_off_icon);
315
316   /* Add an empty expanded GtkToolItem so the volume button is at the end of
317    * the toolbar. */
318   tool_item = gtk_tool_item_new ();
319   gtk_tool_item_set_expand (tool_item, TRUE);
320   gtk_widget_show (GTK_WIDGET (tool_item));
321   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
322
323   priv->volume_button = gtk_volume_button_new ();
324   /* FIXME listen to the audiosinks signals and update the button according to
325    * that, for now starting out at 1.0 and assuming only the app changes the
326    * volume will do */
327   gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
328   g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
329     G_CALLBACK (empathy_streamed_media_window_volume_changed_cb), self);
330
331   tool_item = gtk_tool_item_new ();
332   gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
333   gtk_widget_show_all (GTK_WIDGET (tool_item));
334   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
335 }
336
337 static void
338 dtmf_button_pressed_cb (GtkButton *button, EmpathyStreamedMediaWindow *window)
339 {
340   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
341   EmpathyTpStreamedMedia *call;
342   GQuark button_quark;
343   TpDTMFEvent event;
344
345   g_object_get (priv->handler, "tp-call", &call, NULL);
346
347   button_quark = g_quark_from_static_string (EMPATHY_DTMF_BUTTON_ID);
348   event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
349     button_quark));
350
351   empathy_tp_streamed_media_start_tone (call, event);
352
353   g_object_unref (call);
354 }
355
356 static void
357 dtmf_button_released_cb (GtkButton *button, 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_create_dtmf_dialpad (G_OBJECT (self),
1137       G_CALLBACK (dtmf_button_pressed_cb),
1138       G_CALLBACK (dtmf_button_released_cb));
1139   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "dialpad",
1140       _("Dialpad"), priv->dtmf_panel);
1141
1142   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1143
1144   /* Put the details vbox in a scroll window as it can require a lot of
1145    * horizontal space. */
1146   scroll = gtk_scrolled_window_new (NULL, NULL);
1147   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scroll),
1148       priv->details_vbox);
1149
1150   ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "details",
1151       _("Details"), scroll);
1152
1153   gtk_widget_show_all (top_vbox);
1154
1155   gtk_widget_hide (priv->sidebar);
1156
1157   priv->fullscreen = empathy_streamed_media_window_fullscreen_new (self);
1158   empathy_streamed_media_window_fullscreen_set_video_widget (priv->fullscreen,
1159       priv->video_output);
1160   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1161       "clicked", G_CALLBACK (empathy_streamed_media_window_fullscreen_cb), self);
1162
1163   g_signal_connect (G_OBJECT (self), "realize",
1164     G_CALLBACK (empathy_streamed_media_window_realized_cb), self);
1165
1166   g_signal_connect (G_OBJECT (self), "delete-event",
1167     G_CALLBACK (empathy_streamed_media_window_delete_cb), self);
1168
1169   g_signal_connect (G_OBJECT (self), "window-state-event",
1170     G_CALLBACK (empathy_streamed_media_window_state_event_cb), self);
1171
1172   g_signal_connect (G_OBJECT (self), "key-press-event",
1173       G_CALLBACK (empathy_streamed_media_window_key_press_cb), self);
1174
1175   priv->timer = g_timer_new ();
1176
1177   g_object_ref (priv->ui_manager);
1178   g_object_unref (gui);
1179
1180   priv->sound_mgr = empathy_sound_manager_dup_singleton ();
1181
1182   empathy_geometry_bind (GTK_WINDOW (self), "av-window");
1183 }
1184
1185 /* Instead of specifying a width and a height, we specify only one size. That's
1186    because we want a square avatar icon.  */
1187 static void
1188 init_contact_avatar_with_size (EmpathyContact *contact,
1189     GtkWidget *image_widget,
1190     gint size)
1191 {
1192   GdkPixbuf *pixbuf_avatar = NULL;
1193
1194   if (contact != NULL)
1195     {
1196       pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1197         size, size);
1198     }
1199
1200   if (pixbuf_avatar == NULL)
1201     {
1202       pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
1203           EMPATHY_IMAGE_AVATAR_DEFAULT, size);
1204     }
1205
1206   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1207
1208   if (pixbuf_avatar != NULL)
1209     g_object_unref (pixbuf_avatar);
1210 }
1211
1212 static void
1213 set_window_title (EmpathyStreamedMediaWindow *self)
1214 {
1215   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1216   gchar *tmp;
1217
1218   /* translators: Call is a noun and %s is the contact name. This string
1219    * is used in the window title */
1220   tmp = g_strdup_printf (_("Call with %s"),
1221       empathy_contact_get_alias (priv->contact));
1222   gtk_window_set_title (GTK_WINDOW (self), tmp);
1223   g_free (tmp);
1224 }
1225
1226 static void
1227 contact_name_changed_cb (EmpathyContact *contact,
1228     GParamSpec *pspec, EmpathyStreamedMediaWindow *self)
1229 {
1230   set_window_title (self);
1231 }
1232
1233 static void
1234 contact_avatar_changed_cb (EmpathyContact *contact,
1235     GParamSpec *pspec, GtkWidget *avatar_widget)
1236 {
1237   int size;
1238   GtkAllocation allocation;
1239
1240   gtk_widget_get_allocation (avatar_widget, &allocation);
1241   size = allocation.height;
1242
1243   if (size == 0)
1244     {
1245       /* the widget is not allocated yet, set a default size */
1246       size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1247           REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1248     }
1249
1250   init_contact_avatar_with_size (contact, avatar_widget, size);
1251 }
1252
1253 static void
1254 empathy_streamed_media_window_got_self_contact_cb (TpConnection *connection,
1255     EmpathyContact *contact, const GError *error, gpointer user_data,
1256     GObject *weak_object)
1257 {
1258   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1259   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1260
1261   init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1262       MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1263
1264   g_signal_connect (contact, "notify::avatar",
1265       G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1266 }
1267
1268 static void
1269 empathy_streamed_media_window_setup_avatars (EmpathyStreamedMediaWindow *self,
1270     EmpathyStreamedMediaHandler *handler)
1271 {
1272   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1273
1274   g_object_get (handler, "contact", &(priv->contact), NULL);
1275
1276   if (priv->contact != NULL)
1277     {
1278       TpConnection *connection;
1279
1280       set_window_title (self);
1281
1282       g_signal_connect (priv->contact, "notify::name",
1283           G_CALLBACK (contact_name_changed_cb), self);
1284       g_signal_connect (priv->contact, "notify::avatar",
1285           G_CALLBACK (contact_avatar_changed_cb),
1286           priv->remote_user_avatar_widget);
1287
1288       /* Retreiving the self avatar */
1289       connection = empathy_contact_get_connection (priv->contact);
1290       empathy_tp_contact_factory_get_from_handle (connection,
1291           tp_connection_get_self_handle (connection),
1292           empathy_streamed_media_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1293     }
1294   else
1295     {
1296       g_warning ("call handler doesn't have a contact");
1297       /* translators: Call is a noun. This string is used in the window
1298        * title */
1299       gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1300
1301       /* Since we can't access the remote contact, we can't get a connection
1302          to it and can't get the self contact (and its avatar). This means
1303          that we have to manually set the self avatar. */
1304       init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1305           MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1306     }
1307
1308   init_contact_avatar_with_size (priv->contact,
1309       priv->remote_user_avatar_widget,
1310       MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1311           REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1312
1313   /* The remote avatar is shown by default and will be hidden when we receive
1314      video from the remote side. */
1315   gtk_widget_hide (priv->video_output);
1316   gtk_widget_show (priv->remote_user_avatar_widget);
1317 }
1318
1319 static void
1320 update_send_codec (EmpathyStreamedMediaWindow *self,
1321     gboolean audio)
1322 {
1323   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1324   FsCodec *codec;
1325   GtkWidget *widget;
1326   gchar *tmp;
1327
1328   if (audio)
1329     {
1330       codec = empathy_streamed_media_handler_get_send_audio_codec (priv->handler);
1331       widget = priv->acodec_encoding_label;
1332     }
1333   else
1334     {
1335       codec = empathy_streamed_media_handler_get_send_video_codec (priv->handler);
1336       widget = priv->vcodec_encoding_label;
1337     }
1338
1339   if (codec == NULL)
1340     return;
1341
1342   tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
1343   gtk_label_set_text (GTK_LABEL (widget), tmp);
1344   g_free (tmp);
1345 }
1346
1347 static void
1348 send_audio_codec_notify_cb (GObject *object,
1349     GParamSpec *pspec,
1350     gpointer user_data)
1351 {
1352   EmpathyStreamedMediaWindow *self = user_data;
1353
1354   update_send_codec (self, TRUE);
1355 }
1356
1357 static void
1358 send_video_codec_notify_cb (GObject *object,
1359     GParamSpec *pspec,
1360     gpointer user_data)
1361 {
1362   EmpathyStreamedMediaWindow *self = user_data;
1363
1364   update_send_codec (self, FALSE);
1365 }
1366
1367 static void
1368 update_recv_codec (EmpathyStreamedMediaWindow *self,
1369     gboolean audio)
1370 {
1371   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1372   GList *codecs, *l;
1373   GtkWidget *widget;
1374   GString *str = NULL;
1375
1376   if (audio)
1377     {
1378       codecs = empathy_streamed_media_handler_get_recv_audio_codecs (priv->handler);
1379       widget = priv->acodec_decoding_label;
1380     }
1381   else
1382     {
1383       codecs = empathy_streamed_media_handler_get_recv_video_codecs (priv->handler);
1384       widget = priv->vcodec_decoding_label;
1385     }
1386
1387   if (codecs == NULL)
1388     return;
1389
1390   for (l = codecs; l != NULL; l = g_list_next (l))
1391     {
1392       FsCodec *codec = l->data;
1393
1394       if (str == NULL)
1395         str = g_string_new (NULL);
1396       else
1397         g_string_append (str, ", ");
1398
1399       g_string_append_printf (str, "%s/%u", codec->encoding_name,
1400           codec->clock_rate);
1401     }
1402
1403   gtk_label_set_text (GTK_LABEL (widget), str->str);
1404   g_string_free (str, TRUE);
1405 }
1406
1407 static void
1408 recv_audio_codecs_notify_cb (GObject *object,
1409     GParamSpec *pspec,
1410     gpointer user_data)
1411 {
1412   EmpathyStreamedMediaWindow *self = user_data;
1413
1414   update_recv_codec (self, TRUE);
1415 }
1416
1417 static void
1418 recv_video_codecs_notify_cb (GObject *object,
1419     GParamSpec *pspec,
1420     gpointer user_data)
1421 {
1422   EmpathyStreamedMediaWindow *self = user_data;
1423
1424   update_recv_codec (self, FALSE);
1425 }
1426
1427 static const gchar *
1428 candidate_type_to_str (FsCandidate *candidate)
1429 {
1430   switch (candidate->type)
1431     {
1432       case FS_CANDIDATE_TYPE_HOST:
1433         return "host";
1434       case FS_CANDIDATE_TYPE_SRFLX:
1435         return "server reflexive";
1436       case FS_CANDIDATE_TYPE_PRFLX:
1437         return "peer reflexive";
1438       case FS_CANDIDATE_TYPE_RELAY:
1439         return "relay";
1440       case FS_CANDIDATE_TYPE_MULTICAST:
1441         return "multicast";
1442     }
1443
1444   return NULL;
1445 }
1446
1447 static const gchar *
1448 candidate_type_to_desc (FsCandidate *candidate)
1449 {
1450   switch (candidate->type)
1451     {
1452       case FS_CANDIDATE_TYPE_HOST:
1453         return _("The IP address as seen by the machine");
1454       case FS_CANDIDATE_TYPE_SRFLX:
1455         return _("The IP address as seen by a server on the Internet");
1456       case FS_CANDIDATE_TYPE_PRFLX:
1457         return _("The IP address of the peer as seen by the other side");
1458       case FS_CANDIDATE_TYPE_RELAY:
1459         return _("The IP address of a relay server");
1460       case FS_CANDIDATE_TYPE_MULTICAST:
1461         return _("The IP address of the multicast group");
1462     }
1463
1464   return NULL;
1465 }
1466
1467 static void
1468 update_candidat_widget (EmpathyStreamedMediaWindow *self,
1469     GtkWidget *label,
1470     GtkWidget *img,
1471     FsCandidate *candidate)
1472 {
1473   gchar *str;
1474
1475   g_assert (candidate != NULL);
1476   str = g_strdup_printf ("%s %u (%s)", candidate->ip,
1477       candidate->port, candidate_type_to_str (candidate));
1478
1479   gtk_label_set_text (GTK_LABEL (label), str);
1480   gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
1481
1482   g_free (str);
1483 }
1484
1485 static void
1486 candidates_changed_cb (GObject *object,
1487     FsMediaType type,
1488     EmpathyStreamedMediaWindow *self)
1489 {
1490   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1491   FsCandidate *candidate = NULL;
1492
1493   if (type == FS_MEDIA_TYPE_VIDEO)
1494     {
1495       /* Update remote candidate */
1496       candidate = empathy_streamed_media_handler_get_video_remote_candidate (
1497           priv->handler);
1498
1499       update_candidat_widget (self, priv->video_remote_candidate_label,
1500           priv->video_remote_candidate_info_img, candidate);
1501
1502       /* Update local candidate */
1503       candidate = empathy_streamed_media_handler_get_video_local_candidate (
1504           priv->handler);
1505
1506       update_candidat_widget (self, priv->video_local_candidate_label,
1507           priv->video_local_candidate_info_img, candidate);
1508     }
1509   else
1510     {
1511       /* Update remote candidate */
1512       candidate = empathy_streamed_media_handler_get_audio_remote_candidate (
1513           priv->handler);
1514
1515       update_candidat_widget (self, priv->audio_remote_candidate_label,
1516           priv->audio_remote_candidate_info_img, candidate);
1517
1518       /* Update local candidate */
1519       candidate = empathy_streamed_media_handler_get_audio_local_candidate (
1520           priv->handler);
1521
1522       update_candidat_widget (self, priv->audio_local_candidate_label,
1523           priv->audio_local_candidate_info_img, candidate);
1524     }
1525 }
1526
1527 static void
1528 empathy_streamed_media_window_constructed (GObject *object)
1529 {
1530   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (object);
1531   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1532   EmpathyTpStreamedMedia *call;
1533
1534   g_assert (priv->handler != NULL);
1535
1536   g_object_get (priv->handler, "tp-call", &call, NULL);
1537   priv->outgoing = (call == NULL);
1538   if (call != NULL)
1539     g_object_unref (call);
1540
1541   empathy_streamed_media_window_setup_avatars (self, priv->handler);
1542   empathy_streamed_media_window_set_state_connecting (self);
1543
1544   if (!empathy_streamed_media_handler_has_initial_video (priv->handler))
1545     {
1546       gtk_toggle_tool_button_set_active (
1547           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1548     }
1549   /* If call has InitialVideo, the preview will be started once the call has
1550    * been started (start_call()). */
1551
1552   update_send_codec (self, TRUE);
1553   update_send_codec (self, FALSE);
1554   update_recv_codec (self, TRUE);
1555   update_recv_codec (self, FALSE);
1556
1557   tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
1558       G_CALLBACK (send_audio_codec_notify_cb), self, 0);
1559   tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
1560       G_CALLBACK (send_video_codec_notify_cb), self, 0);
1561   tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
1562       G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
1563   tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
1564       G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
1565
1566   tp_g_signal_connect_object (priv->handler, "candidates-changed",
1567       G_CALLBACK (candidates_changed_cb), self, 0);
1568 }
1569
1570 static void empathy_streamed_media_window_dispose (GObject *object);
1571 static void empathy_streamed_media_window_finalize (GObject *object);
1572
1573 static void
1574 empathy_streamed_media_window_set_property (GObject *object,
1575   guint property_id, const GValue *value, GParamSpec *pspec)
1576 {
1577   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (object);
1578
1579   switch (property_id)
1580     {
1581       case PROP_STREAMED_MEDIA_HANDLER:
1582         priv->handler = g_value_dup_object (value);
1583         break;
1584       default:
1585         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1586     }
1587 }
1588
1589 static void
1590 empathy_streamed_media_window_get_property (GObject *object,
1591   guint property_id, GValue *value, GParamSpec *pspec)
1592 {
1593   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (object);
1594
1595   switch (property_id)
1596     {
1597       case PROP_STREAMED_MEDIA_HANDLER:
1598         g_value_set_object (value, priv->handler);
1599         break;
1600       default:
1601         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1602     }
1603 }
1604
1605 static void
1606 empathy_streamed_media_window_class_init (
1607   EmpathyStreamedMediaWindowClass *empathy_streamed_media_window_class)
1608 {
1609   GObjectClass *object_class = G_OBJECT_CLASS (empathy_streamed_media_window_class);
1610   GParamSpec *param_spec;
1611
1612   g_type_class_add_private (empathy_streamed_media_window_class,
1613     sizeof (EmpathyStreamedMediaWindowPriv));
1614
1615   object_class->constructed = empathy_streamed_media_window_constructed;
1616   object_class->set_property = empathy_streamed_media_window_set_property;
1617   object_class->get_property = empathy_streamed_media_window_get_property;
1618
1619   object_class->dispose = empathy_streamed_media_window_dispose;
1620   object_class->finalize = empathy_streamed_media_window_finalize;
1621
1622   param_spec = g_param_spec_object ("handler",
1623     "handler", "The call handler",
1624     EMPATHY_TYPE_STREAMED_MEDIA_HANDLER,
1625     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1626   g_object_class_install_property (object_class,
1627     PROP_STREAMED_MEDIA_HANDLER, param_spec);
1628 }
1629
1630 static void
1631 empathy_streamed_media_window_video_stream_changed_cb (EmpathyTpStreamedMedia *call,
1632     GParamSpec *property, EmpathyStreamedMediaWindow *self)
1633 {
1634   DEBUG ("video stream changed");
1635   empathy_streamed_media_window_update_avatars_visibility (call, self);
1636 }
1637
1638 void
1639 empathy_streamed_media_window_dispose (GObject *object)
1640 {
1641   EmpathyTpStreamedMedia *call;
1642   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (object);
1643   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1644
1645   if (priv->dispose_has_run)
1646     return;
1647
1648   priv->dispose_has_run = TRUE;
1649
1650   g_object_get (priv->handler, "tp-call", &call, NULL);
1651
1652   if (call != NULL)
1653     {
1654       g_object_unref (call);
1655     }
1656
1657   if (priv->handler != NULL)
1658     {
1659       empathy_streamed_media_handler_stop_call (priv->handler);
1660       g_object_unref (priv->handler);
1661     }
1662   priv->handler = NULL;
1663
1664   if (priv->bus_message_source_id != 0)
1665     {
1666       g_source_remove (priv->bus_message_source_id);
1667       priv->bus_message_source_id = 0;
1668     }
1669
1670   if (priv->pipeline != NULL)
1671     g_object_unref (priv->pipeline);
1672   priv->pipeline = NULL;
1673
1674   if (priv->video_input != NULL)
1675     g_object_unref (priv->video_input);
1676   priv->video_input = NULL;
1677
1678   if (priv->audio_input != NULL)
1679     g_object_unref (priv->audio_input);
1680   priv->audio_input = NULL;
1681
1682   if (priv->video_tee != NULL)
1683     g_object_unref (priv->video_tee);
1684   priv->video_tee = NULL;
1685
1686   if (priv->fsnotifier != NULL)
1687     g_object_unref (priv->fsnotifier);
1688   priv->fsnotifier = NULL;
1689
1690   if (priv->timer_id != 0)
1691     g_source_remove (priv->timer_id);
1692   priv->timer_id = 0;
1693
1694   if (priv->ui_manager != NULL)
1695     g_object_unref (priv->ui_manager);
1696   priv->ui_manager = NULL;
1697
1698   if (priv->fullscreen != NULL)
1699     g_object_unref (priv->fullscreen);
1700   priv->fullscreen = NULL;
1701
1702   if (priv->contact != NULL)
1703     {
1704       g_signal_handlers_disconnect_by_func (priv->contact,
1705           contact_name_changed_cb, self);
1706       g_object_unref (priv->contact);
1707       priv->contact = NULL;
1708     }
1709
1710   tp_clear_object (&priv->sound_mgr);
1711
1712   /* release any references held by the object here */
1713   if (G_OBJECT_CLASS (empathy_streamed_media_window_parent_class)->dispose)
1714     G_OBJECT_CLASS (empathy_streamed_media_window_parent_class)->dispose (object);
1715 }
1716
1717 static void
1718 disconnect_video_output_motion_handler (EmpathyStreamedMediaWindow *self)
1719 {
1720   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1721
1722   if (priv->video_output_motion_handler_id != 0)
1723     {
1724       g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1725           priv->video_output_motion_handler_id);
1726       priv->video_output_motion_handler_id = 0;
1727     }
1728 }
1729
1730 void
1731 empathy_streamed_media_window_finalize (GObject *object)
1732 {
1733   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (object);
1734   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1735
1736   disconnect_video_output_motion_handler (self);
1737
1738   /* free any data held directly by the object here */
1739   g_mutex_free (priv->lock);
1740
1741   g_timer_destroy (priv->timer);
1742
1743   G_OBJECT_CLASS (empathy_streamed_media_window_parent_class)->finalize (object);
1744 }
1745
1746
1747 EmpathyStreamedMediaWindow *
1748 empathy_streamed_media_window_new (EmpathyStreamedMediaHandler *handler)
1749 {
1750   return EMPATHY_STREAMED_MEDIA_WINDOW (
1751     g_object_new (EMPATHY_TYPE_STREAMED_MEDIA_WINDOW, "handler", handler, NULL));
1752 }
1753
1754 static void
1755 empathy_streamed_media_window_conference_added_cb (EmpathyStreamedMediaHandler *handler,
1756   GstElement *conference, gpointer user_data)
1757 {
1758   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1759   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1760
1761   gst_bin_add (GST_BIN (priv->pipeline), conference);
1762
1763   gst_element_set_state (conference, GST_STATE_PLAYING);
1764 }
1765
1766 static gboolean
1767 empathy_streamed_media_window_request_resource_cb (EmpathyStreamedMediaHandler *handler,
1768   FsMediaType type, FsStreamDirection direction, gpointer user_data)
1769 {
1770   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1771   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1772
1773   if (type != FS_MEDIA_TYPE_VIDEO)
1774     return TRUE;
1775
1776   if (direction == FS_DIRECTION_RECV)
1777     return TRUE;
1778
1779   /* video and direction is send */
1780   return priv->video_input != NULL;
1781 }
1782
1783 static gboolean
1784 empathy_streamed_media_window_reset_pipeline (EmpathyStreamedMediaWindow *self)
1785 {
1786   GstStateChangeReturn state_change_return;
1787   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1788
1789   if (priv->pipeline == NULL)
1790     return TRUE;
1791
1792   if (priv->bus_message_source_id != 0)
1793     {
1794       g_source_remove (priv->bus_message_source_id);
1795       priv->bus_message_source_id = 0;
1796     }
1797
1798   state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1799
1800   if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1801         state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1802     {
1803       if (priv->pipeline != NULL)
1804         g_object_unref (priv->pipeline);
1805       priv->pipeline = NULL;
1806
1807       g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1808           empathy_streamed_media_window_mic_volume_changed_cb, self);
1809
1810       if (priv->video_tee != NULL)
1811         g_object_unref (priv->video_tee);
1812       priv->video_tee = NULL;
1813
1814       if (priv->video_preview != NULL)
1815         gtk_widget_destroy (priv->video_preview);
1816       priv->video_preview = NULL;
1817
1818       priv->funnel = NULL;
1819
1820       create_pipeline (self);
1821       /* Call will be started when user will hit the 'redial' button */
1822       priv->start_call_when_playing = FALSE;
1823       gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1824
1825       return TRUE;
1826     }
1827   else
1828     {
1829       g_message ("Error: could not destroy pipeline. Closing call window");
1830       gtk_widget_destroy (GTK_WIDGET (self));
1831
1832       return FALSE;
1833     }
1834 }
1835
1836 static void
1837 reset_details_pane (EmpathyStreamedMediaWindow *self)
1838 {
1839   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1840
1841   /* translators: encoding video codec is unknown */
1842   gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label),
1843       C_("encoding video codec", "Unknown"));
1844   /* translators: encoding audio codec is unknown */
1845   gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label),
1846       C_("encoding audio codec", "Unknown"));
1847   /* translators: decoding video codec is unknown */
1848   gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label),
1849       C_("decoding video codec", "Unknown"));
1850   /* translators: decoding audio codec is unknown */
1851   gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label),
1852       C_("decoding audio codec", "Unknown"));
1853 }
1854
1855 static gboolean
1856 empathy_streamed_media_window_disconnected (EmpathyStreamedMediaWindow *self,
1857     gboolean restart)
1858 {
1859   gboolean could_disconnect = FALSE;
1860   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1861   gboolean could_reset_pipeline;
1862
1863   /* Leave full screen mode if needed */
1864   gtk_window_unfullscreen (GTK_WINDOW (self));
1865
1866   gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1867
1868   could_reset_pipeline = empathy_streamed_media_window_reset_pipeline (self);
1869
1870   if (priv->call_state == CONNECTING)
1871       empathy_sound_manager_stop (priv->sound_mgr,
1872           EMPATHY_SOUND_PHONE_OUTGOING);
1873
1874   if (priv->call_state != REDIALING)
1875     priv->call_state = DISCONNECTED;
1876
1877   if (could_reset_pipeline)
1878     {
1879       g_mutex_lock (priv->lock);
1880
1881       g_timer_stop (priv->timer);
1882
1883       if (priv->timer_id != 0)
1884         g_source_remove (priv->timer_id);
1885       priv->timer_id = 0;
1886
1887       g_mutex_unlock (priv->lock);
1888
1889       if (!restart)
1890         /* We are about to destroy the window, no need to update it or create
1891          * a video preview */
1892         return TRUE;
1893
1894       empathy_streamed_media_window_status_message (self, _("Disconnected"));
1895
1896       gtk_action_set_sensitive (priv->redial, TRUE);
1897       gtk_widget_set_sensitive (priv->redial_button, TRUE);
1898
1899       /* Unsensitive the camera and mic button */
1900       gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1901       gtk_action_set_sensitive (priv->action_camera_on, FALSE);
1902       gtk_widget_set_sensitive (priv->mic_button, FALSE);
1903
1904       /* Be sure that the mic button is enabled */
1905       gtk_toggle_tool_button_set_active (
1906           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1907
1908       if (priv->camera_state == CAMERA_STATE_ON)
1909         {
1910           /* Enable the 'preview' button as we are not sending atm. */
1911           gtk_toggle_tool_button_set_active (
1912               GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_preview), TRUE);
1913         }
1914       else if (priv->camera_state == CAMERA_STATE_PREVIEW)
1915         {
1916           /* Restart the preview with the new pipeline. */
1917           display_video_preview (self, TRUE);
1918         }
1919
1920       gtk_progress_bar_set_fraction (
1921           GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1922
1923       /* destroy the video output; it will be recreated when we'll redial */
1924       disconnect_video_output_motion_handler (self);
1925       gtk_widget_destroy (priv->video_output);
1926       priv->video_output = NULL;
1927
1928       gtk_widget_show (priv->remote_user_avatar_widget);
1929
1930       reset_details_pane (self);
1931
1932       priv->sending_video = FALSE;
1933       priv->call_started = FALSE;
1934
1935       could_disconnect = TRUE;
1936
1937       /* TODO: display the self avatar of the preview (depends if the "Always
1938        * Show Video Preview" is enabled or not) */
1939     }
1940
1941   return could_disconnect;
1942 }
1943
1944
1945 static void
1946 empathy_streamed_media_window_channel_closed_cb (EmpathyStreamedMediaHandler *handler,
1947     gpointer user_data)
1948 {
1949   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1950   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1951
1952   if (empathy_streamed_media_window_disconnected (self, TRUE) &&
1953       priv->call_state == REDIALING)
1954       empathy_streamed_media_window_restart_call (self);
1955 }
1956
1957
1958 static void
1959 empathy_streamed_media_window_channel_stream_closed_cb (EmpathyStreamedMediaHandler *handler,
1960     TfStream *stream, gpointer user_data)
1961 {
1962   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1963   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1964   guint media_type;
1965
1966   g_object_get (stream, "media-type", &media_type, NULL);
1967
1968   /*
1969    * This assumes that there is only one video stream per channel...
1970    */
1971
1972   if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
1973     {
1974       if (priv->funnel != NULL)
1975         {
1976           GstElement *output;
1977
1978           output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1979               (priv->video_output));
1980
1981           gst_element_set_state (output, GST_STATE_NULL);
1982           gst_element_set_state (priv->funnel, GST_STATE_NULL);
1983
1984           gst_bin_remove (GST_BIN (priv->pipeline), output);
1985           gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
1986           priv->funnel = NULL;
1987         }
1988     }
1989   else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
1990     {
1991       if (priv->audio_output != NULL)
1992         {
1993           gst_element_set_state (priv->audio_output, GST_STATE_NULL);
1994
1995           gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
1996           priv->audio_output = NULL;
1997         }
1998     }
1999 }
2000
2001 /* Called with global lock held */
2002 static GstPad *
2003 empathy_streamed_media_window_get_video_sink_pad (EmpathyStreamedMediaWindow *self)
2004 {
2005   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2006   GstPad *pad;
2007   GstElement *output;
2008
2009   if (priv->funnel == NULL)
2010     {
2011       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
2012         (priv->video_output));
2013
2014       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
2015
2016       if (!priv->funnel)
2017         {
2018           g_warning ("Could not create fsfunnel");
2019           return NULL;
2020         }
2021
2022       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
2023         {
2024           gst_object_unref (priv->funnel);
2025           priv->funnel = NULL;
2026           g_warning ("Could  not add funnel to pipeline");
2027           return NULL;
2028         }
2029
2030       if (!gst_bin_add (GST_BIN (priv->pipeline), output))
2031         {
2032           g_warning ("Could not add the video output widget to the pipeline");
2033           goto error;
2034         }
2035
2036       if (!gst_element_link (priv->funnel, output))
2037         {
2038           g_warning ("Could not link output sink to funnel");
2039           goto error_output_added;
2040         }
2041
2042       if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2043         {
2044           g_warning ("Could not start video sink");
2045           goto error_output_added;
2046         }
2047
2048       if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2049         {
2050           g_warning ("Could not start funnel");
2051           goto error_output_added;
2052         }
2053     }
2054
2055   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
2056
2057   if (!pad)
2058     g_warning ("Could not get request pad from funnel");
2059
2060   return pad;
2061
2062
2063  error_output_added:
2064
2065   gst_element_set_locked_state (priv->funnel, TRUE);
2066   gst_element_set_locked_state (output, TRUE);
2067
2068   gst_element_set_state (priv->funnel, GST_STATE_NULL);
2069   gst_element_set_state (output, GST_STATE_NULL);
2070
2071   gst_bin_remove (GST_BIN (priv->pipeline), output);
2072   gst_element_set_locked_state (output, FALSE);
2073
2074  error:
2075
2076   gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
2077   priv->funnel = NULL;
2078
2079   return NULL;
2080 }
2081
2082 /* Called with global lock held */
2083 static GstPad *
2084 empathy_streamed_media_window_get_audio_sink_pad (EmpathyStreamedMediaWindow *self)
2085 {
2086   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2087   GstPad *pad;
2088   GstPadTemplate *template;
2089
2090   if (priv->audio_output == NULL)
2091     {
2092       priv->audio_output = empathy_audio_sink_new ();
2093
2094       if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
2095         {
2096           g_warning ("Could not add audio sink to pipeline");
2097           g_object_unref (priv->audio_output);
2098           goto error_add_output;
2099         }
2100
2101       if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2102         {
2103           g_warning ("Could not start audio sink");
2104           goto error;
2105         }
2106     }
2107
2108   template = gst_element_class_get_pad_template (
2109     GST_ELEMENT_GET_CLASS (priv->audio_output), "sink%d");
2110
2111   pad = gst_element_request_pad (priv->audio_output,
2112     template, NULL, NULL);
2113
2114   if (pad == NULL)
2115     {
2116       g_warning ("Could not get sink pad from sink");
2117       return NULL;
2118     }
2119
2120   return pad;
2121
2122 error:
2123   gst_element_set_locked_state (priv->audio_output, TRUE);
2124   gst_element_set_state (priv->audio_output, GST_STATE_NULL);
2125   gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
2126   priv->audio_output = NULL;
2127
2128 error_add_output:
2129
2130   return NULL;
2131 }
2132
2133 static gboolean
2134 empathy_streamed_media_window_update_timer (gpointer user_data)
2135 {
2136   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2137   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2138   gchar *str;
2139   gdouble time_;
2140
2141   time_ = g_timer_elapsed (priv->timer, NULL);
2142
2143   /* Translators: number of minutes:seconds the caller has been connected */
2144   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time_ / 60,
2145     (int) time_ % 60);
2146   empathy_streamed_media_window_status_message (self, str);
2147   g_free (str);
2148
2149   return TRUE;
2150 }
2151
2152 static void
2153 display_error (EmpathyStreamedMediaWindow *self,
2154     EmpathyTpStreamedMedia *call,
2155     const gchar *img,
2156     const gchar *title,
2157     const gchar *desc,
2158     const gchar *details)
2159 {
2160   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2161   GtkWidget *info_bar;
2162   GtkWidget *content_area;
2163   GtkWidget *hbox;
2164   GtkWidget *vbox;
2165   GtkWidget *image;
2166   GtkWidget *label;
2167   gchar *txt;
2168
2169   /* Create info bar */
2170   info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
2171       NULL);
2172
2173   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
2174
2175   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2176
2177   /* hbox containing the image and the messages vbox */
2178   hbox = gtk_hbox_new (FALSE, 3);
2179   gtk_container_add (GTK_CONTAINER (content_area), hbox);
2180
2181   /* Add image */
2182   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
2183   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2184
2185   /* vbox containing the main message and the details expander */
2186   vbox = gtk_vbox_new (FALSE, 3);
2187   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
2188
2189   /* Add text */
2190   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
2191
2192   label = gtk_label_new (NULL);
2193   gtk_label_set_markup (GTK_LABEL (label), txt);
2194   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2195   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2196   g_free (txt);
2197
2198   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
2199
2200   /* Add details */
2201   if (details != NULL)
2202     {
2203       GtkWidget *expander;
2204
2205       expander = gtk_expander_new (_("Technical Details"));
2206
2207       txt = g_strdup_printf ("<i>%s</i>", details);
2208
2209       label = gtk_label_new (NULL);
2210       gtk_label_set_markup (GTK_LABEL (label), txt);
2211       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2212       gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2213       g_free (txt);
2214
2215       gtk_container_add (GTK_CONTAINER (expander), label);
2216       gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
2217     }
2218
2219   g_signal_connect (info_bar, "response",
2220       G_CALLBACK (gtk_widget_destroy), NULL);
2221
2222   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
2223       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
2224   gtk_widget_show_all (info_bar);
2225 }
2226
2227 static gchar *
2228 media_stream_error_to_txt (EmpathyStreamedMediaWindow *self,
2229     EmpathyTpStreamedMedia *call,
2230     gboolean audio,
2231     TpMediaStreamError error)
2232 {
2233   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2234   const gchar *cm;
2235   gchar *url;
2236   gchar *result;
2237
2238   switch (error)
2239     {
2240       case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
2241         if (audio)
2242           return g_strdup_printf (
2243               _("%s's software does not understand any of the audio formats "
2244                 "supported by your computer"),
2245             empathy_contact_get_alias (priv->contact));
2246         else
2247           return g_strdup_printf (
2248               _("%s's software does not understand any of the video formats "
2249                 "supported by your computer"),
2250             empathy_contact_get_alias (priv->contact));
2251
2252       case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
2253         return g_strdup_printf (
2254             _("Can't establish a connection to %s. "
2255               "One of you might be on a network that does not allow "
2256               "direct connections."),
2257           empathy_contact_get_alias (priv->contact));
2258
2259       case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
2260           return g_strdup (_("There was a failure on the network"));
2261
2262       case TP_MEDIA_STREAM_ERROR_NO_CODECS:
2263         if (audio)
2264           return g_strdup (_("The audio formats necessary for this call "
2265                 "are not installed on your computer"));
2266         else
2267           return g_strdup (_("The video formats necessary for this call "
2268                 "are not installed on your computer"));
2269
2270       case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
2271         cm = empathy_tp_streamed_media_get_connection_manager (call);
2272
2273         url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
2274             "product=Telepathy&amp;component=%s", cm);
2275
2276         result = g_strdup_printf (
2277             _("Something unexpected happened in a Telepathy component. "
2278               "Please <a href=\"%s\">report this bug</a> and attach "
2279               "logs gathered from the 'Debug' window in the Help menu."), url);
2280
2281         g_free (url);
2282         return result;
2283
2284       case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
2285         return g_strdup (_("There was a failure in the call engine"));
2286
2287       case TP_MEDIA_STREAM_ERROR_EOS:
2288         return g_strdup (_("The end of the stream was reached"));
2289
2290       case TP_MEDIA_STREAM_ERROR_UNKNOWN:
2291       default:
2292         return NULL;
2293     }
2294 }
2295
2296 static void
2297 empathy_streamed_media_window_stream_error (EmpathyStreamedMediaWindow *self,
2298     EmpathyTpStreamedMedia *call,
2299     gboolean audio,
2300     guint code,
2301     const gchar *msg,
2302     const gchar *icon,
2303     const gchar *title)
2304 {
2305   gchar *desc;
2306
2307   desc = media_stream_error_to_txt (self, call, audio, code);
2308   if (desc == NULL)
2309     {
2310       /* No description, use the error message. That's not great as it's not
2311        * localized but it's better than nothing. */
2312       display_error (self, call, icon, title, msg, NULL);
2313     }
2314   else
2315     {
2316       display_error (self, call, icon, title, desc, msg);
2317       g_free (desc);
2318     }
2319 }
2320
2321 static void
2322 empathy_streamed_media_window_audio_stream_error (EmpathyTpStreamedMedia *call,
2323     guint code,
2324     const gchar *msg,
2325     EmpathyStreamedMediaWindow *self)
2326 {
2327   empathy_streamed_media_window_stream_error (self, call, TRUE, code, msg,
2328       "gnome-stock-mic", _("Can't establish audio stream"));
2329 }
2330
2331 static void
2332 empathy_streamed_media_window_video_stream_error (EmpathyTpStreamedMedia *call,
2333     guint code,
2334     const gchar *msg,
2335     EmpathyStreamedMediaWindow *self)
2336 {
2337   empathy_streamed_media_window_stream_error (self, call, FALSE, code, msg,
2338       "camera-web", _("Can't establish video stream"));
2339 }
2340
2341 static gboolean
2342 empathy_streamed_media_window_connected (gpointer user_data)
2343 {
2344   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2345   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2346   EmpathyTpStreamedMedia *call;
2347   gboolean can_send_video;
2348
2349   empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2350
2351   can_send_video = priv->video_input != NULL && priv->contact != NULL &&
2352     empathy_contact_can_voip_video (priv->contact);
2353
2354   g_object_get (priv->handler, "tp-call", &call, NULL);
2355
2356   tp_g_signal_connect_object (call, "notify::video-stream",
2357     G_CALLBACK (empathy_streamed_media_window_video_stream_changed_cb),
2358     self, 0);
2359
2360   if (empathy_tp_streamed_media_has_dtmf (call))
2361     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2362
2363   if (priv->video_input == NULL)
2364     empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_OFF);
2365
2366   priv->sending_video = can_send_video ?
2367     empathy_tp_streamed_media_is_sending_video (call) : FALSE;
2368
2369   gtk_toggle_tool_button_set_active (
2370       GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
2371       priv->sending_video && priv->video_input != NULL);
2372   gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
2373   gtk_action_set_sensitive (priv->action_camera_on, can_send_video);
2374
2375   gtk_action_set_sensitive (priv->redial, FALSE);
2376   gtk_widget_set_sensitive (priv->redial_button, FALSE);
2377
2378   gtk_widget_set_sensitive (priv->mic_button, TRUE);
2379
2380   empathy_streamed_media_window_update_avatars_visibility (call, self);
2381
2382   g_object_unref (call);
2383
2384   g_mutex_lock (priv->lock);
2385
2386   priv->timer_id = g_timeout_add_seconds (1,
2387     empathy_streamed_media_window_update_timer, self);
2388
2389   g_mutex_unlock (priv->lock);
2390
2391   empathy_streamed_media_window_update_timer (self);
2392
2393   gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
2394
2395   return FALSE;
2396 }
2397
2398
2399 /* Called from the streaming thread */
2400 static gboolean
2401 empathy_streamed_media_window_src_added_cb (EmpathyStreamedMediaHandler *handler,
2402   GstPad *src, guint media_type, gpointer user_data)
2403 {
2404   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2405   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2406   gboolean retval = FALSE;
2407
2408   GstPad *pad;
2409
2410   g_mutex_lock (priv->lock);
2411
2412   if (priv->call_state != CONNECTED)
2413     {
2414       g_timer_start (priv->timer);
2415       priv->timer_id = g_idle_add  (empathy_streamed_media_window_connected, self);
2416       priv->call_state = CONNECTED;
2417     }
2418
2419   switch (media_type)
2420     {
2421       case TP_MEDIA_STREAM_TYPE_AUDIO:
2422         pad = empathy_streamed_media_window_get_audio_sink_pad (self);
2423         break;
2424       case TP_MEDIA_STREAM_TYPE_VIDEO:
2425         gtk_widget_hide (priv->remote_user_avatar_widget);
2426         gtk_widget_show (priv->video_output);
2427         pad = empathy_streamed_media_window_get_video_sink_pad (self);
2428         break;
2429       default:
2430         g_assert_not_reached ();
2431     }
2432
2433   if (pad == NULL)
2434     goto out;
2435
2436   if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2437       g_warning ("Could not link %s sink pad",
2438           media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2439   else
2440       retval = TRUE;
2441
2442   gst_object_unref (pad);
2443
2444  out:
2445
2446   /* If no sink could be linked, try to add fakesink to prevent the whole call
2447    * aborting */
2448
2449   if (!retval)
2450     {
2451       GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2452
2453       if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2454         {
2455           GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2456           if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2457               GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2458             {
2459               gst_element_set_locked_state (fakesink, TRUE);
2460               gst_element_set_state (fakesink, GST_STATE_NULL);
2461               gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2462             }
2463           else
2464             {
2465               DEBUG ("Could not link real sink, linked fakesink instead");
2466             }
2467           gst_object_unref (sinkpad);
2468         }
2469       else
2470         {
2471           gst_object_unref (fakesink);
2472         }
2473     }
2474
2475
2476   g_mutex_unlock (priv->lock);
2477
2478   return TRUE;
2479 }
2480
2481 static gboolean
2482 empathy_streamed_media_window_sink_added_cb (EmpathyStreamedMediaHandler *handler,
2483   GstPad *sink, guint media_type, gpointer user_data)
2484 {
2485   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2486   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2487   GstPad *pad;
2488   gboolean retval = FALSE;
2489
2490   switch (media_type)
2491     {
2492       case TP_MEDIA_STREAM_TYPE_AUDIO:
2493         if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2494           {
2495             g_warning ("Could not add audio source to pipeline");
2496             break;
2497           }
2498
2499         pad = gst_element_get_static_pad (priv->audio_input, "src");
2500         if (!pad)
2501           {
2502             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2503             g_warning ("Could not get source pad from audio source");
2504             break;
2505           }
2506
2507         if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2508           {
2509             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2510             g_warning ("Could not link audio source to farsight");
2511             break;
2512           }
2513
2514         if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2515           {
2516             g_warning ("Could not start audio source");
2517             gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2518             gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2519             break;
2520           }
2521
2522         retval = TRUE;
2523         break;
2524       case TP_MEDIA_STREAM_TYPE_VIDEO:
2525         if (priv->video_input != NULL)
2526           {
2527             if (priv->video_tee != NULL)
2528               {
2529                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2530                 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2531                   {
2532                     g_warning ("Could not link videp soure input pipeline");
2533                     break;
2534                   }
2535                 gst_object_unref (pad);
2536               }
2537
2538             retval = TRUE;
2539           }
2540         break;
2541       default:
2542         g_assert_not_reached ();
2543     }
2544
2545   return retval;
2546 }
2547
2548 static void
2549 empathy_streamed_media_window_remove_video_input (EmpathyStreamedMediaWindow *self)
2550 {
2551   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2552   GstElement *preview;
2553
2554   disable_camera (self);
2555
2556   DEBUG ("remove video input");
2557   preview = empathy_video_widget_get_element (
2558     EMPATHY_VIDEO_WIDGET (priv->video_preview));
2559
2560   gst_element_set_state (priv->video_input, GST_STATE_NULL);
2561   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2562   gst_element_set_state (preview, GST_STATE_NULL);
2563
2564   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2565     priv->video_tee, preview, NULL);
2566
2567   g_object_unref (priv->video_input);
2568   priv->video_input = NULL;
2569   g_object_unref (priv->video_tee);
2570   priv->video_tee = NULL;
2571   gtk_widget_destroy (priv->video_preview);
2572   priv->video_preview = NULL;
2573
2574   gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
2575   gtk_action_set_sensitive (priv->action_camera_on, FALSE);
2576   gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
2577 }
2578
2579 static void
2580 start_call (EmpathyStreamedMediaWindow *self)
2581 {
2582   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2583
2584   priv->call_started = TRUE;
2585   empathy_streamed_media_handler_start_call (priv->handler,
2586       empathy_get_current_action_time ());
2587
2588   if (empathy_streamed_media_handler_has_initial_video (priv->handler))
2589     {
2590       /* Enable 'send video' buttons and display the preview */
2591       gtk_toggle_tool_button_set_active (
2592           GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
2593     }
2594 }
2595
2596 static gboolean
2597 empathy_streamed_media_window_bus_message (GstBus *bus, GstMessage *message,
2598   gpointer user_data)
2599 {
2600   EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2601   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2602   GstState newstate;
2603
2604   empathy_streamed_media_handler_bus_message (priv->handler, bus, message);
2605
2606   switch (GST_MESSAGE_TYPE (message))
2607     {
2608       case GST_MESSAGE_STATE_CHANGED:
2609         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2610           {
2611             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2612             if (newstate == GST_STATE_PAUSED)
2613                 empathy_streamed_media_window_setup_video_input (self);
2614           }
2615         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2616             !priv->call_started)
2617           {
2618             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2619             if (newstate == GST_STATE_PAUSED)
2620               {
2621                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2622                 priv->pipeline_playing = TRUE;
2623
2624                 if (priv->start_call_when_playing)
2625                   start_call (self);
2626               }
2627           }
2628         break;
2629       case GST_MESSAGE_ERROR:
2630         {
2631           GError *error = NULL;
2632           GstElement *gst_error;
2633           gchar *debug;
2634
2635           gst_message_parse_error (message, &error, &debug);
2636           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2637
2638           g_message ("Element error: %s -- %s\n", error->message, debug);
2639
2640           if (g_str_has_prefix (gst_element_get_name (gst_error),
2641                 VIDEO_INPUT_ERROR_PREFIX))
2642             {
2643               /* Remove the video input and continue */
2644               if (priv->video_input != NULL)
2645                 empathy_streamed_media_window_remove_video_input (self);
2646               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2647             }
2648           else
2649             {
2650               empathy_streamed_media_window_disconnected (self, TRUE);
2651             }
2652           g_error_free (error);
2653           g_free (debug);
2654         }
2655       case GST_MESSAGE_UNKNOWN:
2656       case GST_MESSAGE_EOS:
2657       case GST_MESSAGE_WARNING:
2658       case GST_MESSAGE_INFO:
2659       case GST_MESSAGE_TAG:
2660       case GST_MESSAGE_BUFFERING:
2661       case GST_MESSAGE_STATE_DIRTY:
2662       case GST_MESSAGE_STEP_DONE:
2663       case GST_MESSAGE_CLOCK_PROVIDE:
2664       case GST_MESSAGE_CLOCK_LOST:
2665       case GST_MESSAGE_NEW_CLOCK:
2666       case GST_MESSAGE_STRUCTURE_CHANGE:
2667       case GST_MESSAGE_STREAM_STATUS:
2668       case GST_MESSAGE_APPLICATION:
2669       case GST_MESSAGE_ELEMENT:
2670       case GST_MESSAGE_SEGMENT_START:
2671       case GST_MESSAGE_SEGMENT_DONE:
2672       case GST_MESSAGE_DURATION:
2673       case GST_MESSAGE_ANY:
2674       default:
2675         break;
2676     }
2677
2678   return TRUE;
2679 }
2680
2681 static void
2682 empathy_streamed_media_window_update_avatars_visibility (EmpathyTpStreamedMedia *call,
2683     EmpathyStreamedMediaWindow *window)
2684 {
2685   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2686
2687   if (empathy_tp_streamed_media_is_receiving_video (call))
2688     {
2689       gtk_widget_hide (priv->remote_user_avatar_widget);
2690       gtk_widget_show (priv->video_output);
2691     }
2692   else
2693     {
2694       gtk_widget_hide (priv->video_output);
2695       gtk_widget_show (priv->remote_user_avatar_widget);
2696     }
2697 }
2698
2699 static void
2700 call_handler_notify_tp_streamed_media_cb (EmpathyStreamedMediaHandler *handler,
2701     GParamSpec *spec,
2702     EmpathyStreamedMediaWindow *self)
2703 {
2704   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2705   EmpathyTpStreamedMedia *call;
2706
2707   g_object_get (priv->handler, "tp-call", &call, NULL);
2708   if (call == NULL)
2709     return;
2710
2711   tp_g_signal_connect_object (call, "audio-stream-error",
2712       G_CALLBACK (empathy_streamed_media_window_audio_stream_error), self, 0);
2713   tp_g_signal_connect_object (call, "video-stream-error",
2714       G_CALLBACK (empathy_streamed_media_window_video_stream_error), self, 0);
2715
2716   g_object_unref (call);
2717 }
2718
2719 static void
2720 empathy_streamed_media_window_realized_cb (GtkWidget *widget, EmpathyStreamedMediaWindow *window)
2721 {
2722   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2723   EmpathyTpStreamedMedia *call;
2724
2725   g_signal_connect (priv->handler, "conference-added",
2726     G_CALLBACK (empathy_streamed_media_window_conference_added_cb), window);
2727   g_signal_connect (priv->handler, "request-resource",
2728     G_CALLBACK (empathy_streamed_media_window_request_resource_cb), window);
2729   g_signal_connect (priv->handler, "closed",
2730     G_CALLBACK (empathy_streamed_media_window_channel_closed_cb), window);
2731   g_signal_connect (priv->handler, "src-pad-added",
2732     G_CALLBACK (empathy_streamed_media_window_src_added_cb), window);
2733   g_signal_connect (priv->handler, "sink-pad-added",
2734     G_CALLBACK (empathy_streamed_media_window_sink_added_cb), window);
2735   g_signal_connect (priv->handler, "stream-closed",
2736     G_CALLBACK (empathy_streamed_media_window_channel_stream_closed_cb), window);
2737
2738   g_object_get (priv->handler, "tp-call", &call, NULL);
2739   if (call != NULL)
2740     {
2741       tp_g_signal_connect_object (call, "audio-stream-error",
2742         G_CALLBACK (empathy_streamed_media_window_audio_stream_error), window,
2743         0);
2744       tp_g_signal_connect_object (call, "video-stream-error",
2745         G_CALLBACK (empathy_streamed_media_window_video_stream_error), window,
2746         0);
2747
2748       g_object_unref (call);
2749     }
2750   else
2751     {
2752       /* tp-call doesn't exist yet, we'll connect signals once it has been
2753        * set */
2754       g_signal_connect (priv->handler, "notify::tp-call",
2755         G_CALLBACK (call_handler_notify_tp_streamed_media_cb), window);
2756     }
2757
2758   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2759 }
2760
2761 static gboolean
2762 empathy_streamed_media_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2763   EmpathyStreamedMediaWindow *window)
2764 {
2765   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2766
2767   if (priv->pipeline != NULL)
2768     {
2769       if (priv->bus_message_source_id != 0)
2770         {
2771           g_source_remove (priv->bus_message_source_id);
2772           priv->bus_message_source_id = 0;
2773         }
2774
2775       gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2776     }
2777
2778   if (priv->call_state == CONNECTING)
2779     empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2780
2781   return FALSE;
2782 }
2783
2784 static void
2785 show_controls (EmpathyStreamedMediaWindow *window, gboolean set_fullscreen)
2786 {
2787   GtkWidget *menu;
2788   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2789
2790   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2791             "/menubar1");
2792
2793   if (set_fullscreen)
2794     {
2795       gtk_widget_hide (priv->sidebar);
2796       gtk_widget_hide (menu);
2797       gtk_widget_hide (priv->vbox);
2798       gtk_widget_hide (priv->statusbar);
2799       gtk_widget_hide (priv->toolbar);
2800     }
2801   else
2802     {
2803       if (priv->sidebar_was_visible_before_fs)
2804         gtk_widget_show (priv->sidebar);
2805
2806       gtk_widget_show (menu);
2807       gtk_widget_show (priv->vbox);
2808       gtk_widget_show (priv->statusbar);
2809       gtk_widget_show (priv->toolbar);
2810
2811       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2812           priv->original_height_before_fs);
2813     }
2814 }
2815
2816 static void
2817 show_borders (EmpathyStreamedMediaWindow *window, gboolean set_fullscreen)
2818 {
2819   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2820
2821   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2822       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2823   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2824       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2825
2826   if (priv->video_output != NULL)
2827     {
2828       gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2829           priv->video_output, TRUE, TRUE,
2830           set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2831           GTK_PACK_START);
2832     }
2833
2834   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2835       priv->vbox, TRUE, TRUE,
2836       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2837       GTK_PACK_START);
2838 }
2839
2840 static gboolean
2841 empathy_streamed_media_window_state_event_cb (GtkWidget *widget,
2842   GdkEventWindowState *event, EmpathyStreamedMediaWindow *window)
2843 {
2844   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2845     {
2846       EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2847       gboolean set_fullscreen = event->new_window_state &
2848         GDK_WINDOW_STATE_FULLSCREEN;
2849
2850       if (set_fullscreen)
2851         {
2852           gboolean sidebar_was_visible;
2853           GtkAllocation allocation;
2854           gint original_width, original_height;
2855
2856           gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2857           original_width = allocation.width;
2858           original_height = allocation.height;
2859
2860           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2861
2862           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2863           priv->original_width_before_fs = original_width;
2864           priv->original_height_before_fs = original_height;
2865
2866           if (priv->video_output_motion_handler_id == 0 &&
2867                 priv->video_output != NULL)
2868             {
2869               priv->video_output_motion_handler_id = g_signal_connect (
2870                   G_OBJECT (priv->video_output), "motion-notify-event",
2871                   G_CALLBACK (empathy_streamed_media_window_video_output_motion_notify),
2872                   window);
2873             }
2874         }
2875       else
2876         {
2877           disconnect_video_output_motion_handler (window);
2878         }
2879
2880       empathy_streamed_media_window_fullscreen_set_fullscreen (priv->fullscreen,
2881           set_fullscreen);
2882       show_controls (window, set_fullscreen);
2883       show_borders (window, set_fullscreen);
2884       gtk_action_set_stock_id (priv->menu_fullscreen,
2885           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
2886       priv->is_fullscreen = set_fullscreen;
2887   }
2888
2889   return FALSE;
2890 }
2891
2892 static void
2893 empathy_streamed_media_window_sidebar_toggled_cb (GtkToggleButton *toggle,
2894   EmpathyStreamedMediaWindow *window)
2895 {
2896   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2897   GtkWidget *arrow;
2898   int w, h, handle_size;
2899   GtkAllocation allocation, sidebar_allocation;
2900
2901   gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2902   w = allocation.width;
2903   h = allocation.height;
2904
2905   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
2906
2907   gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
2908   if (gtk_toggle_button_get_active (toggle))
2909     {
2910       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
2911       gtk_widget_show (priv->sidebar);
2912       w += sidebar_allocation.width + handle_size;
2913     }
2914   else
2915     {
2916       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
2917       w -= sidebar_allocation.width + handle_size;
2918       gtk_widget_hide (priv->sidebar);
2919     }
2920
2921   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
2922
2923   if (w > 0 && h > 0)
2924     gtk_window_resize (GTK_WINDOW (window), w, h);
2925 }
2926
2927 static void
2928 empathy_streamed_media_window_set_send_video (EmpathyStreamedMediaWindow *window,
2929   CameraState state)
2930 {
2931   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2932   EmpathyTpStreamedMedia *call;
2933
2934   priv->sending_video = (state == CAMERA_STATE_ON);
2935
2936   if (state == CAMERA_STATE_PREVIEW ||
2937       state == CAMERA_STATE_ON)
2938     {
2939       /* When we start sending video, we want to show the video preview by
2940          default. */
2941       display_video_preview (window, TRUE);
2942     }
2943   else
2944     {
2945       display_video_preview (window, FALSE);
2946     }
2947
2948   if (priv->call_state != CONNECTED)
2949     return;
2950
2951   g_object_get (priv->handler, "tp-call", &call, NULL);
2952   DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
2953   empathy_tp_streamed_media_request_video_stream_direction (call, priv->sending_video);
2954   g_object_unref (call);
2955 }
2956
2957 static void
2958 empathy_streamed_media_window_mic_toggled_cb (GtkToggleToolButton *toggle,
2959   EmpathyStreamedMediaWindow *window)
2960 {
2961   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2962   gboolean active;
2963
2964   active = (gtk_toggle_tool_button_get_active (toggle));
2965
2966   if (active)
2967     {
2968       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2969         priv->volume);
2970       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
2971     }
2972   else
2973     {
2974       /* TODO, Instead of setting the input volume to 0 we should probably
2975        * stop sending but this would cause the audio call to drop if both
2976        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
2977        * in the future. GNOME #574574
2978        */
2979       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
2980         0);
2981       gtk_adjustment_set_value (priv->audio_input_adj, 0);
2982     }
2983 }
2984
2985 static void
2986 empathy_streamed_media_window_sidebar_hidden_cb (EvSidebar *sidebar,
2987   EmpathyStreamedMediaWindow *window)
2988 {
2989   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2990
2991   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
2992     FALSE);
2993 }
2994
2995 static void
2996 empathy_streamed_media_window_sidebar_shown_cb (EvSidebar *sidebar,
2997   EmpathyStreamedMediaWindow *window)
2998 {
2999   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3000
3001   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
3002     TRUE);
3003 }
3004
3005 static void
3006 empathy_streamed_media_window_hangup_cb (gpointer object,
3007                                EmpathyStreamedMediaWindow *window)
3008 {
3009   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3010
3011   empathy_streamed_media_handler_stop_call (priv->handler);
3012
3013   if (empathy_streamed_media_window_disconnected (window, FALSE))
3014     gtk_widget_destroy (GTK_WIDGET (window));
3015 }
3016
3017 static void
3018 empathy_streamed_media_window_restart_call (EmpathyStreamedMediaWindow *window)
3019 {
3020   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3021
3022   /* Remove error info bars */
3023   gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
3024       (GtkCallback) gtk_widget_destroy, NULL);
3025
3026   create_video_output_widget (window);
3027
3028   g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
3029       G_CALLBACK (empathy_streamed_media_window_mic_volume_changed_cb), window);
3030
3031   /* While the call was disconnected, the input volume might have changed.
3032    * However, since the audio_input source was destroyed, its volume has not
3033    * been updated during that time. That's why we manually update it here */
3034   empathy_streamed_media_window_mic_volume_changed_cb (priv->audio_input_adj, window);
3035
3036   priv->outgoing = TRUE;
3037   empathy_streamed_media_window_set_state_connecting (window);
3038
3039   if (priv->pipeline_playing)
3040     start_call (window);
3041   else
3042     /* call will be started when the pipeline is ready */
3043     priv->start_call_when_playing = TRUE;
3044
3045
3046   empathy_streamed_media_window_setup_avatars (window, priv->handler);
3047
3048   gtk_action_set_sensitive (priv->redial, FALSE);
3049   gtk_widget_set_sensitive (priv->redial_button, FALSE);
3050 }
3051
3052 static void
3053 empathy_streamed_media_window_redial_cb (gpointer object,
3054     EmpathyStreamedMediaWindow *window)
3055 {
3056   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3057
3058   if (priv->call_state == CONNECTED)
3059     priv->call_state = REDIALING;
3060
3061   empathy_streamed_media_handler_stop_call (priv->handler);
3062
3063   if (priv->call_state != CONNECTED)
3064     empathy_streamed_media_window_restart_call (window);
3065 }
3066
3067 static void
3068 empathy_streamed_media_window_fullscreen_cb (gpointer object,
3069                                    EmpathyStreamedMediaWindow *window)
3070 {
3071   empathy_streamed_media_window_fullscreen_toggle (window);
3072 }
3073
3074 static void
3075 empathy_streamed_media_window_fullscreen_toggle (EmpathyStreamedMediaWindow *window)
3076 {
3077   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3078
3079   if (priv->is_fullscreen)
3080     gtk_window_unfullscreen (GTK_WINDOW (window));
3081   else
3082     gtk_window_fullscreen (GTK_WINDOW (window));
3083 }
3084
3085 static gboolean
3086 empathy_streamed_media_window_video_button_press_cb (GtkWidget *video_output,
3087   GdkEventButton *event, EmpathyStreamedMediaWindow *window)
3088 {
3089   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
3090     {
3091       empathy_streamed_media_window_video_menu_popup (window, event->button);
3092       return TRUE;
3093     }
3094
3095   return FALSE;
3096 }
3097
3098 static gboolean
3099 empathy_streamed_media_window_key_press_cb (GtkWidget *video_output,
3100   GdkEventKey *event, EmpathyStreamedMediaWindow *window)
3101 {
3102   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3103
3104   if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
3105     {
3106       /* Since we are in fullscreen mode, toggling will bring us back to
3107          normal mode. */
3108       empathy_streamed_media_window_fullscreen_toggle (window);
3109       return TRUE;
3110     }
3111
3112   return FALSE;
3113 }
3114
3115 static gboolean
3116 empathy_streamed_media_window_video_output_motion_notify (GtkWidget *widget,
3117     GdkEventMotion *event, EmpathyStreamedMediaWindow *window)
3118 {
3119   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3120
3121   if (priv->is_fullscreen)
3122     {
3123       empathy_streamed_media_window_fullscreen_show_popup (priv->fullscreen);
3124       return TRUE;
3125     }
3126   return FALSE;
3127 }
3128
3129 static void
3130 empathy_streamed_media_window_video_menu_popup (EmpathyStreamedMediaWindow *window,
3131   guint button)
3132 {
3133   GtkWidget *menu;
3134   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3135
3136   menu = gtk_ui_manager_get_widget (priv->ui_manager,
3137             "/video-popup");
3138   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
3139       button, gtk_get_current_event_time ());
3140   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
3141 }
3142
3143 static void
3144 empathy_streamed_media_window_status_message (EmpathyStreamedMediaWindow *window,
3145   gchar *message)
3146 {
3147   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3148
3149   if (priv->context_id == 0)
3150     {
3151       priv->context_id = gtk_statusbar_get_context_id (
3152         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
3153     }
3154   else
3155     {
3156       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
3157     }
3158
3159   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
3160     message);
3161 }
3162
3163 static void
3164 empathy_streamed_media_window_volume_changed_cb (GtkScaleButton *button,
3165   gdouble value, EmpathyStreamedMediaWindow *window)
3166 {
3167   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3168
3169   if (priv->audio_output == NULL)
3170     return;
3171
3172   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
3173     value);
3174 }
3175
3176 /* block all the signals related to camera control widgets. This is useful
3177  * when we are manually updating the UI and so don't want to fire the
3178  * callbacks */
3179 static void
3180 block_camera_control_signals (EmpathyStreamedMediaWindow *self)
3181 {
3182   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
3183
3184   g_signal_handlers_block_by_func (priv->tool_button_camera_off,
3185       tool_button_camera_off_toggled_cb, self);
3186   g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
3187       tool_button_camera_preview_toggled_cb, self);
3188   g_signal_handlers_block_by_func (priv->tool_button_camera_on,
3189       tool_button_camera_on_toggled_cb, self);
3190   g_signal_handlers_block_by_func (priv->action_camera_on,
3191       action_camera_change_cb, self);
3192 }
3193
3194 static void
3195 unblock_camera_control_signals (EmpathyStreamedMediaWindow *self)
3196 {
3197   EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
3198
3199   g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
3200       tool_button_camera_off_toggled_cb, self);
3201   g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
3202       tool_button_camera_preview_toggled_cb, self);
3203   g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
3204       tool_button_camera_on_toggled_cb, self);
3205   g_signal_handlers_unblock_by_func (priv->action_camera_on,
3206       action_camera_change_cb, self);
3207 }