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