]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
Merge commit 'jtellier/bug-580771'
[empathy.git] / src / empathy-call-window.c
1 /*
2  * empathy-call-window.c - Source for EmpathyCallWindow
3  * Copyright (C) 2008 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-utils.h>
35 #include <libempathy-gtk/empathy-video-widget.h>
36 #include <libempathy-gtk/empathy-audio-src.h>
37 #include <libempathy-gtk/empathy-audio-sink.h>
38 #include <libempathy-gtk/empathy-video-src.h>
39 #include <libempathy-gtk/empathy-ui-utils.h>
40
41 #include "empathy-call-window.h"
42
43 #include "empathy-call-window-fullscreen.h"
44 #include "empathy-sidebar.h"
45
46 #define BUTTON_ID "empathy-call-dtmf-button-id"
47
48 #define CONTENT_HBOX_BORDER_WIDTH 6
49 #define CONTENT_HBOX_SPACING 3
50 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
51
52 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
53
54 /* signal enum */
55 #if 0
56 enum
57 {
58     LAST_SIGNAL
59 };
60
61 static guint signals[LAST_SIGNAL] = {0};
62 #endif
63
64 enum {
65   PROP_CALL_HANDLER = 1,
66 };
67
68 /* private structure */
69 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
70
71 struct _EmpathyCallWindowPriv
72 {
73   gboolean dispose_has_run;
74   EmpathyCallHandler *handler;
75   EmpathyContact *contact;
76
77   gboolean connected;
78
79   GtkUIManager *ui_manager;
80   GtkWidget *video_output;
81   GtkWidget *video_preview;
82   GtkWidget *sidebar;
83   GtkWidget *sidebar_button;
84   GtkWidget *statusbar;
85   GtkWidget *volume_button;
86   GtkWidget *mic_button;
87   GtkWidget *camera_button;
88   GtkWidget *toolbar;
89   GtkWidget *pane;
90   GtkAction *send_video;
91   GtkAction *menu_fullscreen;
92
93   /* We keep a reference on the hbox which contains the main content so we can
94      easilly repack everything when toggling fullscreen */
95   GtkWidget *content_hbox;
96
97   /* This vbox is contained in the content_hbox. When toggling fullscreen,
98      it needs to be repacked. We keep a reference on it for easier access. */
99   GtkWidget *vbox;
100
101   gulong video_output_motion_handler_id;
102
103   gdouble volume;
104   GtkAdjustment *audio_input_adj;
105
106   GtkWidget *dtmf_panel;
107
108   GstElement *video_input;
109   GstElement *audio_input;
110   GstElement *audio_output;
111   GstElement *pipeline;
112   GstElement *video_tee;
113
114   GstElement *funnel;
115   GstElement *liveadder;
116
117   guint context_id;
118
119   GTimer *timer;
120   guint timer_id;
121
122   GtkWidget *video_contrast;
123   GtkWidget *video_brightness;
124   GtkWidget *video_gamma;
125
126   GMutex *lock;
127   gboolean call_started;
128   gboolean sending_video;
129
130   EmpathyCallWindowFullscreen *fullscreen;
131   gboolean is_fullscreen;
132
133   /* Those fields represent the state of the window before it actually was in
134      fullscreen mode. */
135   gboolean sidebar_was_visible_before_fs;
136   gint original_width_before_fs;
137   gint original_height_before_fs;
138 };
139
140 #define GET_PRIV(o) \
141   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW, \
142     EmpathyCallWindowPriv))
143
144 static void empathy_call_window_realized_cb (GtkWidget *widget,
145   EmpathyCallWindow *window);
146
147 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
148   GdkEvent *event, EmpathyCallWindow *window);
149
150 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
151   GdkEventWindowState *event, EmpathyCallWindow *window);
152
153 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
154   EmpathyCallWindow *window);
155
156 static void empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
157   EmpathyCallWindow *window);
158
159 static void empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
160   EmpathyCallWindow *window);
161
162 static void empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
163   EmpathyCallWindow *window);
164
165 static void empathy_call_window_mic_toggled_cb (
166   GtkToggleToolButton *toggle, EmpathyCallWindow *window);
167
168 static void empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
169   EmpathyCallWindow *window);
170
171 static void empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
172   EmpathyCallWindow *window);
173
174 static void empathy_call_window_hangup_cb (gpointer object,
175   EmpathyCallWindow *window);
176
177 static void empathy_call_window_fullscreen_cb (gpointer object,
178   EmpathyCallWindow *window);
179
180 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
181
182 static gboolean empathy_call_window_video_button_press_cb (GtkWidget *video_output,
183   GdkEventButton *event, EmpathyCallWindow *window);
184
185 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
186   GdkEventKey *event, EmpathyCallWindow *window);
187
188 static gboolean empathy_call_window_video_output_motion_notify (GtkWidget *widget,
189   GdkEventMotion *event, EmpathyCallWindow *window);
190
191 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
192   guint button);
193
194 static void empathy_call_window_status_message (EmpathyCallWindow *window,
195   gchar *message);
196
197 static gboolean empathy_call_window_bus_message (GstBus *bus,
198   GstMessage *message, gpointer user_data);
199
200 static void
201 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
202   gdouble value, EmpathyCallWindow *window);
203
204 static void
205 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
206 {
207   EmpathyCallWindowPriv *priv = GET_PRIV (self);
208   GtkToolItem *tool_item;
209
210   /* Add an empty expanded GtkToolItem so the volume button is at the end of
211    * the toolbar. */
212   tool_item = gtk_tool_item_new ();
213   gtk_tool_item_set_expand (tool_item, TRUE);
214   gtk_widget_show (GTK_WIDGET (tool_item));
215   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
216
217   priv->volume_button = gtk_volume_button_new ();
218   /* FIXME listen to the audiosinks signals and update the button according to
219    * that, for now starting out at 1.0 and assuming only the app changes the
220    * volume will do */
221   gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
222   g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
223     G_CALLBACK (empathy_call_window_volume_changed_cb), self);
224
225   tool_item = gtk_tool_item_new ();
226   gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
227   gtk_widget_show_all (GTK_WIDGET (tool_item));
228   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
229 }
230
231 static void
232 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
233 {
234   EmpathyCallWindowPriv *priv = GET_PRIV (window);
235   EmpathyTpCall *call;
236   GQuark button_quark;
237   TpDTMFEvent event;
238
239   g_object_get (priv->handler, "tp-call", &call, NULL);
240
241   button_quark = g_quark_from_static_string (BUTTON_ID);
242   event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
243     button_quark));
244
245   empathy_tp_call_start_tone (call, event);
246
247   g_object_unref (call);
248 }
249
250 static void
251 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
252 {
253   EmpathyCallWindowPriv *priv = GET_PRIV (window);
254   EmpathyTpCall *call;
255
256   g_object_get (priv->handler, "tp-call", &call, NULL);
257
258   empathy_tp_call_stop_tone (call);
259
260   g_object_unref (call);
261 }
262
263 static GtkWidget *
264 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
265 {
266   GtkWidget *table;
267   int i;
268   GQuark button_quark;
269   struct {
270     gchar *label;
271     TpDTMFEvent event;
272   } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
273                       { "2", TP_DTMF_EVENT_DIGIT_2 },
274                       { "3", TP_DTMF_EVENT_DIGIT_3 },
275                       { "4", TP_DTMF_EVENT_DIGIT_4 },
276                       { "5", TP_DTMF_EVENT_DIGIT_5 },
277                       { "6", TP_DTMF_EVENT_DIGIT_6 },
278                       { "7", TP_DTMF_EVENT_DIGIT_7 },
279                       { "8", TP_DTMF_EVENT_DIGIT_8 },
280                       { "9", TP_DTMF_EVENT_DIGIT_9 },
281                       { "#", TP_DTMF_EVENT_HASH },
282                       { "0", TP_DTMF_EVENT_DIGIT_0 },
283                       { "*", TP_DTMF_EVENT_ASTERISK },
284                       { NULL, } };
285
286   button_quark = g_quark_from_static_string (BUTTON_ID);
287
288   table = gtk_table_new (4, 3, TRUE);
289
290   for (i = 0; dtmfbuttons[i].label != NULL; i++)
291     {
292       GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
293       gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
294         i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
295
296       g_object_set_qdata (G_OBJECT (button), button_quark,
297         GUINT_TO_POINTER (dtmfbuttons[i].event));
298
299       g_signal_connect (G_OBJECT (button), "pressed",
300         G_CALLBACK (dtmf_button_pressed_cb), self);
301       g_signal_connect (G_OBJECT (button), "released",
302         G_CALLBACK (dtmf_button_released_cb), self);
303     }
304
305   return table;
306 }
307
308 static GtkWidget *
309 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
310   gchar *label_text, GtkWidget *bin)
311 {
312    GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
313    GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
314    GtkWidget *label = gtk_label_new (label_text);
315
316    gtk_widget_set_sensitive (scale, FALSE);
317
318    gtk_container_add (GTK_CONTAINER (bin), vbox);
319
320    gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
321    gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
322    gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
323
324    return scale;
325 }
326
327 static void
328 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
329   EmpathyCallWindow *self)
330
331 {
332   EmpathyCallWindowPriv *priv = GET_PRIV (self);
333
334   empathy_video_src_set_channel (priv->video_input,
335     EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
336 }
337
338 static void
339 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
340   EmpathyCallWindow *self)
341
342 {
343   EmpathyCallWindowPriv *priv = GET_PRIV (self);
344
345   empathy_video_src_set_channel (priv->video_input,
346     EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
347 }
348
349 static void
350 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
351   EmpathyCallWindow *self)
352
353 {
354   EmpathyCallWindowPriv *priv = GET_PRIV (self);
355
356   empathy_video_src_set_channel (priv->video_input,
357     EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
358 }
359
360
361 static GtkWidget *
362 empathy_call_window_create_video_input (EmpathyCallWindow *self)
363 {
364   EmpathyCallWindowPriv *priv = GET_PRIV (self);
365   GtkWidget *hbox;
366
367   hbox = gtk_hbox_new (TRUE, 3);
368
369   priv->video_contrast = empathy_call_window_create_video_input_add_slider (
370     self,  _("Contrast"), hbox);
371
372   priv->video_brightness = empathy_call_window_create_video_input_add_slider (
373     self,  _("Brightness"), hbox);
374
375   priv->video_gamma = empathy_call_window_create_video_input_add_slider (
376     self,  _("Gamma"), hbox);
377
378   return hbox;
379 }
380
381 static void
382 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
383 {
384   EmpathyCallWindowPriv *priv = GET_PRIV (self);
385   guint supported;
386   GtkAdjustment *adj;
387
388   supported = empathy_video_src_get_supported_channels (priv->video_input);
389
390   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
391     {
392       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
393
394       gtk_adjustment_set_value (adj,
395         empathy_video_src_get_channel (priv->video_input,
396           EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
397
398       g_signal_connect (G_OBJECT (adj), "value-changed",
399         G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
400
401       gtk_widget_set_sensitive (priv->video_contrast, TRUE);
402     }
403
404   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
405     {
406       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
407
408       gtk_adjustment_set_value (adj,
409         empathy_video_src_get_channel (priv->video_input,
410           EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
411
412       g_signal_connect (G_OBJECT (adj), "value-changed",
413         G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
414       gtk_widget_set_sensitive (priv->video_brightness, TRUE);
415     }
416
417   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
418     {
419       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
420
421       gtk_adjustment_set_value (adj,
422         empathy_video_src_get_channel (priv->video_input,
423           EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
424
425       g_signal_connect (G_OBJECT (adj), "value-changed",
426         G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
427       gtk_widget_set_sensitive (priv->video_gamma, TRUE);
428     }
429 }
430
431 static void
432 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
433   EmpathyCallWindow *self)
434 {
435   EmpathyCallWindowPriv *priv = GET_PRIV (self);
436   gdouble volume;
437
438   volume =  gtk_adjustment_get_value (adj)/100.0;
439
440   /* Don't store the volume because of muting */
441   if (volume > 0 || gtk_toggle_tool_button_get_active (
442         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
443     priv->volume = volume;
444
445   /* Ensure that the toggle button is active if the volume is > 0 and inactive
446    * if it's smaller then 0 */
447   if ((volume > 0) != gtk_toggle_tool_button_get_active (
448         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
449     gtk_toggle_tool_button_set_active (
450       GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
451
452   empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
453     volume);
454 }
455
456 static void
457 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
458   gdouble level, GtkProgressBar *bar)
459 {
460   gdouble value;
461
462   value = CLAMP (pow (10, level / 20), 0.0, 1.0);
463   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), value);
464 }
465
466 static GtkWidget *
467 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
468 {
469   EmpathyCallWindowPriv *priv = GET_PRIV (self);
470   GtkWidget *hbox, *vbox, *scale, *progress, *label;
471   GtkAdjustment *adj;
472
473   hbox = gtk_hbox_new (TRUE, 3);
474
475   vbox = gtk_vbox_new (FALSE, 3);
476   gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
477
478   scale = gtk_vscale_new_with_range (0, 150, 100);
479   gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
480   label = gtk_label_new (_("Volume"));
481
482   priv->audio_input_adj = adj = gtk_range_get_adjustment (GTK_RANGE (scale));
483   priv->volume =  empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
484     (priv->audio_input));
485   gtk_adjustment_set_value (adj, priv->volume * 100);
486
487   g_signal_connect (G_OBJECT (adj), "value-changed",
488     G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
489
490   gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 3);
491   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
492
493   progress = gtk_progress_bar_new ();
494   gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (progress),
495     GTK_PROGRESS_BOTTOM_TO_TOP);
496   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), 0);
497
498   g_signal_connect (priv->audio_input, "peak-level-changed",
499     G_CALLBACK (empathy_call_window_audio_input_level_changed_cb), progress);
500
501   gtk_box_pack_start (GTK_BOX (hbox), progress, FALSE, FALSE, 3);
502
503   return hbox;
504 }
505
506 static void
507 empathy_call_window_init (EmpathyCallWindow *self)
508 {
509   EmpathyCallWindowPriv *priv = GET_PRIV (self);
510   GtkBuilder *gui;
511   GtkWidget *top_vbox;
512   GtkWidget *h;
513   GtkWidget *arrow;
514   GtkWidget *page;
515   GstBus *bus;
516   gchar *filename;
517
518   filename = empathy_file_lookup ("empathy-call-window.ui", "src");
519   gui = empathy_builder_get_file (filename,
520     "call_window_vbox", &top_vbox,
521     "pane", &priv->pane,
522     "statusbar", &priv->statusbar,
523     "microphone", &priv->mic_button,
524     "camera", &priv->camera_button,
525     "toolbar", &priv->toolbar,
526     "send_video", &priv->send_video,
527     "ui_manager", &priv->ui_manager,
528     "menufullscreen", &priv->menu_fullscreen,
529     NULL);
530
531   empathy_builder_connect (gui, self,
532     "menuhangup", "activate", empathy_call_window_hangup_cb,
533     "hangup", "clicked", empathy_call_window_hangup_cb,
534     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
535     "camera", "toggled", empathy_call_window_camera_toggled_cb,
536     "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
537     "show_preview", "toggled", empathy_call_window_show_preview_toggled_cb,
538     "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
539     NULL);
540
541   priv->lock = g_mutex_new ();
542
543   gtk_container_add (GTK_CONTAINER (self), top_vbox);
544
545   empathy_call_window_setup_toolbar (self);
546
547   priv->pipeline = gst_pipeline_new (NULL);
548
549   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
550   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
551                                   CONTENT_HBOX_BORDER_WIDTH);
552   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
553
554   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
555
556   gst_bus_add_watch (bus, empathy_call_window_bus_message, self);
557
558   priv->video_output = empathy_video_widget_new (bus);
559   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->video_output,
560                       TRUE, TRUE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
561   gtk_widget_add_events (priv->video_output,
562       GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
563   g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
564       G_CALLBACK (empathy_call_window_video_button_press_cb), self);
565
566   priv->video_tee = gst_element_factory_make ("tee", NULL);
567   gst_object_ref (priv->video_tee);
568   gst_object_sink (priv->video_tee);
569
570   priv->vbox = gtk_vbox_new (FALSE, 3);
571   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
572                       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
573
574   priv->video_preview = empathy_video_widget_new_with_size (bus, 160, 120);
575   g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
576   gtk_box_pack_start (GTK_BOX (priv->vbox), priv->video_preview, FALSE, FALSE, 0);
577
578   priv->video_input = empathy_video_src_new ();
579   gst_object_ref (priv->video_input);
580   gst_object_sink (priv->video_input);
581
582   priv->audio_input = empathy_audio_src_new ();
583   gst_object_ref (priv->audio_input);
584   gst_object_sink (priv->audio_input);
585
586   priv->audio_output = empathy_audio_sink_new ();
587   gst_object_ref (priv->audio_output);
588   gst_object_sink (priv->audio_output);
589
590   g_object_unref (bus);
591
592   priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
593   arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
594   g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
595     G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
596
597   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
598
599   h = gtk_hbox_new (FALSE, 3);
600   gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
601   gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
602
603   priv->sidebar = empathy_sidebar_new ();
604   g_signal_connect (G_OBJECT (priv->sidebar),
605     "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
606   g_signal_connect (G_OBJECT (priv->sidebar),
607     "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
608   gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
609
610   priv->dtmf_panel = empathy_call_window_create_dtmf (self);
611   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
612     priv->dtmf_panel);
613
614   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
615
616   page = empathy_call_window_create_audio_input (self);
617   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
618     page);
619
620   page = empathy_call_window_create_video_input (self);
621   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
622     page);
623
624   gtk_widget_show_all (top_vbox);
625
626   gtk_widget_hide (priv->sidebar);
627
628   priv->fullscreen = empathy_call_window_fullscreen_new (self);
629   empathy_call_window_fullscreen_set_video_widget (priv->fullscreen, priv->video_output);
630   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
631       "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
632
633   g_signal_connect (G_OBJECT (self), "realize",
634     G_CALLBACK (empathy_call_window_realized_cb), self);
635
636   g_signal_connect (G_OBJECT (self), "delete-event",
637     G_CALLBACK (empathy_call_window_delete_cb), self);
638
639   g_signal_connect (G_OBJECT (self), "window-state-event",
640     G_CALLBACK (empathy_call_window_state_event_cb), self);
641
642   g_signal_connect (G_OBJECT (self), "key-press-event",
643       G_CALLBACK (empathy_call_window_key_press_cb), self);
644
645   empathy_call_window_status_message (self, _("Connecting..."));
646
647   priv->timer = g_timer_new ();
648
649   g_object_ref (priv->ui_manager);
650   g_object_unref (gui);
651   g_free (filename);
652 }
653
654 static void
655 set_window_title (EmpathyCallWindow *self)
656 {
657   EmpathyCallWindowPriv *priv = GET_PRIV (self);
658   gchar *tmp;
659
660   tmp = g_strdup_printf (_("Call with %s"),
661       empathy_contact_get_name (priv->contact));
662   gtk_window_set_title (GTK_WINDOW (self), tmp);
663   g_free (tmp);
664 }
665
666 static void
667 contact_name_changed_cb (EmpathyContact *contact,
668                          GParamSpec *pspec,
669                          EmpathyCallWindow *self)
670 {
671   set_window_title (self);
672 }
673
674 static void
675 empathy_call_window_constructed (GObject *object)
676 {
677   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
678   EmpathyCallWindowPriv *priv = GET_PRIV (self);
679
680   g_assert (priv->handler != NULL);
681
682   g_object_get (priv->handler, "contact", &(priv->contact), NULL);
683
684   if (priv->contact != NULL)
685     {
686       set_window_title (self);
687
688       g_signal_connect (priv->contact, "notify::name",
689           G_CALLBACK (contact_name_changed_cb), self);
690     }
691   else
692     {
693       g_warning ("call handler doesn't have a contact");
694       gtk_window_set_title (GTK_WINDOW (self), _("Call"));
695     }
696 }
697
698 static void empathy_call_window_dispose (GObject *object);
699 static void empathy_call_window_finalize (GObject *object);
700
701 static void
702 empathy_call_window_set_property (GObject *object,
703   guint property_id, const GValue *value, GParamSpec *pspec)
704 {
705   EmpathyCallWindowPriv *priv = GET_PRIV (object);
706
707   switch (property_id)
708     {
709       case PROP_CALL_HANDLER:
710         priv->handler = g_value_dup_object (value);
711         break;
712       default:
713         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
714     }
715 }
716
717 static void
718 empathy_call_window_get_property (GObject *object,
719   guint property_id, GValue *value, GParamSpec *pspec)
720 {
721   EmpathyCallWindowPriv *priv = GET_PRIV (object);
722
723   switch (property_id)
724     {
725       case PROP_CALL_HANDLER:
726         g_value_set_object (value, priv->handler);
727         break;
728       default:
729         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
730     }
731 }
732
733 static void
734 empathy_call_window_class_init (
735   EmpathyCallWindowClass *empathy_call_window_class)
736 {
737   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
738   GParamSpec *param_spec;
739
740   g_type_class_add_private (empathy_call_window_class,
741     sizeof (EmpathyCallWindowPriv));
742
743   object_class->constructed = empathy_call_window_constructed;
744   object_class->set_property = empathy_call_window_set_property;
745   object_class->get_property = empathy_call_window_get_property;
746
747   object_class->dispose = empathy_call_window_dispose;
748   object_class->finalize = empathy_call_window_finalize;
749
750   param_spec = g_param_spec_object ("handler",
751     "handler", "The call handler",
752     EMPATHY_TYPE_CALL_HANDLER,
753     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
754   g_object_class_install_property (object_class,
755     PROP_CALL_HANDLER, param_spec);
756
757 }
758
759 void
760 empathy_call_window_dispose (GObject *object)
761 {
762   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
763   EmpathyCallWindowPriv *priv = GET_PRIV (self);
764
765   if (priv->dispose_has_run)
766     return;
767
768   priv->dispose_has_run = TRUE;
769
770   if (priv->handler != NULL)
771     g_object_unref (priv->handler);
772
773   priv->handler = NULL;
774
775   if (priv->pipeline != NULL)
776     g_object_unref (priv->pipeline);
777   priv->pipeline = NULL;
778
779   if (priv->video_input != NULL)
780     g_object_unref (priv->video_input);
781   priv->video_input = NULL;
782
783   if (priv->audio_input != NULL)
784     g_object_unref (priv->audio_input);
785   priv->audio_input = NULL;
786
787   if (priv->audio_output != NULL)
788     g_object_unref (priv->audio_output);
789   priv->audio_output = NULL;
790
791   if (priv->video_tee != NULL)
792     g_object_unref (priv->video_tee);
793   priv->video_tee = NULL;
794
795   if (priv->timer_id != 0)
796     g_source_remove (priv->timer_id);
797   priv->timer_id = 0;
798
799   if (priv->ui_manager != NULL)
800     g_object_unref (priv->ui_manager);
801   priv->ui_manager = NULL;
802
803   if (priv->contact != NULL)
804     {
805       g_signal_handlers_disconnect_by_func (priv->contact,
806           contact_name_changed_cb, self);
807       g_object_unref (priv->contact);
808       priv->contact = NULL;
809     }
810
811   /* release any references held by the object here */
812   if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
813     G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
814 }
815
816 void
817 empathy_call_window_finalize (GObject *object)
818 {
819   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
820   EmpathyCallWindowPriv *priv = GET_PRIV (self);
821
822   if (priv->video_output_motion_handler_id != 0)
823     {
824       g_signal_handler_disconnect (G_OBJECT (priv->video_output),
825           priv->video_output_motion_handler_id);
826       priv->video_output_motion_handler_id = 0;
827     }
828
829   /* free any data held directly by the object here */
830   g_mutex_free (priv->lock);
831
832   g_timer_destroy (priv->timer);
833
834   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
835 }
836
837
838 EmpathyCallWindow *
839 empathy_call_window_new (EmpathyCallHandler *handler)
840 {
841   return EMPATHY_CALL_WINDOW (
842     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
843 }
844
845 static void
846 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
847   GstElement *conference, gpointer user_data)
848 {
849   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
850   EmpathyCallWindowPriv *priv = GET_PRIV (self);
851
852   gst_bin_add (GST_BIN (priv->pipeline), conference);
853
854   gst_element_set_state (conference, GST_STATE_PLAYING);
855 }
856
857 static gboolean
858 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
859   FsMediaType type, FsStreamDirection direction, gpointer user_data)
860 {
861   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
862   EmpathyCallWindowPriv *priv = GET_PRIV (self);
863
864   if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
865     return TRUE;
866
867   if (direction == FS_DIRECTION_RECV)
868     return TRUE;
869
870   /* video and direction is send */
871   return priv->video_input != NULL;
872 }
873
874 static void
875 empathy_call_window_disconnected (EmpathyCallWindow *self)
876 {
877   EmpathyCallWindowPriv *priv = GET_PRIV (self);
878
879   g_mutex_lock (priv->lock);
880
881   g_timer_stop (priv->timer);
882
883   if (priv->timer_id != 0)
884     g_source_remove (priv->timer_id);
885   priv->timer_id = 0;
886
887   g_mutex_unlock (priv->lock);
888
889   empathy_call_window_status_message (self, _("Disconnected"));
890
891   gtk_widget_set_sensitive (priv->camera_button, FALSE);
892   gtk_action_set_sensitive (priv->send_video, FALSE);
893   priv->sending_video = FALSE;
894 }
895
896
897 static void
898 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
899 {
900   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
901
902   empathy_call_window_disconnected (self);
903 }
904
905 /* Called with global lock held */
906 static GstPad *
907 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
908 {
909   EmpathyCallWindowPriv *priv = GET_PRIV (self);
910   GstPad *pad;
911
912   if (priv->funnel == NULL)
913     {
914       GstElement *output;
915
916       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
917         (priv->video_output));
918
919       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
920
921       gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
922       gst_bin_add (GST_BIN (priv->pipeline), output);
923
924       gst_element_link (priv->funnel, output);
925
926       gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
927       gst_element_set_state (output, GST_STATE_PLAYING);
928     }
929
930   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
931
932   return pad;
933 }
934
935 /* Called with global lock held */
936 static GstPad *
937 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
938 {
939   EmpathyCallWindowPriv *priv = GET_PRIV (self);
940   GstPad *pad;
941
942   if (priv->liveadder == NULL)
943     {
944       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
945
946       gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
947       gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
948
949       gst_element_link (priv->liveadder, priv->audio_output);
950
951       gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
952       gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
953     }
954
955   pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
956
957   return pad;
958 }
959
960 static gboolean
961 empathy_call_window_update_timer (gpointer user_data)
962 {
963   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
964   EmpathyCallWindowPriv *priv = GET_PRIV (self);
965   gchar *str;
966   gdouble time;
967
968   time = g_timer_elapsed (priv->timer, NULL);
969
970   /* Translators: number of minutes:seconds the caller has been connected */
971   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time / 60,
972     (int) time % 60);
973   empathy_call_window_status_message (self, str);
974   g_free (str);
975
976   return TRUE;
977 }
978
979 static gboolean
980 empathy_call_window_connected (gpointer user_data)
981 {
982   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
983   EmpathyCallWindowPriv *priv = GET_PRIV (self);
984   EmpathyTpCall *call;
985
986   g_object_get (priv->handler, "tp-call", &call, NULL);
987
988   if (empathy_tp_call_has_dtmf (call))
989     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
990
991   if (priv->video_input != NULL)
992     {
993       gtk_widget_set_sensitive (priv->camera_button, TRUE);
994       gtk_action_set_sensitive (priv->send_video, TRUE);
995     }
996
997   g_object_unref (call);
998
999   g_mutex_lock (priv->lock);
1000
1001   priv->timer_id = g_timeout_add_seconds (1,
1002     empathy_call_window_update_timer, self);
1003
1004   g_mutex_unlock (priv->lock);
1005
1006   empathy_call_window_update_timer (self);
1007
1008   return FALSE;
1009 }
1010
1011
1012 /* Called from the streaming thread */
1013 static void
1014 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1015   GstPad *src, guint media_type, gpointer user_data)
1016 {
1017   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1018   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1019
1020   GstPad *pad;
1021
1022   g_mutex_lock (priv->lock);
1023
1024   if (priv->connected == FALSE)
1025     {
1026       g_timer_start (priv->timer);
1027       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
1028       priv->connected = TRUE;
1029     }
1030
1031   switch (media_type)
1032     {
1033       case TP_MEDIA_STREAM_TYPE_AUDIO:
1034         pad = empathy_call_window_get_audio_sink_pad (self);
1035         break;
1036       case TP_MEDIA_STREAM_TYPE_VIDEO:
1037         pad = empathy_call_window_get_video_sink_pad (self);
1038         break;
1039       default:
1040         g_assert_not_reached ();
1041     }
1042
1043   gst_pad_link (src, pad);
1044   gst_object_unref (pad);
1045
1046   g_mutex_unlock (priv->lock);
1047 }
1048
1049 /* Called from the streaming thread */
1050 static void
1051 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1052   GstPad *sink, guint media_type, gpointer user_data)
1053 {
1054   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1055   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1056   GstPad *pad;
1057
1058   switch (media_type)
1059     {
1060       case TP_MEDIA_STREAM_TYPE_AUDIO:
1061         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1062
1063         pad = gst_element_get_static_pad (priv->audio_input, "src");
1064         gst_pad_link (pad, sink);
1065
1066         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1067         break;
1068       case TP_MEDIA_STREAM_TYPE_VIDEO:
1069         if (priv->video_input != NULL)
1070           {
1071             pad =  gst_element_get_request_pad (priv->video_tee, "src%d");
1072             gst_pad_link (pad, sink);
1073           }
1074         break;
1075       default:
1076         g_assert_not_reached ();
1077     }
1078
1079 }
1080
1081 static gboolean
1082 empathy_gst_bin_has_child (GstBin *bin, GstElement *element)
1083 {
1084   GstIterator *it;
1085   gboolean ret = FALSE;
1086   GstElement *item;
1087
1088   it = gst_bin_iterate_recurse (bin);
1089
1090   for (;;)
1091     {
1092       switch (gst_iterator_next (it, (gpointer *)&item))
1093        {
1094          case GST_ITERATOR_OK:
1095            if (item == element)
1096             {
1097               gst_object_unref (GST_OBJECT (item));
1098               ret = TRUE;
1099               goto out;
1100             }
1101            gst_object_unref (GST_OBJECT (item));
1102            break;
1103          case GST_ITERATOR_RESYNC:
1104            gst_iterator_resync (it);
1105            break;
1106         case GST_ITERATOR_ERROR:
1107            g_assert_not_reached ();
1108            /* fallthrough */
1109         case GST_ITERATOR_DONE:
1110            goto out;
1111            break;
1112       }
1113     }
1114     gst_iterator_free (it);
1115
1116 out:
1117   return ret;
1118 }
1119
1120 static void
1121 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1122 {
1123   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1124   GstElement *preview;
1125
1126   preview = empathy_video_widget_get_element (
1127     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1128
1129   gst_element_set_state (priv->video_input, GST_STATE_NULL);
1130   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1131   gst_element_set_state (preview, GST_STATE_NULL);
1132
1133   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1134     priv->video_tee, preview, NULL);
1135
1136   g_object_unref (priv->video_input);
1137   priv->video_input = NULL;
1138   g_object_unref (priv->video_tee);
1139   priv->video_tee = NULL;
1140 }
1141
1142
1143 static gboolean
1144 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1145   gpointer user_data)
1146 {
1147   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1148   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1149   GstState newstate;
1150
1151   empathy_call_handler_bus_message (priv->handler, bus, message);
1152
1153   switch (GST_MESSAGE_TYPE (message))
1154     {
1155       case GST_MESSAGE_STATE_CHANGED:
1156         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1157           {
1158             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1159             if (newstate == GST_STATE_PAUSED)
1160                 empathy_call_window_setup_video_input (self);
1161           }
1162         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1163             !priv->call_started)
1164           {
1165             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1166             if (newstate == GST_STATE_PAUSED)
1167               {
1168                 priv->call_started = TRUE;
1169                 empathy_call_handler_start_call (priv->handler);
1170                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1171               }
1172           }
1173         break;
1174       case GST_MESSAGE_ERROR:
1175         {
1176           GError *error;
1177           gchar *debug;
1178
1179           gst_message_parse_error (message, &error, &debug);
1180
1181           g_message ("Element error: %s -- %s\n", error->message, debug);
1182
1183           if (priv->video_input != NULL &&
1184               empathy_gst_bin_has_child (GST_BIN (priv->video_input),
1185                 GST_ELEMENT (GST_MESSAGE_SRC (message))))
1186             {
1187               /* Remove the video input and continue */
1188               empathy_call_window_remove_video_input (self);
1189               gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1190             }
1191           else
1192             {
1193               gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1194               empathy_call_window_disconnected (self);
1195             }
1196           g_error_free (error);
1197           g_free (debug);
1198         }
1199       default:
1200         break;
1201     }
1202
1203   return TRUE;
1204 }
1205
1206 static void
1207 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1208 {
1209   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1210   GstElement *preview;
1211
1212   g_signal_connect (priv->handler, "conference-added",
1213     G_CALLBACK (empathy_call_window_conference_added_cb), window);
1214   g_signal_connect (priv->handler, "request-resource",
1215     G_CALLBACK (empathy_call_window_request_resource_cb), window);
1216   g_signal_connect (priv->handler, "closed",
1217     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1218   g_signal_connect (priv->handler, "src-pad-added",
1219     G_CALLBACK (empathy_call_window_src_added_cb), window);
1220   g_signal_connect (priv->handler, "sink-pad-added",
1221     G_CALLBACK (empathy_call_window_sink_added_cb), window);
1222
1223
1224   preview = empathy_video_widget_get_element (
1225     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1226
1227   gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
1228     priv->video_tee, preview, NULL);
1229   gst_element_link_many (priv->video_input, priv->video_tee,
1230     preview, NULL);
1231
1232   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1233 }
1234
1235 static gboolean
1236 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1237   EmpathyCallWindow *window)
1238 {
1239   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1240
1241   gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1242
1243   return FALSE;
1244 }
1245
1246 static void
1247 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1248 {
1249   GtkWidget *menu;
1250   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1251
1252   menu = gtk_ui_manager_get_widget (priv->ui_manager,
1253             "/menubar1");
1254
1255   if (set_fullscreen)
1256     {
1257       gtk_widget_hide (priv->sidebar);
1258       gtk_widget_hide (menu);
1259       gtk_widget_hide (priv->vbox);
1260       gtk_widget_hide (priv->statusbar);
1261       gtk_widget_hide (priv->toolbar);
1262     }
1263   else
1264     {
1265       if (priv->sidebar_was_visible_before_fs)
1266         gtk_widget_show (priv->sidebar);
1267
1268       gtk_widget_show (menu);
1269       gtk_widget_show (priv->vbox);
1270       gtk_widget_show (priv->statusbar);
1271       gtk_widget_show (priv->toolbar);
1272
1273       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1274           priv->original_height_before_fs);
1275     }
1276 }
1277
1278 static void
1279 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1280 {
1281   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1282
1283   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1284       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1285   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1286       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1287   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1288       priv->video_output, TRUE, TRUE,
1289       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1290       GTK_PACK_START);
1291   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1292       priv->vbox, TRUE, TRUE,
1293       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1294       GTK_PACK_START);
1295 }
1296
1297 static gboolean
1298 empathy_call_window_state_event_cb (GtkWidget *widget,
1299   GdkEventWindowState *event, EmpathyCallWindow *window)
1300 {
1301   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1302     {
1303       EmpathyCallWindowPriv *priv = GET_PRIV (window);
1304       gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1305
1306       if (set_fullscreen)
1307         {
1308           gboolean sidebar_was_visible;
1309           gint original_width = GTK_WIDGET (window)->allocation.width;
1310           gint original_height = GTK_WIDGET (window)->allocation.height;
1311
1312           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1313
1314           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1315           priv->original_width_before_fs = original_width;
1316           priv->original_height_before_fs = original_height;
1317
1318           if (priv->video_output_motion_handler_id == 0 &&
1319                 priv->video_output != NULL)
1320             {
1321               priv->video_output_motion_handler_id = g_signal_connect (
1322                   G_OBJECT (priv->video_output), "motion-notify-event",
1323                   G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1324             }
1325         }
1326       else
1327         {
1328           if (priv->video_output_motion_handler_id != 0)
1329             {
1330               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1331                   priv->video_output_motion_handler_id);
1332               priv->video_output_motion_handler_id = 0;
1333             }
1334         }
1335
1336       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1337           set_fullscreen);
1338       show_controls (window, set_fullscreen);
1339       show_borders (window, set_fullscreen);
1340       gtk_action_set_stock_id(priv->menu_fullscreen,
1341           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1342       priv->is_fullscreen = set_fullscreen;
1343   }
1344
1345   return FALSE;
1346 }
1347
1348 static void
1349 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1350   EmpathyCallWindow *window)
1351 {
1352   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1353   GtkWidget *arrow;
1354   int w,h, handle_size;
1355
1356   w = GTK_WIDGET (window)->allocation.width;
1357   h = GTK_WIDGET (window)->allocation.height;
1358
1359   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1360
1361   if (gtk_toggle_button_get_active (toggle))
1362     {
1363       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1364       gtk_widget_show (priv->sidebar);
1365       w += priv->sidebar->allocation.width + handle_size;
1366     }
1367   else
1368     {
1369       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1370       w -= priv->sidebar->allocation.width + handle_size;
1371       gtk_widget_hide (priv->sidebar);
1372     }
1373
1374   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1375
1376   if (w > 0 && h > 0)
1377     gtk_window_resize (GTK_WINDOW (window), w, h);
1378 }
1379
1380 static void
1381 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1382   gboolean send)
1383 {
1384   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1385   EmpathyTpCall *call;
1386
1387   g_object_get (priv->handler, "tp-call", &call, NULL);
1388   empathy_tp_call_request_video_stream_direction (call, send);
1389   g_object_unref (call);
1390 }
1391
1392 static void
1393 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1394   EmpathyCallWindow *window)
1395 {
1396   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1397   gboolean active;
1398
1399   active = (gtk_toggle_tool_button_get_active (toggle));
1400
1401   if (priv->sending_video == active)
1402     return;
1403   priv->sending_video = active;
1404
1405   empathy_call_window_set_send_video (window, active);
1406   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1407 }
1408
1409 static void
1410 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1411   EmpathyCallWindow *window)
1412 {
1413   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1414   gboolean active;
1415
1416   active = (gtk_toggle_action_get_active (toggle));
1417
1418   if (priv->sending_video == active)
1419     return;
1420   priv->sending_video = active;
1421
1422   empathy_call_window_set_send_video (window, active);
1423   gtk_toggle_tool_button_set_active (
1424       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1425 }
1426
1427 static void
1428 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1429   EmpathyCallWindow *window)
1430 {
1431   /* FIXME: Not implemented */
1432 }
1433
1434 static void
1435 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1436   EmpathyCallWindow *window)
1437 {
1438   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1439   gboolean active;
1440
1441   active = (gtk_toggle_tool_button_get_active (toggle));
1442
1443   if (active)
1444     {
1445       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1446         priv->volume);
1447       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1448     }
1449   else
1450     {
1451       /* TODO, Instead of setting the input volume to 0 we should probably
1452        * stop sending but this would cause the audio call to drop if both
1453        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1454        * in the future. GNOME #574574
1455        */
1456       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1457         0);
1458       gtk_adjustment_set_value (priv->audio_input_adj, 0);
1459     }
1460 }
1461
1462 static void
1463 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1464   EmpathyCallWindow *window)
1465 {
1466   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1467
1468   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1469     FALSE);
1470 }
1471
1472 static void
1473 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1474   EmpathyCallWindow *window)
1475 {
1476   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1477
1478   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1479     TRUE);
1480 }
1481
1482 static void
1483 empathy_call_window_hangup_cb (gpointer object,
1484                                EmpathyCallWindow *window)
1485 {
1486   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1487
1488   gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1489   gtk_widget_destroy (GTK_WIDGET (window));
1490 }
1491
1492 static void
1493 empathy_call_window_fullscreen_cb (gpointer object,
1494                                    EmpathyCallWindow *window)
1495 {
1496   empathy_call_window_fullscreen_toggle (window);
1497 }
1498
1499 static void
1500 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
1501 {
1502   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1503
1504   if (priv->is_fullscreen)
1505     gtk_window_unfullscreen (GTK_WINDOW (window));
1506   else
1507     gtk_window_fullscreen (GTK_WINDOW (window));
1508 }
1509
1510 static gboolean
1511 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
1512   GdkEventButton *event, EmpathyCallWindow *window)
1513 {
1514   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1515     {
1516       empathy_call_window_video_menu_popup (window, event->button);
1517       return TRUE;
1518     }
1519
1520   return FALSE;
1521 }
1522
1523 static gboolean
1524 empathy_call_window_key_press_cb (GtkWidget *video_output,
1525   GdkEventKey *event, EmpathyCallWindow *window)
1526 {
1527   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1528
1529   if (priv->is_fullscreen && event->keyval == GDK_Escape)
1530     {
1531       /* Since we are in fullscreen mode, toggling will bring us back to
1532          normal mode. */
1533       empathy_call_window_fullscreen_toggle (window);
1534       return TRUE;
1535     }
1536
1537   return FALSE;
1538 }
1539
1540 static gboolean
1541 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
1542     GdkEventMotion *event, EmpathyCallWindow *window)
1543 {
1544   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1545
1546   if (priv->is_fullscreen)
1547     {
1548       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
1549       return TRUE;
1550     }
1551
1552   return FALSE;
1553 }
1554
1555 static void
1556 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
1557   guint button)
1558 {
1559   GtkWidget *menu;
1560   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1561
1562   menu = gtk_ui_manager_get_widget (priv->ui_manager,
1563             "/video-popup");
1564   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1565       button, gtk_get_current_event_time ());
1566   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
1567 }
1568
1569 static void
1570 empathy_call_window_status_message (EmpathyCallWindow *window,
1571   gchar *message)
1572 {
1573   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1574
1575   if (priv->context_id == 0)
1576     {
1577       priv->context_id = gtk_statusbar_get_context_id (
1578         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
1579     }
1580   else
1581     {
1582       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
1583     }
1584
1585   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
1586     message);
1587 }
1588
1589 static void
1590 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
1591   gdouble value, EmpathyCallWindow *window)
1592 {
1593   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1594
1595   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
1596     value);
1597 }