]> git.0d.be Git - empathy.git/blob - libempathy/empathy-call-handler.c
a49baed447109abd071935900b0dad856739f216
[empathy.git] / libempathy / empathy-call-handler.c
1 /*
2  * empathy-call-handler.c - Source for EmpathyCallHandler
3  * Copyright (C) 2008-2009 Collabora Ltd.
4  * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include <telepathy-glib/util.h>
26 #include <telepathy-glib/interfaces.h>
27
28 #include <telepathy-farsight/channel.h>
29 #include <telepathy-farsight/stream.h>
30
31 #include "empathy-call-handler.h"
32 #include "empathy-dispatcher.h"
33 #include "empathy-marshal.h"
34 #include "empathy-utils.h"
35
36 G_DEFINE_TYPE(EmpathyCallHandler, empathy_call_handler, G_TYPE_OBJECT)
37
38 /* signal enum */
39 enum {
40   CONFERENCE_ADDED,
41   SRC_PAD_ADDED,
42   SINK_PAD_ADDED,
43   REQUEST_RESOURCE,
44   CLOSED,
45   STREAM_CLOSED,
46   LAST_SIGNAL
47 };
48
49 static guint signals[LAST_SIGNAL] = {0};
50
51 enum {
52   PROP_TP_CALL = 1,
53   PROP_GST_BUS,
54   PROP_CONTACT,
55   PROP_INITIAL_AUDIO,
56   PROP_INITIAL_VIDEO
57 };
58
59 /* private structure */
60
61 typedef struct {
62   gboolean dispose_has_run;
63   EmpathyTpCall *call;
64   EmpathyContact *contact;
65   TfChannel *tfchannel;
66   gboolean initial_audio;
67   gboolean initial_video;
68 } EmpathyCallHandlerPriv;
69
70 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyCallHandler)
71
72 static void
73 empathy_call_handler_dispose (GObject *object)
74 {
75   EmpathyCallHandlerPriv *priv = GET_PRIV (object);
76
77   if (priv->dispose_has_run)
78     return;
79
80   priv->dispose_has_run = TRUE;
81
82   if (priv->contact != NULL)
83     g_object_unref (priv->contact);
84
85   priv->contact = NULL;
86
87   if (priv->tfchannel != NULL)
88     g_object_unref (priv->tfchannel);
89
90   priv->tfchannel = NULL;
91
92   if (priv->call != NULL)
93     {
94       empathy_tp_call_close (priv->call);
95       g_object_unref (priv->call);
96     }
97
98   priv->call = NULL;
99
100   /* release any references held by the object here */
101   if (G_OBJECT_CLASS (empathy_call_handler_parent_class)->dispose)
102     G_OBJECT_CLASS (empathy_call_handler_parent_class)->dispose (object);
103 }
104
105 static void
106 empathy_call_handler_finalize (GObject *object)
107 {
108   /* free any data held directly by the object here */
109   if (G_OBJECT_CLASS (empathy_call_handler_parent_class)->finalize)
110     G_OBJECT_CLASS (empathy_call_handler_parent_class)->finalize (object);
111 }
112
113 static void
114 empathy_call_handler_init (EmpathyCallHandler *obj)
115 {
116   EmpathyCallHandlerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (obj,
117     EMPATHY_TYPE_CALL_HANDLER, EmpathyCallHandlerPriv);
118
119   obj->priv = priv;
120 }
121
122 static void
123 empathy_call_handler_constructed (GObject *object)
124 {
125   EmpathyCallHandlerPriv *priv = GET_PRIV (object);
126
127   if (priv->contact == NULL)
128     {
129       g_object_get (priv->call, "contact", &(priv->contact), NULL);
130     }
131 }
132
133 static void
134 empathy_call_handler_set_property (GObject *object,
135   guint property_id, const GValue *value, GParamSpec *pspec)
136 {
137   EmpathyCallHandlerPriv *priv = GET_PRIV (object);
138
139   switch (property_id)
140     {
141       case PROP_CONTACT:
142         priv->contact = g_value_dup_object (value);
143         break;
144       case PROP_TP_CALL:
145         priv->call = g_value_dup_object (value);
146         break;
147       case PROP_INITIAL_AUDIO:
148         priv->initial_audio = g_value_get_boolean (value);
149         break;
150       case PROP_INITIAL_VIDEO:
151         priv->initial_video = g_value_get_boolean (value);
152         break;
153       default:
154         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
155     }
156 }
157
158 static void
159 empathy_call_handler_get_property (GObject *object,
160   guint property_id, GValue *value, GParamSpec *pspec)
161 {
162   EmpathyCallHandlerPriv *priv = GET_PRIV (object);
163
164   switch (property_id)
165     {
166       case PROP_CONTACT:
167         g_value_set_object (value, priv->contact);
168         break;
169       case PROP_TP_CALL:
170         g_value_set_object (value, priv->call);
171         break;
172       case PROP_INITIAL_AUDIO:
173         g_value_set_boolean (value, priv->initial_audio);
174         break;
175       case PROP_INITIAL_VIDEO:
176         g_value_set_boolean (value, priv->initial_video);
177         break;
178       default:
179         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
180     }
181 }
182
183
184 static void
185 empathy_call_handler_class_init (EmpathyCallHandlerClass *klass)
186 {
187   GObjectClass *object_class = G_OBJECT_CLASS (klass);
188   GParamSpec *param_spec;
189
190   g_type_class_add_private (klass, sizeof (EmpathyCallHandlerPriv));
191
192   object_class->constructed = empathy_call_handler_constructed;
193   object_class->set_property = empathy_call_handler_set_property;
194   object_class->get_property = empathy_call_handler_get_property;
195   object_class->dispose = empathy_call_handler_dispose;
196   object_class->finalize = empathy_call_handler_finalize;
197
198   param_spec = g_param_spec_object ("contact",
199     "contact", "The remote contact",
200     EMPATHY_TYPE_CONTACT,
201     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
202   g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
203
204   param_spec = g_param_spec_object ("tp-call",
205     "tp-call", "The calls channel wrapper",
206     EMPATHY_TYPE_TP_CALL,
207     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
208   g_object_class_install_property (object_class, PROP_TP_CALL, param_spec);
209
210   param_spec = g_param_spec_boolean ("initial-audio",
211     "initial-audio", "Whether the call should start with audio",
212     TRUE,
213     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
214   g_object_class_install_property (object_class, PROP_INITIAL_AUDIO,
215       param_spec);
216
217   param_spec = g_param_spec_boolean ("initial-video",
218     "initial-video", "Whether the call should start with video",
219     FALSE,
220     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
221   g_object_class_install_property (object_class, PROP_INITIAL_VIDEO,
222     param_spec);
223
224   signals[CONFERENCE_ADDED] =
225     g_signal_new ("conference-added", G_TYPE_FROM_CLASS (klass),
226       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
227       g_cclosure_marshal_VOID__OBJECT,
228       G_TYPE_NONE,
229       1, FS_TYPE_CONFERENCE);
230
231   signals[SRC_PAD_ADDED] =
232     g_signal_new ("src-pad-added", G_TYPE_FROM_CLASS (klass),
233       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
234       _empathy_marshal_BOOLEAN__OBJECT_UINT,
235       G_TYPE_BOOLEAN,
236       2, GST_TYPE_PAD, G_TYPE_UINT);
237
238   signals[SINK_PAD_ADDED] =
239     g_signal_new ("sink-pad-added", G_TYPE_FROM_CLASS (klass),
240       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
241       _empathy_marshal_BOOLEAN__OBJECT_UINT,
242       G_TYPE_BOOLEAN,
243       2, GST_TYPE_PAD, G_TYPE_UINT);
244
245   signals[REQUEST_RESOURCE] =
246     g_signal_new ("request-resource", G_TYPE_FROM_CLASS (klass),
247       G_SIGNAL_RUN_LAST, 0,
248       g_signal_accumulator_true_handled, NULL,
249       _empathy_marshal_BOOLEAN__UINT_UINT,
250       G_TYPE_BOOLEAN, 2, G_TYPE_UINT, G_TYPE_UINT);
251
252   signals[CLOSED] =
253     g_signal_new ("closed", G_TYPE_FROM_CLASS (klass),
254       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
255       g_cclosure_marshal_VOID__VOID,
256       G_TYPE_NONE,
257       0);
258
259   signals[STREAM_CLOSED] =
260     g_signal_new ("stream-closed", G_TYPE_FROM_CLASS (klass),
261       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
262       g_cclosure_marshal_VOID__OBJECT,
263       G_TYPE_NONE, 1, TF_TYPE_STREAM);
264 }
265
266 /**
267  * empathy_call_handler_new_for_contact:
268  * @contact: an #EmpathyContact
269  *
270  * Creates a new #EmpathyCallHandler with contact @contact.
271  *
272  * Return value: a new #EmpathyCallHandler
273  */
274 EmpathyCallHandler *
275 empathy_call_handler_new_for_contact (EmpathyContact *contact)
276 {
277   return EMPATHY_CALL_HANDLER (g_object_new (EMPATHY_TYPE_CALL_HANDLER,
278     "contact", contact, NULL));
279 }
280
281 /**
282  * empathy_call_handler_new_for_contact_with_streams:
283  * @contact: an #EmpathyContact
284  * @audio: if %TRUE the call will be started with audio
285  * @video: if %TRUE the call will be started with video
286  *
287  * Creates a new #EmpathyCallHandler with contact @contact.
288  *
289  * Return value: a new #EmpathyCallHandler
290  */
291 EmpathyCallHandler *
292 empathy_call_handler_new_for_contact_with_streams (EmpathyContact *contact,
293     gboolean audio, gboolean video)
294 {
295   return EMPATHY_CALL_HANDLER (g_object_new (EMPATHY_TYPE_CALL_HANDLER,
296     "contact", contact,
297     "initial-audio", audio,
298     "initial-video", video,
299     NULL));
300 }
301
302 EmpathyCallHandler *
303 empathy_call_handler_new_for_channel (EmpathyTpCall *call)
304 {
305   return EMPATHY_CALL_HANDLER (g_object_new (EMPATHY_TYPE_CALL_HANDLER,
306     "tp-call", call,
307     "initial-video", empathy_tp_call_is_receiving_video (call),
308     NULL));
309 }
310
311 void
312 empathy_call_handler_bus_message (EmpathyCallHandler *handler,
313   GstBus *bus, GstMessage *message)
314 {
315   EmpathyCallHandlerPriv *priv = GET_PRIV (handler);
316
317   if (priv->tfchannel == NULL)
318     return;
319
320   tf_channel_bus_message (priv->tfchannel, message);
321 }
322
323 static void
324 empathy_call_handler_tf_channel_session_created_cb (TfChannel *tfchannel,
325   FsConference *conference, FsParticipant *participant,
326   EmpathyCallHandler *self)
327 {
328   g_signal_emit (G_OBJECT (self), signals[CONFERENCE_ADDED], 0,
329     GST_ELEMENT (conference));
330 }
331
332 static gboolean
333 src_pad_added_error_idle (gpointer data)
334 {
335   TfStream *stream = data;
336
337   tf_stream_error (stream, TP_MEDIA_STREAM_ERROR_MEDIA_ERROR,
338       "Could not link sink");
339   g_object_unref (stream);
340
341   return FALSE;
342 }
343
344 static void
345 empathy_call_handler_tf_stream_src_pad_added_cb (TfStream *stream,
346   GstPad *pad, FsCodec *codec, EmpathyCallHandler  *handler)
347 {
348   guint media_type;
349   gboolean retval;
350
351   g_object_get (stream, "media-type", &media_type, NULL);
352
353   g_signal_emit (G_OBJECT (handler), signals[SRC_PAD_ADDED], 0,
354       pad, media_type, &retval);
355
356   if (!retval)
357     g_idle_add (src_pad_added_error_idle, g_object_ref (stream));
358 }
359
360
361 static gboolean
362 empathy_call_handler_tf_stream_request_resource_cb (TfStream *stream,
363   guint direction, EmpathyTpCall *call)
364 {
365   gboolean ret;
366   guint media_type;
367
368   g_object_get (G_OBJECT (stream), "media-type", &media_type, NULL);
369
370   g_signal_emit (G_OBJECT (call),
371     signals[REQUEST_RESOURCE], 0, media_type, direction, &ret);
372
373   return ret;
374 }
375
376 static void
377 empathy_call_handler_tf_stream_closed_cb (TfStream *stream,
378   EmpathyCallHandler *handler)
379 {
380   g_signal_emit (handler, signals[STREAM_CLOSED], 0, stream);
381 }
382
383 static void
384 empathy_call_handler_tf_channel_stream_created_cb (TfChannel *tfchannel,
385   TfStream *stream, EmpathyCallHandler *handler)
386 {
387   guint media_type;
388   GstPad *spad;
389   gboolean retval;
390
391   g_signal_connect (stream, "src-pad-added",
392       G_CALLBACK (empathy_call_handler_tf_stream_src_pad_added_cb), handler);
393   g_signal_connect (stream, "request-resource",
394       G_CALLBACK (empathy_call_handler_tf_stream_request_resource_cb),
395         handler);
396   g_signal_connect (stream, "closed",
397       G_CALLBACK (empathy_call_handler_tf_stream_closed_cb), handler);
398
399   g_object_get (stream, "media-type", &media_type,
400     "sink-pad", &spad, NULL);
401
402   g_signal_emit (G_OBJECT (handler), signals[SINK_PAD_ADDED], 0,
403       spad, media_type, &retval);
404
405  if (!retval)
406       tf_stream_error (stream, TP_MEDIA_STREAM_ERROR_MEDIA_ERROR,
407           "Could not link source");
408
409   gst_object_unref (spad);
410 }
411
412 static void
413 empathy_call_handler_tf_channel_closed_cb (TfChannel *tfchannel,
414   EmpathyCallHandler *handler)
415 {
416   g_signal_emit (G_OBJECT (handler), signals[CLOSED], 0);
417 }
418
419 static GList *
420 empathy_call_handler_tf_channel_codec_config_cb (TfChannel *channel,
421   guint stream_id, FsMediaType media_type, guint direction, gpointer user_data)
422 {
423   gchar *filename = empathy_file_lookup ("codec-preferences", "data");
424   GList *codecs;
425   GError *error = NULL;
426
427   codecs = fs_codec_list_from_keyfile (filename, &error);
428   g_free (filename);
429
430   if (!codecs)
431     {
432       g_warning ("No codec-preferences file: %s",
433           error ? error->message : "No error message");
434     }
435   g_clear_error (&error);
436
437   return codecs;
438 }
439
440 static void
441 empathy_call_handler_start_tpfs (EmpathyCallHandler *self)
442 {
443   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
444   TpChannel *channel;
445
446   g_object_get (priv->call, "channel", &channel, NULL);
447
448   g_assert (channel != NULL);
449
450   priv->tfchannel = tf_channel_new (channel);
451
452   /* Set up the telepathy farsight channel */
453   g_signal_connect (priv->tfchannel, "session-created",
454       G_CALLBACK (empathy_call_handler_tf_channel_session_created_cb), self);
455   g_signal_connect (priv->tfchannel, "stream-created",
456       G_CALLBACK (empathy_call_handler_tf_channel_stream_created_cb), self);
457   g_signal_connect (priv->tfchannel, "closed",
458       G_CALLBACK (empathy_call_handler_tf_channel_closed_cb), self);
459   g_signal_connect (priv->tfchannel, "stream-get-codec-config",
460       G_CALLBACK (empathy_call_handler_tf_channel_codec_config_cb), self);
461
462   g_object_unref (channel);
463 }
464
465 static void
466 empathy_call_handler_request_cb (EmpathyDispatchOperation *operation,
467   const GError *error, gpointer user_data)
468 {
469   EmpathyCallHandler *self = EMPATHY_CALL_HANDLER (user_data);
470   EmpathyCallHandlerPriv *priv = GET_PRIV (self);
471
472   if (error != NULL)
473     return;
474
475   priv->call = EMPATHY_TP_CALL (
476     empathy_dispatch_operation_get_channel_wrapper (operation));
477
478   g_object_ref (priv->call);
479   g_object_notify (G_OBJECT (self), "tp-call");
480
481   empathy_call_handler_start_tpfs (self);
482
483   empathy_tp_call_to (priv->call, priv->contact,
484     priv->initial_audio, priv->initial_video);
485
486   empathy_dispatch_operation_claim (operation);
487 }
488
489 void
490 empathy_call_handler_start_call (EmpathyCallHandler *handler)
491 {
492
493   EmpathyCallHandlerPriv *priv = GET_PRIV (handler);
494   EmpathyDispatcher *dispatcher;
495   TpConnection *connection;
496   GList *classes;
497   GValue *value;
498   GHashTable *request;
499
500   if (priv->call != NULL)
501     {
502       empathy_call_handler_start_tpfs (handler);
503       empathy_tp_call_accept_incoming_call (priv->call);
504       return;
505     }
506
507   g_assert (priv->contact != NULL);
508
509   dispatcher = empathy_dispatcher_dup_singleton ();
510   connection = empathy_contact_get_connection (priv->contact);
511   classes = empathy_dispatcher_find_requestable_channel_classes
512     (dispatcher, connection, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
513      TP_HANDLE_TYPE_CONTACT, NULL);
514
515   if (classes == NULL)
516     return;
517
518   g_list_free (classes);
519
520   request = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
521       (GDestroyNotify) tp_g_value_slice_free);
522
523   /* org.freedesktop.Telepathy.Channel.ChannelType */
524   value = tp_g_value_slice_new (G_TYPE_STRING);
525   g_value_set_string (value, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
526   g_hash_table_insert (request, TP_IFACE_CHANNEL ".ChannelType", value);
527
528   /* org.freedesktop.Telepathy.Channel.TargetHandleType */
529   value = tp_g_value_slice_new (G_TYPE_UINT);
530   g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
531   g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandleType", value);
532
533   /* org.freedesktop.Telepathy.Channel.TargetHandle*/
534   value = tp_g_value_slice_new (G_TYPE_UINT);
535   g_value_set_uint (value, empathy_contact_get_handle (priv->contact));
536   g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandle", value);
537
538   empathy_dispatcher_create_channel (dispatcher, connection,
539     request, empathy_call_handler_request_cb, handler);
540
541   g_object_unref (dispatcher);
542 }
543
544 /**
545  * empathy_call_handler_stop_call:
546  * @handler: an #EmpathyCallHandler
547  *
548  * Closes the #EmpathyCallHandler's call and frees its resources.
549  */
550 void
551 empathy_call_handler_stop_call (EmpathyCallHandler *handler)
552 {
553   EmpathyCallHandlerPriv *priv = GET_PRIV (handler);
554
555   if (priv->call != NULL)
556     {
557       empathy_tp_call_leave (priv->call);
558       g_object_unref (priv->call);
559     }
560
561   priv->call = NULL;
562 }
563
564 /**
565  * empathy_call_handler_has_initial_video:
566  * @handler: an #EmpathyCallHandler
567  *
568  * Return %TRUE if the call managed by this #EmpathyCallHandler was
569  * created with video enabled
570  *
571  * Return value: %TRUE if the call was created as a video conversation.
572  */
573 gboolean
574 empathy_call_handler_has_initial_video (EmpathyCallHandler *handler)
575 {
576   EmpathyCallHandlerPriv *priv = GET_PRIV (handler);
577
578   return priv->initial_video;
579 }
580