]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
102395e57faf465df7263454a06307d3326b3b72
[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 void
707 empathy_call_window_disconnected (EmpathyCallWindow *self)
708 {
709   EmpathyCallWindowPriv *priv = GET_PRIV (self);
710
711   g_mutex_lock (priv->lock);
712
713   g_timer_stop (priv->timer);
714
715   if (priv->timer_id != 0)
716     g_source_remove (priv->timer_id);
717   priv->timer_id = 0;
718
719   g_mutex_unlock (priv->lock);
720
721   empathy_call_window_status_message (self, _("Disconnected"));
722
723   gtk_widget_set_sensitive (priv->camera_button, FALSE);
724 }
725
726
727 static void
728 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
729 {
730   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
731
732   empathy_call_window_disconnected (self);
733 }
734
735 /* Called with global lock held */
736 static GstPad *
737 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
738 {
739   EmpathyCallWindowPriv *priv = GET_PRIV (self);
740   GstPad *pad;
741
742   if (priv->funnel == NULL)
743     {
744       GstElement *output;
745
746       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
747         (priv->video_output));
748
749       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
750
751       gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
752       gst_bin_add (GST_BIN (priv->pipeline), output);
753
754       gst_element_link (priv->funnel, output);
755
756       gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
757       gst_element_set_state (output, GST_STATE_PLAYING);
758     }
759
760   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
761
762   return pad;
763 }
764
765 /* Called with global lock held */
766 static GstPad *
767 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
768 {
769   EmpathyCallWindowPriv *priv = GET_PRIV (self);
770   GstPad *pad;
771
772   if (priv->liveadder == NULL)
773     {
774       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
775
776       gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
777       gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
778
779       gst_element_link (priv->liveadder, priv->audio_output);
780
781       gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
782       gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
783     }
784
785   pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
786
787   return pad;
788 }
789
790 static gboolean
791 empathy_call_window_update_timer (gpointer user_data)
792 {
793   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
794   EmpathyCallWindowPriv *priv = GET_PRIV (self);
795   gchar *str;
796   gdouble time;
797
798   time = g_timer_elapsed (priv->timer, NULL);
799
800   /* Translators: number of minutes:seconds the caller has been connected */
801   str = g_strdup_printf (_("Connected -- %d:%02dm"), (int) time / 60,
802     (int) time % 60);
803   empathy_call_window_status_message (self, str);
804   g_free (str);
805
806   return TRUE;
807 }
808
809 static gboolean
810 empathy_call_window_connected (gpointer user_data)
811 {
812   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
813   EmpathyCallWindowPriv *priv = GET_PRIV (self);
814   EmpathyTpCall *call;
815
816   g_object_get (priv->handler, "tp-call", &call, NULL);
817
818   if (empathy_tp_call_has_dtmf (call))
819     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
820
821   g_object_unref (call);
822
823   g_mutex_lock (priv->lock);
824
825   priv->timer_id = g_timeout_add_seconds (1,
826     empathy_call_window_update_timer, self);
827
828   g_mutex_unlock (priv->lock);
829
830   empathy_call_window_update_timer (self);
831
832   return FALSE;
833 }
834
835
836 /* Called from the streaming thread */
837 static void
838 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
839   GstPad *src, guint media_type, gpointer user_data)
840 {
841   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
842   EmpathyCallWindowPriv *priv = GET_PRIV (self);
843
844   GstPad *pad;
845
846   g_mutex_lock (priv->lock);
847
848   if (priv->connected == FALSE)
849     {
850       g_timer_start (priv->timer);
851       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
852       priv->connected = TRUE;
853     }
854
855   switch (media_type)
856     {
857       case TP_MEDIA_STREAM_TYPE_AUDIO:
858         pad = empathy_call_window_get_audio_sink_pad (self);
859         break;
860       case TP_MEDIA_STREAM_TYPE_VIDEO:
861         pad = empathy_call_window_get_video_sink_pad (self);
862         break;
863       default:
864         g_assert_not_reached ();
865     }
866
867   gst_pad_link (src, pad);
868   gst_object_unref (pad);
869
870   g_mutex_unlock (priv->lock);
871 }
872
873 /* Called from the streaming thread */
874 static void
875 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
876   GstPad *sink, guint media_type, gpointer user_data)
877 {
878   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
879   EmpathyCallWindowPriv *priv = GET_PRIV (self);
880   GstPad *pad;
881
882   switch (media_type)
883     {
884       case TP_MEDIA_STREAM_TYPE_AUDIO:
885         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
886         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
887
888         pad = gst_element_get_static_pad (priv->audio_input, "src");
889         gst_pad_link (pad, sink);
890         break;
891       case TP_MEDIA_STREAM_TYPE_VIDEO:
892         if (priv->video_input != NULL)
893           {
894             pad =  gst_element_get_request_pad (priv->video_tee, "src%d");
895             gst_pad_link (pad, sink);
896           }
897         break;
898       default:
899         g_assert_not_reached ();
900     }
901
902 }
903
904 static gboolean
905 empathy_gst_bin_has_child (GstBin *bin, GstElement *element)
906 {
907   GstIterator *it;
908   gboolean ret = FALSE;
909   GstElement *item;
910
911   it = gst_bin_iterate_recurse (bin);
912
913   for (;;)
914     {
915       switch (gst_iterator_next (it, (gpointer *)&item))
916        {
917          case GST_ITERATOR_OK:
918            if (item == element)
919             {
920               gst_object_unref (GST_OBJECT (item));
921               ret = TRUE;
922               goto out;
923             }
924            gst_object_unref (GST_OBJECT (item));
925            break;
926          case GST_ITERATOR_RESYNC:
927            gst_iterator_resync (it);
928            break;
929         case GST_ITERATOR_ERROR:
930            g_assert_not_reached ();
931            /* fallthrough */
932         case GST_ITERATOR_DONE:
933            goto out;
934            break;
935       }
936     }
937     gst_iterator_free (it);
938
939 out:
940   return ret;
941 }
942
943 static void
944 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
945 {
946   EmpathyCallWindowPriv *priv = GET_PRIV (self);
947   GstElement *preview;
948
949   preview = empathy_video_widget_get_element (
950     EMPATHY_VIDEO_WIDGET (priv->video_preview));
951
952   gst_element_set_state (priv->video_input, GST_STATE_NULL);
953   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
954   gst_element_set_state (preview, GST_STATE_NULL);
955
956   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
957     priv->video_tee, preview, NULL);
958
959   g_object_unref (priv->video_input);
960   priv->video_input = NULL;
961   g_object_unref (priv->video_tee);
962   priv->video_tee = NULL;
963 }
964
965
966 static gboolean
967 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
968   gpointer user_data)
969 {
970   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
971   EmpathyCallWindowPriv *priv = GET_PRIV (self);
972   GstState newstate;
973
974   empathy_call_handler_bus_message (priv->handler, bus, message);
975
976   switch (GST_MESSAGE_TYPE (message))
977     {
978       case GST_MESSAGE_STATE_CHANGED:
979         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
980           {
981             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
982             if (newstate == GST_STATE_PAUSED)
983                 empathy_call_window_setup_video_input (self);
984           }
985         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
986             !priv->call_started)
987           {
988             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
989             if (newstate == GST_STATE_PAUSED)
990               {
991                 priv->call_started = TRUE;
992                 empathy_call_handler_start_call (priv->handler);
993                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
994               }
995           }
996         break;
997       case GST_MESSAGE_ERROR:
998         {
999           GError *error;
1000           gchar *debug;
1001
1002           gst_message_parse_error (message, &error, &debug);
1003
1004           g_message ("Element error: %s -- %s\n", error->message, debug);
1005
1006           if (empathy_gst_bin_has_child (GST_BIN (priv->video_input),
1007               GST_ELEMENT (GST_MESSAGE_SRC (message))))
1008             {
1009               /* Remove the video input and continue */
1010               empathy_call_window_remove_video_input (self);
1011               gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1012             }
1013           else
1014             {
1015               gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1016               empathy_call_window_disconnected (self);
1017             }
1018           g_error_free (error);
1019           g_free (debug);
1020         }
1021       default:
1022         break;
1023     }
1024
1025   return TRUE;
1026 }
1027
1028 static void
1029 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1030 {
1031   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1032   GstElement *preview;
1033
1034   g_signal_connect (priv->handler, "conference-added",
1035     G_CALLBACK (empathy_call_window_conference_added_cb), window);
1036   g_signal_connect (priv->handler, "closed",
1037     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1038   g_signal_connect (priv->handler, "src-pad-added",
1039     G_CALLBACK (empathy_call_window_src_added_cb), window);
1040   g_signal_connect (priv->handler, "sink-pad-added",
1041     G_CALLBACK (empathy_call_window_sink_added_cb), window);
1042
1043
1044   preview = empathy_video_widget_get_element (
1045     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1046
1047   gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
1048     priv->video_tee, preview, NULL);
1049   gst_element_link_many (priv->video_input, priv->video_tee,
1050     preview, NULL);
1051
1052   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1053 }
1054
1055 static gboolean
1056 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1057   EmpathyCallWindow *window)
1058 {
1059   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1060
1061   gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1062
1063   return FALSE;
1064 }
1065
1066 static void
1067 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1068   EmpathyCallWindow *window)
1069 {
1070   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1071   GtkWidget *arrow;
1072
1073   if (gtk_toggle_button_get_active (toggle))
1074     {
1075       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1076       gtk_widget_show (priv->sidebar);
1077     }
1078   else
1079     {
1080       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1081       gtk_widget_hide (priv->sidebar);
1082     }
1083
1084   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1085 }
1086
1087 static void
1088 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1089   EmpathyCallWindow *window)
1090 {
1091   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1092   gboolean active;
1093   EmpathyTpCall *call;
1094
1095   active = (gtk_toggle_tool_button_get_active (toggle));
1096
1097   g_object_get (priv->handler, "tp-call", &call, NULL);
1098
1099   empathy_tp_call_request_video_stream_direction (call, active);
1100
1101   g_object_unref (call);
1102 }
1103
1104 static void
1105 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1106   EmpathyCallWindow *window)
1107 {
1108   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1109
1110   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1111     FALSE);
1112 }
1113
1114 static void
1115 empathy_call_window_hangup (EmpathyCallWindow *window)
1116 {
1117   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1118
1119   gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1120   gtk_widget_destroy (GTK_WIDGET (window));
1121 }
1122
1123 static void
1124 empathy_call_window_status_message (EmpathyCallWindow *window,
1125   gchar *message)
1126 {
1127   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1128
1129   if (priv->context_id == 0)
1130     {
1131       priv->context_id = gtk_statusbar_get_context_id (
1132         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
1133     }
1134   else
1135     {
1136       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
1137     }
1138
1139   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
1140     message);
1141 }
1142
1143 static void
1144 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
1145   gdouble value, EmpathyCallWindow *window)
1146 {
1147   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1148
1149   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
1150     value);
1151 }