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