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