]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
Handle resource-request
[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 <gst/gst.h>
28 #include <gtk/gtk.h>
29 #include <glib/gi18n.h>
30
31 #include <telepathy-farsight/channel.h>
32
33 #include <libempathy/empathy-utils.h>
34 #include <libempathy-gtk/empathy-video-widget.h>
35 #include <libempathy-gtk/empathy-audio-src.h>
36 #include <libempathy-gtk/empathy-audio-sink.h>
37 #include <libempathy-gtk/empathy-video-src.h>
38 #include <libempathy-gtk/empathy-ui-utils.h>
39
40 #include "empathy-call-window.h"
41
42 #include "empathy-sidebar.h"
43
44 #define BUTTON_ID "empathy-call-dtmf-button-id"
45
46 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
47
48 /* signal enum */
49 #if 0
50 enum
51 {
52     LAST_SIGNAL
53 };
54
55 static guint signals[LAST_SIGNAL] = {0};
56 #endif
57
58 enum {
59   PROP_CALL_HANDLER = 1,
60 };
61
62 /* private structure */
63 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
64
65 struct _EmpathyCallWindowPriv
66 {
67   gboolean dispose_has_run;
68   EmpathyCallHandler *handler;
69
70   gboolean connected;
71
72   GtkWidget *video_output;
73   GtkWidget *video_preview;
74   GtkWidget *sidebar;
75   GtkWidget *sidebar_button;
76   GtkWidget *statusbar;
77   GtkWidget *volume_button;
78   GtkWidget *camera_button;
79
80   GtkWidget *dtmf_panel;
81
82   GstElement *video_input;
83   GstElement *audio_input;
84   GstElement *audio_output;
85   GstElement *pipeline;
86   GstElement *video_tee;
87
88   GstElement *funnel;
89   GstElement *liveadder;
90
91   GladeXML *glade;
92   guint context_id;
93
94   GTimer *timer;
95   guint timer_id;
96
97   GtkWidget *video_contrast;
98   GtkWidget *video_brightness;
99   GtkWidget *video_gamma;
100
101   GMutex *lock;
102   gboolean call_started;
103 };
104
105 #define GET_PRIV(o) \
106   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW, \
107     EmpathyCallWindowPriv))
108
109 static void empathy_call_window_realized_cb (GtkWidget *widget,
110   EmpathyCallWindow *window);
111
112 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
113   GdkEvent*event, EmpathyCallWindow *window);
114
115 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
116   EmpathyCallWindow *window);
117
118 static void empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
119   EmpathyCallWindow *window);
120
121 static void empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
122   EmpathyCallWindow *window);
123
124 static void empathy_call_window_hangup (EmpathyCallWindow *window);
125
126 static void empathy_call_window_status_message (EmpathyCallWindow *window,
127   gchar *message);
128
129 static gboolean empathy_call_window_bus_message (GstBus *bus,
130   GstMessage *message, gpointer user_data);
131
132 static void
133 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
134   gdouble value, EmpathyCallWindow *window);
135
136 static void
137 empathy_call_window_setup_menubar (EmpathyCallWindow *self)
138 {
139   EmpathyCallWindowPriv *priv = GET_PRIV (self);
140   GtkWidget *hangup;
141
142   hangup = glade_xml_get_widget (priv->glade, "menuhangup");
143   g_signal_connect_swapped (G_OBJECT (hangup), "activate",
144     G_CALLBACK (empathy_call_window_hangup), self);
145 }
146
147 static void
148 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
149 {
150   EmpathyCallWindowPriv *priv = GET_PRIV (self);
151   GtkWidget *hangup;
152   GtkWidget *mic;
153   GtkWidget *camera;
154   GtkWidget *toolbar;
155   GtkToolItem *tool_item;
156
157   hangup = glade_xml_get_widget (priv->glade, "hangup");
158
159   g_signal_connect_swapped (G_OBJECT (hangup), "clicked",
160     G_CALLBACK (empathy_call_window_hangup), self);
161
162   mic = glade_xml_get_widget (priv->glade, "microphone");
163   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (mic), TRUE);
164
165   toolbar = glade_xml_get_widget (priv->glade, "toolbar");
166
167   /* Add an empty expanded GtkToolItem so the volume button is at the end of
168    * the toolbar. */
169   tool_item = gtk_tool_item_new ();
170   gtk_tool_item_set_expand (tool_item, TRUE);
171   gtk_widget_show (GTK_WIDGET (tool_item));
172   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1);
173
174   priv->volume_button = gtk_volume_button_new ();
175   /* FIXME listen to the audiosinks signals and update the button according to
176    * that, for now starting out at 1.0 and assuming only the app changes the
177    * volume will do */
178   gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
179   g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
180     G_CALLBACK (empathy_call_window_volume_changed_cb), self);
181
182   tool_item = gtk_tool_item_new ();
183   gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
184   gtk_widget_show_all (GTK_WIDGET (tool_item));
185   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1);
186
187
188   camera = glade_xml_get_widget (priv->glade, "camera");
189   priv->camera_button = camera;
190   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (camera), FALSE);
191
192   g_signal_connect (G_OBJECT (camera), "toggled",
193     G_CALLBACK (empathy_call_window_camera_toggled_cb), self);
194 }
195
196 static void
197 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
198 {
199   EmpathyCallWindowPriv *priv = GET_PRIV (window);
200   EmpathyTpCall *call;
201   GQuark button_quark;
202   TpDTMFEvent event;
203
204   g_object_get (priv->handler, "tp-call", &call, NULL);
205
206   button_quark = g_quark_from_static_string (BUTTON_ID);
207   event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
208     button_quark));
209
210   empathy_tp_call_start_tone (call, event);
211
212   g_object_unref (call);
213 }
214
215 static void
216 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
217 {
218   EmpathyCallWindowPriv *priv = GET_PRIV (window);
219   EmpathyTpCall *call;
220
221   g_object_get (priv->handler, "tp-call", &call, NULL);
222
223   empathy_tp_call_stop_tone (call);
224
225   g_object_unref (call);
226 }
227
228 static GtkWidget *
229 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
230 {
231   GtkWidget *table;
232   int i;
233   GQuark button_quark;
234   struct {
235     gchar *label;
236     TpDTMFEvent event;
237   } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
238                       { "2", TP_DTMF_EVENT_DIGIT_2 },
239                       { "3", TP_DTMF_EVENT_DIGIT_3 },
240                       { "4", TP_DTMF_EVENT_DIGIT_4 },
241                       { "5", TP_DTMF_EVENT_DIGIT_5 },
242                       { "6", TP_DTMF_EVENT_DIGIT_6 },
243                       { "7", TP_DTMF_EVENT_DIGIT_7 },
244                       { "8", TP_DTMF_EVENT_DIGIT_8 },
245                       { "9", TP_DTMF_EVENT_DIGIT_9 },
246                       { "#", TP_DTMF_EVENT_HASH },
247                       { "0", TP_DTMF_EVENT_DIGIT_0 },
248                       { "*", TP_DTMF_EVENT_ASTERISK },
249                       { NULL, } };
250
251   button_quark = g_quark_from_static_string (BUTTON_ID);
252
253   table = gtk_table_new (4, 3, TRUE);
254
255   for (i = 0; dtmfbuttons[i].label != NULL; i++)
256     {
257       GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
258       gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
259         i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
260
261       g_object_set_qdata (G_OBJECT (button), button_quark,
262         GUINT_TO_POINTER (dtmfbuttons[i].event));
263
264       g_signal_connect (G_OBJECT (button), "pressed",
265         G_CALLBACK (dtmf_button_pressed_cb), self);
266       g_signal_connect (G_OBJECT (button), "released",
267         G_CALLBACK (dtmf_button_released_cb), self);
268     }
269
270   return table;
271 }
272
273 static GtkWidget *
274 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
275   gchar *label_text, GtkWidget *bin)
276 {
277    GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
278    GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
279    GtkWidget *label = gtk_label_new (label_text);
280
281    gtk_widget_set_sensitive (scale, FALSE);
282
283    gtk_container_add (GTK_CONTAINER (bin), vbox);
284
285    gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
286    gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
287    gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
288
289    return scale;
290 }
291
292 static void
293 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
294   EmpathyCallWindow *self)
295
296 {
297   EmpathyCallWindowPriv *priv = GET_PRIV (self);
298
299   empathy_video_src_set_channel (priv->video_input,
300     EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
301 }
302
303 static void
304 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
305   EmpathyCallWindow *self)
306
307 {
308   EmpathyCallWindowPriv *priv = GET_PRIV (self);
309
310   empathy_video_src_set_channel (priv->video_input,
311     EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
312 }
313
314 static void
315 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
316   EmpathyCallWindow *self)
317
318 {
319   EmpathyCallWindowPriv *priv = GET_PRIV (self);
320
321   empathy_video_src_set_channel (priv->video_input,
322     EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
323 }
324
325
326 static GtkWidget *
327 empathy_call_window_create_video_input (EmpathyCallWindow *self)
328 {
329   EmpathyCallWindowPriv *priv = GET_PRIV (self);
330   GtkWidget *hbox;
331
332   hbox = gtk_hbox_new (TRUE, 3);
333
334   priv->video_contrast = empathy_call_window_create_video_input_add_slider (
335     self,  _("Contrast"), hbox);
336
337   priv->video_brightness = empathy_call_window_create_video_input_add_slider (
338     self,  _("Brightness"), hbox);
339
340   priv->video_gamma = empathy_call_window_create_video_input_add_slider (
341     self,  _("Gamma"), hbox);
342
343   return hbox;
344 }
345
346 static void
347 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
348 {
349   EmpathyCallWindowPriv *priv = GET_PRIV (self);
350   guint supported;
351   GtkAdjustment *adj;
352
353   supported = empathy_video_src_get_supported_channels (priv->video_input);
354
355   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
356     {
357       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
358
359       gtk_adjustment_set_value (adj,
360         empathy_video_src_get_channel (priv->video_input,
361           EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
362
363       g_signal_connect (G_OBJECT (adj), "value-changed",
364         G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
365
366       gtk_widget_set_sensitive (priv->video_contrast, TRUE);
367     }
368
369   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
370     {
371       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
372
373       gtk_adjustment_set_value (adj,
374         empathy_video_src_get_channel (priv->video_input,
375           EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
376
377       g_signal_connect (G_OBJECT (adj), "value-changed",
378         G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
379       gtk_widget_set_sensitive (priv->video_brightness, TRUE);
380     }
381
382   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
383     {
384       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
385
386       gtk_adjustment_set_value (adj,
387         empathy_video_src_get_channel (priv->video_input,
388           EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
389
390       g_signal_connect (G_OBJECT (adj), "value-changed",
391         G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
392       gtk_widget_set_sensitive (priv->video_gamma, TRUE);
393     }
394 }
395
396 static void
397 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
398   EmpathyCallWindow *self)
399 {
400   EmpathyCallWindowPriv *priv = GET_PRIV (self);
401
402   empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
403     gtk_adjustment_get_value (adj)/100);
404 }
405
406 static void
407 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
408   gdouble level, GtkProgressBar *bar)
409 {
410   gdouble value;
411
412   value = CLAMP (pow (10, level / 20), 0.0, 1.0);
413   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), value);
414 }
415
416 static GtkWidget *
417 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
418 {
419   EmpathyCallWindowPriv *priv = GET_PRIV (self);
420   GtkWidget *hbox, *vbox, *scale, *progress, *label;
421   GtkAdjustment *adj;
422
423   hbox = gtk_hbox_new (TRUE, 3);
424
425   vbox = gtk_vbox_new (FALSE, 3);
426   gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
427
428   scale = gtk_vscale_new_with_range (0, 150, 100);
429   gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
430   label = gtk_label_new (_("Volume"));
431
432   adj = gtk_range_get_adjustment (GTK_RANGE (scale));
433   gtk_adjustment_set_value (adj,
434     empathy_audio_src_get_volume (
435       EMPATHY_GST_AUDIO_SRC (priv->audio_input)) * 100);
436
437   g_signal_connect (G_OBJECT (adj), "value-changed",
438     G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
439
440   gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 3);
441   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
442
443   progress = gtk_progress_bar_new ();
444   gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (progress),
445     GTK_PROGRESS_BOTTOM_TO_TOP);
446   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), 0);
447
448   g_signal_connect (priv->audio_input, "peak-level-changed",
449     G_CALLBACK (empathy_call_window_audio_input_level_changed_cb), progress);
450
451   gtk_box_pack_start (GTK_BOX (hbox), progress, FALSE, FALSE, 3);
452
453   return hbox;
454 }
455
456 static void
457 empathy_call_window_init (EmpathyCallWindow *self)
458 {
459   EmpathyCallWindowPriv *priv = GET_PRIV (self);
460   GtkWidget *vbox, *top_vbox;
461   GtkWidget *hbox, *h;
462   GtkWidget *arrow;
463   GtkWidget *page;
464   GstBus *bus;
465   gchar *filename;
466   GtkWidget *pane;
467
468   filename = empathy_file_lookup ("empathy-call-window.glade", "src");
469
470   priv->glade = empathy_glade_get_file (filename, "call_window", NULL,
471     "call_window_vbox", &top_vbox,
472     "pane", &pane,
473     "statusbar", &priv->statusbar,
474     NULL);
475
476   priv->lock = g_mutex_new ();
477
478   gtk_widget_reparent (top_vbox, GTK_WIDGET (self));
479
480   empathy_call_window_setup_menubar (self);
481   empathy_call_window_setup_toolbar (self);
482
483   priv->pipeline = gst_pipeline_new (NULL);
484
485   hbox = gtk_hbox_new (FALSE, 3);
486   gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
487   gtk_paned_pack1 (GTK_PANED(pane), hbox, TRUE, FALSE);
488
489   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
490
491   gst_bus_add_watch (bus, empathy_call_window_bus_message, self);
492
493   priv->video_output = empathy_video_widget_new (bus);
494   gtk_box_pack_start (GTK_BOX (hbox), priv->video_output, TRUE, TRUE, 3);
495
496   priv->video_tee = gst_element_factory_make ("tee", NULL);
497   gst_object_ref (priv->video_tee);
498   gst_object_sink (priv->video_tee);
499
500   vbox = gtk_vbox_new (FALSE, 3);
501   gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
502
503   priv->video_preview = empathy_video_widget_new_with_size (bus, 160, 120);
504   g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
505   gtk_box_pack_start (GTK_BOX (vbox), priv->video_preview, FALSE, FALSE, 0);
506
507   priv->video_input = empathy_video_src_new ();
508   gst_object_ref (priv->video_input);
509   gst_object_sink (priv->video_input);
510
511   priv->audio_input = empathy_audio_src_new ();
512   gst_object_ref (priv->audio_input);
513   gst_object_sink (priv->audio_input);
514
515   priv->audio_output = empathy_audio_sink_new ();
516   gst_object_ref (priv->audio_output);
517   gst_object_sink (priv->audio_output);
518
519   g_object_unref (bus);
520
521   priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
522   arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
523   g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
524     G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
525
526   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
527
528   h = gtk_hbox_new (FALSE, 3);
529   gtk_box_pack_end (GTK_BOX (vbox), h, FALSE, FALSE, 3);
530   gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
531
532   priv->sidebar = empathy_sidebar_new ();
533   g_signal_connect (G_OBJECT (priv->sidebar),
534     "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb),
535     self);
536   gtk_paned_pack2 (GTK_PANED(pane), priv->sidebar, FALSE, FALSE);
537
538   priv->dtmf_panel = empathy_call_window_create_dtmf (self);
539   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
540     priv->dtmf_panel);
541
542   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
543
544   page = empathy_call_window_create_audio_input (self);
545   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
546     page);
547
548   page = empathy_call_window_create_video_input (self);
549   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
550     page);
551
552   gtk_widget_show_all (top_vbox);
553
554   gtk_widget_hide (priv->sidebar);
555
556   g_signal_connect (G_OBJECT (self), "realize",
557     G_CALLBACK (empathy_call_window_realized_cb), self);
558
559   g_signal_connect (G_OBJECT (self), "delete-event",
560     G_CALLBACK (empathy_call_window_delete_cb), self);
561
562   empathy_call_window_status_message (self, _("Connecting..."));
563
564   priv->timer = g_timer_new ();
565 }
566
567 static void empathy_call_window_dispose (GObject *object);
568 static void empathy_call_window_finalize (GObject *object);
569
570 static void
571 empathy_call_window_set_property (GObject *object,
572   guint property_id, const GValue *value, GParamSpec *pspec)
573 {
574   EmpathyCallWindowPriv *priv = GET_PRIV (object);
575
576   switch (property_id)
577     {
578       case PROP_CALL_HANDLER:
579         priv->handler = g_value_dup_object (value);
580         break;
581       default:
582         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
583     }
584 }
585
586 static void
587 empathy_call_window_get_property (GObject *object,
588   guint property_id, GValue *value, GParamSpec *pspec)
589 {
590   EmpathyCallWindowPriv *priv = GET_PRIV (object);
591
592   switch (property_id)
593     {
594       case PROP_CALL_HANDLER:
595         g_value_set_object (value, priv->handler);
596         break;
597       default:
598         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
599     }
600 }
601
602 static void
603 empathy_call_window_class_init (
604   EmpathyCallWindowClass *empathy_call_window_class)
605 {
606   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
607   GParamSpec *param_spec;
608
609   g_type_class_add_private (empathy_call_window_class,
610     sizeof (EmpathyCallWindowPriv));
611
612   object_class->set_property = empathy_call_window_set_property;
613   object_class->get_property = empathy_call_window_get_property;
614
615   object_class->dispose = empathy_call_window_dispose;
616   object_class->finalize = empathy_call_window_finalize;
617
618   param_spec = g_param_spec_object ("handler",
619     "handler", "The call handler",
620     EMPATHY_TYPE_CALL_HANDLER,
621     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
622   g_object_class_install_property (object_class,
623     PROP_CALL_HANDLER, param_spec);
624
625 }
626
627 void
628 empathy_call_window_dispose (GObject *object)
629 {
630   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
631   EmpathyCallWindowPriv *priv = GET_PRIV (self);
632
633   if (priv->dispose_has_run)
634     return;
635
636   priv->dispose_has_run = TRUE;
637
638   if (priv->handler != NULL)
639     g_object_unref (priv->handler);
640
641   priv->handler = NULL;
642
643   if (priv->pipeline != NULL)
644     g_object_unref (priv->pipeline);
645   priv->pipeline = NULL;
646
647   if (priv->video_input != NULL)
648     g_object_unref (priv->video_input);
649   priv->video_input = NULL;
650
651   if (priv->audio_input != NULL)
652     g_object_unref (priv->audio_input);
653   priv->audio_input = NULL;
654
655   if (priv->audio_output != NULL)
656     g_object_unref (priv->audio_output);
657   priv->audio_output = NULL;
658
659   if (priv->video_tee != NULL)
660     g_object_unref (priv->video_tee);
661   priv->video_tee = NULL;
662
663   if (priv->timer_id != 0)
664     g_source_remove (priv->timer_id);
665   priv->timer_id = 0;
666
667   /* release any references held by the object here */
668   if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
669     G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
670 }
671
672 void
673 empathy_call_window_finalize (GObject *object)
674 {
675   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
676   EmpathyCallWindowPriv *priv = GET_PRIV (self);
677
678   /* free any data held directly by the object here */
679   g_mutex_free (priv->lock);
680
681   g_timer_destroy (priv->timer);
682
683   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
684 }
685
686
687 EmpathyCallWindow *
688 empathy_call_window_new (EmpathyCallHandler *handler)
689 {
690   return EMPATHY_CALL_WINDOW (
691     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
692 }
693
694 static void
695 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
696   GstElement *conference, gpointer user_data)
697 {
698   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
699   EmpathyCallWindowPriv *priv = GET_PRIV (self);
700
701   gst_bin_add (GST_BIN (priv->pipeline), conference);
702
703   gst_element_set_state (conference, GST_STATE_PLAYING);
704 }
705
706 static gboolean
707 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
708   FsMediaType type, FsStreamDirection direction, gpointer user_data)
709 {
710   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
711   EmpathyCallWindowPriv *priv = GET_PRIV (self);
712
713   if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
714     return TRUE;
715
716   if (direction == FS_DIRECTION_RECV)
717     return TRUE;
718
719   /* video and direction is send */
720   return priv->video_input != NULL;
721 }
722
723 static void
724 empathy_call_window_disconnected (EmpathyCallWindow *self)
725 {
726   EmpathyCallWindowPriv *priv = GET_PRIV (self);
727
728   g_mutex_lock (priv->lock);
729
730   g_timer_stop (priv->timer);
731
732   if (priv->timer_id != 0)
733     g_source_remove (priv->timer_id);
734   priv->timer_id = 0;
735
736   g_mutex_unlock (priv->lock);
737
738   empathy_call_window_status_message (self, _("Disconnected"));
739
740   gtk_widget_set_sensitive (priv->camera_button, FALSE);
741 }
742
743
744 static void
745 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
746 {
747   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
748
749   empathy_call_window_disconnected (self);
750 }
751
752 /* Called with global lock held */
753 static GstPad *
754 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
755 {
756   EmpathyCallWindowPriv *priv = GET_PRIV (self);
757   GstPad *pad;
758
759   if (priv->funnel == NULL)
760     {
761       GstElement *output;
762
763       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
764         (priv->video_output));
765
766       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
767
768       gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
769       gst_bin_add (GST_BIN (priv->pipeline), output);
770
771       gst_element_link (priv->funnel, output);
772
773       gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
774       gst_element_set_state (output, GST_STATE_PLAYING);
775     }
776
777   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
778
779   return pad;
780 }
781
782 /* Called with global lock held */
783 static GstPad *
784 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
785 {
786   EmpathyCallWindowPriv *priv = GET_PRIV (self);
787   GstPad *pad;
788
789   if (priv->liveadder == NULL)
790     {
791       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
792
793       gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
794       gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
795
796       gst_element_link (priv->liveadder, priv->audio_output);
797
798       gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
799       gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
800     }
801
802   pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
803
804   return pad;
805 }
806
807 static gboolean
808 empathy_call_window_update_timer (gpointer user_data)
809 {
810   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
811   EmpathyCallWindowPriv *priv = GET_PRIV (self);
812   gchar *str;
813   gdouble time;
814
815   time = g_timer_elapsed (priv->timer, NULL);
816
817   /* Translators: number of minutes:seconds the caller has been connected */
818   str = g_strdup_printf (_("Connected -- %d:%02dm"), (int) time / 60,
819     (int) time % 60);
820   empathy_call_window_status_message (self, str);
821   g_free (str);
822
823   return TRUE;
824 }
825
826 static gboolean
827 empathy_call_window_connected (gpointer user_data)
828 {
829   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
830   EmpathyCallWindowPriv *priv = GET_PRIV (self);
831   EmpathyTpCall *call;
832
833   g_object_get (priv->handler, "tp-call", &call, NULL);
834
835   if (empathy_tp_call_has_dtmf (call))
836     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
837
838   g_object_unref (call);
839
840   g_mutex_lock (priv->lock);
841
842   priv->timer_id = g_timeout_add_seconds (1,
843     empathy_call_window_update_timer, self);
844
845   g_mutex_unlock (priv->lock);
846
847   empathy_call_window_update_timer (self);
848
849   return FALSE;
850 }
851
852
853 /* Called from the streaming thread */
854 static void
855 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
856   GstPad *src, guint media_type, gpointer user_data)
857 {
858   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
859   EmpathyCallWindowPriv *priv = GET_PRIV (self);
860
861   GstPad *pad;
862
863   g_mutex_lock (priv->lock);
864
865   if (priv->connected == FALSE)
866     {
867       g_timer_start (priv->timer);
868       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
869       priv->connected = TRUE;
870     }
871
872   switch (media_type)
873     {
874       case TP_MEDIA_STREAM_TYPE_AUDIO:
875         pad = empathy_call_window_get_audio_sink_pad (self);
876         break;
877       case TP_MEDIA_STREAM_TYPE_VIDEO:
878         pad = empathy_call_window_get_video_sink_pad (self);
879         break;
880       default:
881         g_assert_not_reached ();
882     }
883
884   gst_pad_link (src, pad);
885   gst_object_unref (pad);
886
887   g_mutex_unlock (priv->lock);
888 }
889
890 /* Called from the streaming thread */
891 static void
892 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
893   GstPad *sink, guint media_type, gpointer user_data)
894 {
895   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
896   EmpathyCallWindowPriv *priv = GET_PRIV (self);
897   GstPad *pad;
898
899   switch (media_type)
900     {
901       case TP_MEDIA_STREAM_TYPE_AUDIO:
902         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
903         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
904
905         pad = gst_element_get_static_pad (priv->audio_input, "src");
906         gst_pad_link (pad, sink);
907         break;
908       case TP_MEDIA_STREAM_TYPE_VIDEO:
909         if (priv->video_input != NULL)
910           {
911             pad =  gst_element_get_request_pad (priv->video_tee, "src%d");
912             gst_pad_link (pad, sink);
913           }
914         break;
915       default:
916         g_assert_not_reached ();
917     }
918
919 }
920
921 static gboolean
922 empathy_gst_bin_has_child (GstBin *bin, GstElement *element)
923 {
924   GstIterator *it;
925   gboolean ret = FALSE;
926   GstElement *item;
927
928   it = gst_bin_iterate_recurse (bin);
929
930   for (;;)
931     {
932       switch (gst_iterator_next (it, (gpointer *)&item))
933        {
934          case GST_ITERATOR_OK:
935            if (item == element)
936             {
937               gst_object_unref (GST_OBJECT (item));
938               ret = TRUE;
939               goto out;
940             }
941            gst_object_unref (GST_OBJECT (item));
942            break;
943          case GST_ITERATOR_RESYNC:
944            gst_iterator_resync (it);
945            break;
946         case GST_ITERATOR_ERROR:
947            g_assert_not_reached ();
948            /* fallthrough */
949         case GST_ITERATOR_DONE:
950            goto out;
951            break;
952       }
953     }
954     gst_iterator_free (it);
955
956 out:
957   return ret;
958 }
959
960 static void
961 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
962 {
963   EmpathyCallWindowPriv *priv = GET_PRIV (self);
964   GstElement *preview;
965
966   preview = empathy_video_widget_get_element (
967     EMPATHY_VIDEO_WIDGET (priv->video_preview));
968
969   gst_element_set_state (priv->video_input, GST_STATE_NULL);
970   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
971   gst_element_set_state (preview, GST_STATE_NULL);
972
973   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
974     priv->video_tee, preview, NULL);
975
976   g_object_unref (priv->video_input);
977   priv->video_input = NULL;
978   g_object_unref (priv->video_tee);
979   priv->video_tee = NULL;
980 }
981
982
983 static gboolean
984 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
985   gpointer user_data)
986 {
987   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
988   EmpathyCallWindowPriv *priv = GET_PRIV (self);
989   GstState newstate;
990
991   empathy_call_handler_bus_message (priv->handler, bus, message);
992
993   switch (GST_MESSAGE_TYPE (message))
994     {
995       case GST_MESSAGE_STATE_CHANGED:
996         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
997           {
998             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
999             if (newstate == GST_STATE_PAUSED)
1000                 empathy_call_window_setup_video_input (self);
1001           }
1002         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1003             !priv->call_started)
1004           {
1005             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1006             if (newstate == GST_STATE_PAUSED)
1007               {
1008                 priv->call_started = TRUE;
1009                 empathy_call_handler_start_call (priv->handler);
1010                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1011               }
1012           }
1013         break;
1014       case GST_MESSAGE_ERROR:
1015         {
1016           GError *error;
1017           gchar *debug;
1018
1019           gst_message_parse_error (message, &error, &debug);
1020
1021           g_message ("Element error: %s -- %s\n", error->message, debug);
1022
1023           if (empathy_gst_bin_has_child (GST_BIN (priv->video_input),
1024               GST_ELEMENT (GST_MESSAGE_SRC (message))))
1025             {
1026               /* Remove the video input and continue */
1027               empathy_call_window_remove_video_input (self);
1028               gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1029             }
1030           else
1031             {
1032               gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1033               empathy_call_window_disconnected (self);
1034             }
1035           g_error_free (error);
1036           g_free (debug);
1037         }
1038       default:
1039         break;
1040     }
1041
1042   return TRUE;
1043 }
1044
1045 static void
1046 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1047 {
1048   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1049   GstElement *preview;
1050
1051   g_signal_connect (priv->handler, "conference-added",
1052     G_CALLBACK (empathy_call_window_conference_added_cb), window);
1053   g_signal_connect (priv->handler, "request-resource",
1054     G_CALLBACK (empathy_call_window_request_resource_cb), window);
1055   g_signal_connect (priv->handler, "closed",
1056     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1057   g_signal_connect (priv->handler, "src-pad-added",
1058     G_CALLBACK (empathy_call_window_src_added_cb), window);
1059   g_signal_connect (priv->handler, "sink-pad-added",
1060     G_CALLBACK (empathy_call_window_sink_added_cb), window);
1061
1062
1063   preview = empathy_video_widget_get_element (
1064     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1065
1066   gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
1067     priv->video_tee, preview, NULL);
1068   gst_element_link_many (priv->video_input, priv->video_tee,
1069     preview, NULL);
1070
1071   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1072 }
1073
1074 static gboolean
1075 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1076   EmpathyCallWindow *window)
1077 {
1078   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1079
1080   gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1081
1082   return FALSE;
1083 }
1084
1085 static void
1086 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1087   EmpathyCallWindow *window)
1088 {
1089   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1090   GtkWidget *arrow;
1091
1092   if (gtk_toggle_button_get_active (toggle))
1093     {
1094       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1095       gtk_widget_show (priv->sidebar);
1096     }
1097   else
1098     {
1099       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1100       gtk_widget_hide (priv->sidebar);
1101     }
1102
1103   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1104 }
1105
1106 static void
1107 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1108   EmpathyCallWindow *window)
1109 {
1110   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1111   gboolean active;
1112   EmpathyTpCall *call;
1113
1114   active = (gtk_toggle_tool_button_get_active (toggle));
1115
1116   g_object_get (priv->handler, "tp-call", &call, NULL);
1117
1118   empathy_tp_call_request_video_stream_direction (call, active);
1119
1120   g_object_unref (call);
1121 }
1122
1123 static void
1124 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1125   EmpathyCallWindow *window)
1126 {
1127   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1128
1129   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1130     FALSE);
1131 }
1132
1133 static void
1134 empathy_call_window_hangup (EmpathyCallWindow *window)
1135 {
1136   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1137
1138   gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1139   gtk_widget_destroy (GTK_WIDGET (window));
1140 }
1141
1142 static void
1143 empathy_call_window_status_message (EmpathyCallWindow *window,
1144   gchar *message)
1145 {
1146   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1147
1148   if (priv->context_id == 0)
1149     {
1150       priv->context_id = gtk_statusbar_get_context_id (
1151         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
1152     }
1153   else
1154     {
1155       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
1156     }
1157
1158   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
1159     message);
1160 }
1161
1162 static void
1163 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
1164   gdouble value, EmpathyCallWindow *window)
1165 {
1166   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1167
1168   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
1169     value);
1170 }