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