2
* call-content.h - high level API for Call contents
4
* Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
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.
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.
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
22
* SECTION:call-content
23
* @title: TpCallContent
24
* @short_description: proxy object for a call content
26
* #TpCallContent is a sub-class of #TpProxy providing convenient API
27
* to represent #TpCallChannel's content.
33
* Data structure representing a #TpCallContent.
41
* The class of a #TpCallContent.
48
#include "telepathy-glib/call-content.h"
50
#include <telepathy-glib/call-misc.h>
51
#include <telepathy-glib/call-stream.h>
52
#include <telepathy-glib/dbus.h>
53
#include <telepathy-glib/dtmf.h>
54
#include <telepathy-glib/enums.h>
55
#include <telepathy-glib/errors.h>
56
#include <telepathy-glib/gtypes.h>
57
#include <telepathy-glib/interfaces.h>
58
#include <telepathy-glib/proxy-subclass.h>
59
#include <telepathy-glib/util.h>
61
#define DEBUG_FLAG TP_DEBUG_CALL
62
#include "telepathy-glib/debug-internal.h"
63
#include "telepathy-glib/call-internal.h"
64
#include "telepathy-glib/proxy-internal.h"
65
#include "telepathy-glib/util-internal.h"
66
#include "telepathy-glib/_gen/signals-marshal.h"
68
#include "_gen/tp-cli-call-content-body.h"
70
G_DEFINE_TYPE (TpCallContent, tp_call_content, TP_TYPE_PROXY)
72
typedef struct _SendTonesData SendTonesData;
74
struct _TpCallContentPrivate
76
TpConnection *connection;
79
TpMediaStreamType media_type;
80
TpCallContentDisposition disposition;
83
gboolean properties_retrieved;
86
SendTonesData *current_tones;
106
static guint _signals[LAST_SIGNAL] = { 0, };
108
static TpCallStream *
109
_tp_call_stream_new (TpCallContent *self,
110
const gchar *object_path)
112
return g_object_new (TP_TYPE_CALL_STREAM,
113
"bus-name", tp_proxy_get_bus_name (self),
114
"dbus-daemon", tp_proxy_get_dbus_daemon (self),
115
"dbus-connection", tp_proxy_get_dbus_connection (self),
116
"object-path", object_path,
117
"connection", self->priv->connection,
122
streams_added_cb (TpCallContent *self,
123
const GPtrArray *streams,
125
GObject *weak_object)
128
GPtrArray *added_streams;
130
if (!self->priv->properties_retrieved)
133
added_streams = g_ptr_array_sized_new (streams->len);
135
for (i = 0; i < streams->len; i++)
137
const gchar *object_path = g_ptr_array_index (streams, i);
138
TpCallStream *stream ;
140
DEBUG ("Stream added: %s", object_path);
142
stream = _tp_call_stream_new (self, object_path);
143
g_ptr_array_add (self->priv->streams, stream);
144
g_ptr_array_add (added_streams, stream);
147
g_signal_emit (self, _signals[STREAMS_ADDED], 0, added_streams);
148
g_ptr_array_unref (added_streams);
152
streams_removed_cb (TpCallContent *self,
153
const GPtrArray *streams,
154
const GValueArray *reason,
156
GObject *weak_object)
158
GPtrArray *removed_streams;
161
if (!self->priv->properties_retrieved)
164
removed_streams = _tp_g_ptr_array_new_full (streams->len, g_object_unref);
166
for (i = 0; i < streams->len; i++)
168
const gchar *object_path = g_ptr_array_index (streams, i);
169
gboolean found = FALSE;
172
for (j = 0; j < self->priv->streams->len; j++)
174
TpCallStream *stream = g_ptr_array_index (self->priv->streams, j);
176
if (!tp_strdiff (tp_proxy_get_object_path (stream), object_path))
178
DEBUG ("Stream removed: %s", object_path);
181
g_ptr_array_add (removed_streams, g_object_ref (stream));
182
g_ptr_array_remove_index_fast (self->priv->streams, j);
188
DEBUG ("Stream '%s' removed but not found", object_path);
191
if (removed_streams->len > 0)
193
TpCallStateReason *r;
195
r = _tp_call_state_reason_new (reason);
196
g_signal_emit (self, _signals[STREAMS_REMOVED], 0, removed_streams, r);
197
_tp_call_state_reason_unref (r);
200
g_ptr_array_unref (removed_streams);
203
static void tones_stopped_cb (TpCallContent *self,
206
GObject *weak_object);
209
got_all_properties_cb (TpProxy *proxy,
210
GHashTable *properties,
213
GObject *weak_object)
215
TpCallContent *self = (TpCallContent *) proxy;
216
const gchar * const *interfaces;
222
DEBUG ("Could not get the call content properties: %s", error->message);
223
_tp_proxy_set_feature_prepared (proxy,
224
TP_CALL_CONTENT_FEATURE_CORE, FALSE);
228
self->priv->properties_retrieved = TRUE;
230
interfaces = tp_asv_get_boxed (properties,
231
"Interfaces", G_TYPE_STRV);
232
self->priv->name = g_strdup (tp_asv_get_string (properties,
234
self->priv->media_type = tp_asv_get_uint32 (properties,
236
self->priv->disposition = tp_asv_get_uint32 (properties,
237
"Disposition", NULL);
238
streams = tp_asv_get_boxed (properties,
239
"Streams", TP_ARRAY_TYPE_OBJECT_PATH_LIST);
241
tp_proxy_add_interfaces ((TpProxy *) self, interfaces);
243
for (i = 0; i < streams->len; i++)
245
const gchar *object_path = g_ptr_array_index (streams, i);
247
DEBUG ("Initial stream added: %s", object_path);
249
g_ptr_array_add (self->priv->streams,
250
_tp_call_stream_new (self, object_path));
253
if (tp_proxy_has_interface_by_id (self,
254
TP_IFACE_QUARK_CALL_CONTENT_INTERFACE_DTMF))
256
tp_cli_call_content_interface_dtmf_connect_to_stopped_tones (self,
257
tones_stopped_cb, NULL, NULL, NULL, NULL);
260
_tp_proxy_set_feature_prepared (proxy, TP_CALL_CONTENT_FEATURE_CORE, TRUE);
263
struct _SendTonesData
265
TpCallContent *content;
267
GSimpleAsyncResult *result;
268
GCancellable *cancellable;
272
static void maybe_send_tones (TpCallContent *self);
273
static void send_tones_cancelled_cb (GCancellable *cancellable,
274
SendTonesData *data);
276
static SendTonesData *
277
send_tones_data_new (TpCallContent *self,
279
GSimpleAsyncResult *result,
280
GCancellable *cancellable)
284
data = g_slice_new0 (SendTonesData);
285
data->content = g_object_ref (self);
286
data->tones = g_strdup (tones);
287
data->result = g_object_ref (result);
289
if (cancellable != NULL)
291
data->cancellable = g_object_ref (cancellable);
292
data->cancel_id = g_cancellable_connect (cancellable,
293
G_CALLBACK (send_tones_cancelled_cb), data, NULL);
300
send_tones_data_free (SendTonesData *data)
302
g_free (data->tones);
303
g_object_unref (data->result);
304
g_object_unref (data->content);
306
if (data->cancellable != NULL)
308
if (data->cancel_id != 0)
309
g_cancellable_disconnect (data->cancellable, data->cancel_id);
311
g_object_unref (data->cancellable);
314
g_slice_free (SendTonesData, data);
318
send_tones_cancelled_idle_cb (gpointer user_data)
320
SendTonesData *data = user_data;
321
TpCallContent *self = data->content;
323
/* If it is the tone currently being played, stop it. Otherwise wait for its
324
* turn in the queue to preserve order. */
325
if (self->priv->current_tones == data)
327
tp_cli_call_content_interface_dtmf_call_stop_tone (self, -1,
328
NULL, NULL, NULL, NULL);
335
send_tones_cancelled_cb (GCancellable *cancellable,
338
/* Cancel in idle for thread-safeness */
339
g_idle_add (send_tones_cancelled_idle_cb, data);
343
complete_sending_tones (TpCallContent *self,
346
if (self->priv->current_tones == NULL)
351
g_simple_async_result_set_from_error (self->priv->current_tones->result,
355
g_simple_async_result_complete (self->priv->current_tones->result);
357
send_tones_data_free (self->priv->current_tones);
358
self->priv->current_tones = NULL;
360
maybe_send_tones (self);
364
tones_stopped_cb (TpCallContent *self,
367
GObject *weak_object)
371
GError e = { TP_ERRORS, TP_ERROR_CANCELLED,
372
"The DTMF tones were actively cancelled via StopTones" };
373
complete_sending_tones (self, &e);
377
complete_sending_tones (self, NULL);
381
multiple_tones_cb (TpCallContent *self,
384
GObject *weak_object)
387
complete_sending_tones (self, error);
391
maybe_send_tones (TpCallContent *self)
393
if (self->priv->current_tones != NULL)
396
if (g_queue_is_empty (self->priv->tones_queue))
399
self->priv->current_tones = g_queue_pop_head (self->priv->tones_queue);
401
/* Yes this is safe if cancellable is NULL! */
402
if (g_cancellable_is_cancelled (self->priv->current_tones->cancellable))
404
GError e = { TP_ERRORS, TP_ERROR_CANCELLED,
405
"The DTMF tones were cancelled before it has started" };
406
complete_sending_tones (self, &e);
410
DEBUG ("Emitting multiple tones: %s", self->priv->current_tones->tones);
411
tp_cli_call_content_interface_dtmf_call_multiple_tones (self, -1,
412
self->priv->current_tones->tones, multiple_tones_cb, NULL, NULL, NULL);
416
tp_call_content_constructed (GObject *obj)
418
TpCallContent *self = (TpCallContent *) obj;
420
((GObjectClass *) tp_call_content_parent_class)->constructed (obj);
422
/* Connect signals for mutable properties */
423
tp_cli_call_content_connect_to_streams_added (self,
424
streams_added_cb, NULL, NULL, G_OBJECT (self), NULL);
425
tp_cli_call_content_connect_to_streams_removed (self,
426
streams_removed_cb, NULL, NULL, G_OBJECT (self), NULL);
428
tp_cli_dbus_properties_call_get_all (self, -1,
429
TP_IFACE_CALL_CONTENT,
430
got_all_properties_cb, NULL, NULL, G_OBJECT (self));
434
tp_call_content_dispose (GObject *object)
436
TpCallContent *self = (TpCallContent *) object;
438
g_clear_object (&self->priv->connection);
439
tp_clear_pointer (&self->priv->name, g_free);
440
tp_clear_pointer (&self->priv->streams, g_ptr_array_unref);
442
G_OBJECT_CLASS (tp_call_content_parent_class)->dispose (object);
446
tp_call_content_finalize (GObject *object)
448
TpCallContent *self = (TpCallContent *) object;
450
/* Results hold a ref on self, finalize can't happen if queue isn't empty */
451
g_assert (self->priv->current_tones == NULL);
452
g_assert (g_queue_is_empty (self->priv->tones_queue));
453
g_queue_free (self->priv->tones_queue);
455
G_OBJECT_CLASS (tp_call_content_parent_class)->finalize (object);
459
tp_call_content_get_property (GObject *object,
464
TpCallContent *self = (TpCallContent *) object;
468
case PROP_CONNECTION:
469
g_value_set_object (value, self->priv->connection);
472
g_value_set_string (value, self->priv->name);
474
case PROP_MEDIA_TYPE:
475
g_value_set_uint (value, self->priv->media_type);
477
case PROP_DISPOSITION:
478
g_value_set_uint (value, self->priv->disposition);
481
g_value_set_boxed (value, self->priv->streams);
484
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
490
tp_call_content_set_property (GObject *object,
495
TpCallContent *self = (TpCallContent *) object;
499
case PROP_CONNECTION:
500
g_assert (self->priv->connection == NULL); /* construct-only */
501
self->priv->connection = g_value_dup_object (value);
504
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
514
static const TpProxyFeature *
515
tp_call_content_list_features (TpProxyClass *cls G_GNUC_UNUSED)
517
static TpProxyFeature features[N_FEAT + 1] = { { 0 } };
519
if (G_LIKELY (features[0].name != 0))
522
/* started from constructed */
523
features[FEAT_CORE].name = TP_CALL_CONTENT_FEATURE_CORE;
524
features[FEAT_CORE].core = TRUE;
526
/* assert that the terminator at the end is there */
527
g_assert (features[N_FEAT].name == 0);
533
tp_call_content_class_init (TpCallContentClass *klass)
535
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
536
TpProxyClass *proxy_class = (TpProxyClass *) klass;
537
GParamSpec *param_spec;
539
gobject_class->constructed = tp_call_content_constructed;
540
gobject_class->get_property = tp_call_content_get_property;
541
gobject_class->set_property = tp_call_content_set_property;
542
gobject_class->dispose = tp_call_content_dispose;
543
gobject_class->finalize = tp_call_content_finalize;
545
proxy_class->list_features = tp_call_content_list_features;
546
proxy_class->interface = TP_IFACE_QUARK_CALL_CONTENT;
548
g_type_class_add_private (gobject_class, sizeof (TpCallContentPrivate));
549
tp_call_content_init_known_interfaces ();
552
* TpCallContent:connection:
554
* The #TpConnection of the call.
558
param_spec = g_param_spec_object ("connection", "Connection",
559
"The connection of this content",
561
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
562
g_object_class_install_property (gobject_class, PROP_CONNECTION,
566
* TpCallContent:name:
568
* The name of this content.
572
param_spec = g_param_spec_string ("name", "Name",
573
"The name of this content, if any",
575
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
576
g_object_class_install_property (gobject_class, PROP_NAME, param_spec);
579
* TpCallContent:media-type:
581
* The media type of this content, from #TpMediaStreamType.
585
param_spec = g_param_spec_uint ("media-type", "Media type",
586
"The media type of this content",
588
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
589
g_object_class_install_property (gobject_class, PROP_MEDIA_TYPE, param_spec);
592
* TpCallContent:disposition:
594
* The disposition of this content, from #TpCallContentDisposition.
598
param_spec = g_param_spec_uint ("disposition", "Disposition",
599
"The disposition of this content",
601
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
602
g_object_class_install_property (gobject_class, PROP_DISPOSITION, param_spec);
604
/* FIXME: Should be annoted with
606
* Type: GLib.PtrArray<TelepathyGLib.CallStream>
607
* Transfer: container
609
* But it does not work (bgo#663846) and makes gtkdoc fail myserably.
613
* TpCallContent:streams:
615
* #GPtrArray of #TpCallStream objects. The list of stream objects that are
616
* part of this content.
618
* It is NOT guaranteed that %TP_CALL_STREAM_FEATURE_CORE is prepared on
623
param_spec = g_param_spec_boxed ("streams", "Stream",
624
"The streams of this content",
626
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
627
g_object_class_install_property (gobject_class, PROP_STREAMS,
631
* TpCallContent::removed
632
* @self: the #TpCallContent
634
* The ::removed signal is emitted when @self is removed from
639
_signals[REMOVED] = g_signal_new ("removed",
640
G_OBJECT_CLASS_TYPE (klass),
643
g_cclosure_marshal_VOID__VOID,
648
* TpCallContent::streams-added
649
* @self: the #TpCallContent
650
* @streams: (type GLib.PtrArray) (element-type TelepathyGLib.CallStream):
651
* a #GPtrArray of newly added #TpCallStream
653
* The ::streams-added signal is emitted whenever
654
* #TpCallStream are added to @self.
656
* It is NOT guaranteed that %TP_CALL_STREAM_FEATURE_CORE is prepared on
661
_signals[STREAMS_ADDED] = g_signal_new ("streams-added",
662
G_OBJECT_CLASS_TYPE (klass),
665
g_cclosure_marshal_VOID__BOXED,
667
1, G_TYPE_PTR_ARRAY);
670
* TpCallContent::streams-removed
671
* @self: the #TpCallContent
672
* @streams: (type GLib.PtrArray) (element-type TelepathyGLib.CallStream):
673
* a #GPtrArray of newly removed #TpCallStream
674
* @reason: a #TpCallStateReason
676
* The ::streams-removed signal is emitted whenever
677
* #TpCallStreams are removed from @self.
679
* It is NOT guaranteed that %TP_CALL_STREAM_FEATURE_CORE is prepared on
684
_signals[STREAMS_REMOVED] = g_signal_new ("streams-removed",
685
G_OBJECT_CLASS_TYPE (klass),
688
_tp_marshal_VOID__BOXED_BOXED,
690
2, G_TYPE_PTR_ARRAY, TP_TYPE_CALL_STATE_REASON);
695
tp_call_content_init (TpCallContent *self)
697
self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), TP_TYPE_CALL_CONTENT,
698
TpCallContentPrivate);
700
self->priv->streams = g_ptr_array_new_with_free_func (g_object_unref);
701
self->priv->tones_queue = g_queue_new ();
705
* tp_call_content_init_known_interfaces:
707
* Ensure that the known interfaces for #TpCallContent have been set up.
708
* This is done automatically when necessary, but for correct
709
* overriding of library interfaces by local extensions, you should
710
* call this function before calling
711
* tp_proxy_or_subclass_hook_on_interface_add() with first argument
712
* %TP_TYPE_CALL_CONTENT.
717
tp_call_content_init_known_interfaces (void)
719
static gsize once = 0;
721
if (g_once_init_enter (&once))
723
GType tp_type = TP_TYPE_CALL_CONTENT;
725
tp_proxy_init_known_interfaces ();
726
tp_proxy_or_subclass_hook_on_interface_add (tp_type,
727
tp_cli_call_content_add_signals);
728
tp_proxy_subclass_add_error_mapping (tp_type,
729
TP_ERROR_PREFIX, TP_ERRORS, TP_TYPE_ERROR);
731
g_once_init_leave (&once, 1);
736
* TP_CALL_CONTENT_FEATURE_CORE:
738
* Expands to a call to a function that returns a quark for the "core"
739
* feature on a #TpCallContent.
741
* One can ask for a feature to be prepared using the tp_proxy_prepare_async()
742
* function, and waiting for it to trigger the callback.
745
tp_call_content_get_feature_quark_core (void)
747
return g_quark_from_static_string ("tp-call-content-feature-core");
751
* tp_call_content_get_name:
752
* @self: a #TpCallContent
756
* Returns: the value of #TpCallContent:name
760
tp_call_content_get_name (TpCallContent *self)
762
g_return_val_if_fail (TP_IS_CALL_CONTENT (self), NULL);
764
return self->priv->name;
768
* tp_call_content_get_media_type:
769
* @self: a #TpCallContent
773
* Returns: the value of #TpCallContent:name
777
tp_call_content_get_media_type (TpCallContent *self)
779
g_return_val_if_fail (TP_IS_CALL_CONTENT (self), 0);
781
return self->priv->media_type;
785
* tp_call_content_get_disposition:
786
* @self: a #TpCallContent
790
* Returns: the value of #TpCallContent:disposition
793
TpCallContentDisposition
794
tp_call_content_get_disposition (TpCallContent *self)
796
g_return_val_if_fail (TP_IS_CALL_CONTENT (self), 0);
798
return self->priv->disposition;
802
* tp_call_content_get_streams:
803
* @self: a #TpCallContent
807
* Returns: (transfer none) (type GLib.PtrArray) (element-type TelepathyGLib.CallStream):
808
* the value of #TpCallContent:streams
812
tp_call_content_get_streams (TpCallContent *self)
814
g_return_val_if_fail (TP_IS_CALL_CONTENT (self), NULL);
816
return self->priv->streams;
820
generic_async_cb (TpCallContent *self,
823
GObject *weak_object)
825
GSimpleAsyncResult *result = user_data;
829
DEBUG ("Error: %s", error->message);
830
g_simple_async_result_set_from_error (result, error);
833
g_simple_async_result_complete (result);
837
* tp_call_content_remove_async:
838
* @self: a #TpCallContent
839
* @callback: a callback to call when the operation finishes
840
* @user_data: data to pass to @callback
842
* Remove the content from the call. This will cause #TpCallContent::removed
848
tp_call_content_remove_async (TpCallContent *self,
849
GAsyncReadyCallback callback,
852
GSimpleAsyncResult *result;
854
g_return_if_fail (TP_IS_CALL_CONTENT (self));
856
result = g_simple_async_result_new (G_OBJECT (self), callback,
857
user_data, tp_call_content_remove_async);
859
tp_cli_call_content_call_remove (self, -1,
860
generic_async_cb, result, g_object_unref, G_OBJECT (self));
864
* tp_call_content_remove_finish:
865
* @self: a #TpCallContent
866
* @result: a #GAsyncResult
867
* @error: a #GError to fill
869
* Finishes tp_call_content_remove_async().
874
tp_call_content_remove_finish (TpCallContent *self,
875
GAsyncResult *result,
878
_tp_implement_finish_void (self, tp_call_content_remove_async);
882
* tp_call_content_send_tones_async:
883
* @self: a #TpCallContent
884
* @tones: a string representation of one or more DTMF events.
885
* @cancellable: optional #GCancellable object, %NULL to ignore
886
* @callback: a callback to call when the operation finishes
887
* @user_data: data to pass to @callback
889
* Send @tones DTMF code on @self content. @self must have the
890
* %TP_IFACE_CALL_CONTENT_INTERFACE_DTMF interface.
892
* If DTMF tones are already being played, this request is queued.
897
tp_call_content_send_tones_async (TpCallContent *self,
899
GCancellable *cancellable,
900
GAsyncReadyCallback callback,
903
GSimpleAsyncResult *result;
906
g_return_if_fail (TP_IS_CALL_CONTENT (self));
908
if (!tp_proxy_has_interface_by_id (self,
909
TP_IFACE_QUARK_CALL_CONTENT_INTERFACE_DTMF))
911
g_simple_async_report_error_in_idle (G_OBJECT (self),
912
callback, user_data, TP_ERRORS, TP_ERROR_NOT_CAPABLE,
913
"Content does not support DTMF");
917
result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
918
tp_call_content_send_tones_async);
920
data = send_tones_data_new (self, tones, result, cancellable);
921
g_queue_push_tail (self->priv->tones_queue, data);
923
maybe_send_tones (self);
925
g_object_unref (result);
929
* tp_call_content_send_tones_finish:
930
* @self: a #TpCallContent
931
* @result: a #GAsyncResult
932
* @error: a #GError to fill
934
* Finishes tp_call_content_send_tones_async().
936
* Returns: %TRUE on success, %FALSE otherwise.
940
tp_call_content_send_tones_finish (TpCallContent *self,
941
GAsyncResult *result,
944
_tp_implement_finish_void (self, tp_call_content_send_tones_async);