1 /*
2 * call-channel.h - high level API for Call channels
3 *
4 * Copyright (C) 2011 Collabora Ltd. <http://www.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 * SECTION:call-channel
23 * @title: TpCallChannel
24 * @short_description: proxy object for a call channel
25 *
26 * #TpCallChannel is a sub-class of #TpChannel providing convenient API
27 * to make calls
28 */
29
30 /**
31 * TpCallChannel:
32 *
33 * Data structure representing a #TpCallChannel.
34 *
35 * Since: 0.17.5
36 */
37
38 /**
39 * TpCallChannelClass:
40 *
41 * The class of a #TpCallChannel.
42 *
43 * Since: 0.17.5
44 */
45
46 #include "config.h"
47
48 #include "telepathy-glib/call-channel.h"
49
50 #include <config.h>
51
52 #include <telepathy-glib/call-content.h>
53 #include <telepathy-glib/call-misc.h>
54 #include <telepathy-glib/call-stream.h>
55 #include <telepathy-glib/dbus.h>
56 #include <telepathy-glib/gtypes.h>
57 #include <telepathy-glib/interfaces.h>
58 #include <telepathy-glib/util.h>
59
60 #define DEBUG_FLAG TP_DEBUG_CALL
61 #include "telepathy-glib/automatic-client-factory-internal.h"
62 #include "telepathy-glib/call-internal.h"
63 #include "telepathy-glib/channel-internal.h"
64 #include "telepathy-glib/connection-internal.h"
65 #include "telepathy-glib/debug-internal.h"
66 #include "telepathy-glib/proxy-internal.h"
67 #include "telepathy-glib/util-internal.h"
68
69 G_DEFINE_TYPE (TpCallChannel, tp_call_channel, TP_TYPE_CHANNEL)
70
71 struct _TpCallChannelPrivate
72 {
73 /* Array of TpCallContents */
74 GPtrArray *contents;
75 TpCallState state;
76 TpCallFlags flags;
77 GHashTable *state_details;
78 TpCallStateReason *state_reason;
79 gboolean hardware_streaming;
80 /* TpContact -> TpCallMemberFlags */
81 GHashTable *members;
82 gboolean initial_audio;
83 gboolean initial_video;
84 gchar *initial_audio_name;
85 gchar *initial_video_name;
86 gboolean mutable_contents;
87 TpLocalHoldState hold_state;
88 TpLocalHoldStateReason hold_state_reason;
89
90 GSimpleAsyncResult *core_result;
91 gboolean properties_retrieved;
92 gboolean initial_members_retrieved;
93 gboolean hold_state_retrieved;
94 };
95
96 enum /* props */
97 {
98 PROP_CONTENTS = 1,
99 PROP_STATE,
100 PROP_FLAGS,
101 PROP_STATE_DETAILS,
102 PROP_STATE_REASON,
103 PROP_HARDWARE_STREAMING,
104 PROP_INITIAL_AUDIO,
105 PROP_INITIAL_VIDEO,
106 PROP_INITIAL_AUDIO_NAME,
107 PROP_INITIAL_VIDEO_NAME,
108 PROP_MUTABLE_CONTENTS,
109 PROP_HOLD_STATE,
110 PROP_HOLD_STATE_REASON,
111 };
112
113 enum /* signals */
114 {
115 CONTENT_ADDED,
116 CONTENT_REMOVED,
117 STATE_CHANGED,
118 MEMBERS_CHANGED,
119 LAST_SIGNAL
120 };
121
122 static guint _signals[LAST_SIGNAL] = { 0, };
123
124 static TpCallContent *
_tp_call_content_new(TpCallChannel * self,const gchar * object_path)125 _tp_call_content_new (TpCallChannel *self,
126 const gchar *object_path)
127 {
128 return g_object_new (TP_TYPE_CALL_CONTENT,
129 "bus-name", tp_proxy_get_bus_name (self),
130 "dbus-daemon", tp_proxy_get_dbus_daemon (self),
131 "dbus-connection", tp_proxy_get_dbus_connection (self),
132 "object-path", object_path,
133 "connection", tp_channel_get_connection ((TpChannel *) self),
134 "channel", self,
135 "factory", tp_proxy_get_factory (self),
136 NULL);
137 }
138
139 /**
140 * TpCallStateReason:
141 * @actor: the contact responsible for the change, or 0 if no contact was
142 * responsible
143 * @reason: the reason for the change. If
144 * #TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED then the @actor member will
145 * dictate whether it was the local user or a remote contact responsible
146 * @dbus_reason: A specific reason for the change, which may be a D-Bus error in
147 * the Telepathy namespace, a D-Bus error in any other namespace
148 * (for implementation-specific errors), or the empty string to indicate that
149 * the state change was not an error
150 * @message: A developer readable debug message giving the reason for the state
151 * change.
152 *
153 * Data structure representing the reason for a call state change.
154 *
155 * Since: 0.17.5
156 */
157
158 static TpCallStateReason *
_tp_call_state_reason_new_full(TpHandle actor,TpCallStateChangeReason reason,const gchar * dbus_reason,const gchar * message)159 _tp_call_state_reason_new_full (TpHandle actor,
160 TpCallStateChangeReason reason,
161 const gchar *dbus_reason,
162 const gchar *message)
163 {
164 TpCallStateReason *r;
165
166 r = g_slice_new0 (TpCallStateReason);
167 r->actor = actor;
168 r->reason = reason;
169 r->dbus_reason = g_strdup (dbus_reason);
170 r->message = g_strdup (message);
171 r->ref_count = 1;
172
173 return r;
174 }
175
176 TpCallStateReason *
_tp_call_state_reason_new(const GValueArray * value_array)177 _tp_call_state_reason_new (const GValueArray *value_array)
178 {
179 TpHandle handle;
180 TpCallStateChangeReason reason;
181 const gchar *dbus_reason;
182 const gchar *message;
183
184 tp_value_array_unpack ((GValueArray *) value_array, 4,
185 &handle,
186 &reason,
187 &dbus_reason,
188 &message);
189
190 return _tp_call_state_reason_new_full (handle, reason, dbus_reason, message);
191 }
192
193 TpCallStateReason *
_tp_call_state_reason_ref(TpCallStateReason * r)194 _tp_call_state_reason_ref (TpCallStateReason *r)
195 {
196 g_atomic_int_inc (&r->ref_count);
197 return r;
198 }
199
200 void
_tp_call_state_reason_unref(TpCallStateReason * r)201 _tp_call_state_reason_unref (TpCallStateReason *r)
202 {
203 g_return_if_fail (r != NULL);
204
205 if (g_atomic_int_dec_and_test (&r->ref_count))
206 {
207 g_free (r->dbus_reason);
208 g_free (r->message);
209 g_slice_free (TpCallStateReason, r);
210 }
211 }
212
213 G_DEFINE_BOXED_TYPE (TpCallStateReason, tp_call_state_reason,
214 _tp_call_state_reason_ref, _tp_call_state_reason_unref);
215
216 /* Convert GHashTable<TpHandle, anything> to GHashTable<TpContact, anything>.
217 * Assuming value does not need to be copied */
218 GHashTable *
_tp_call_members_convert_table(TpConnection * connection,GHashTable * table,GHashTable * identifiers)219 _tp_call_members_convert_table (TpConnection *connection,
220 GHashTable *table,
221 GHashTable *identifiers)
222 {
223 GHashTable *result;
224 GHashTableIter iter;
225 gpointer key, value;
226
227 result = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
228
229 g_hash_table_iter_init (&iter, table);
230 while (g_hash_table_iter_next (&iter, &key, &value))
231 {
232 TpHandle handle = GPOINTER_TO_UINT (key);
233 const gchar *id;
234 TpContact *contact;
235
236 id = g_hash_table_lookup (identifiers, key);
237 if (id == NULL)
238 {
239 DEBUG ("Missing identifier for member %u - broken CM", handle);
240 continue;
241 }
242
243 contact = tp_connection_dup_contact_if_possible (connection, handle, id);
244 if (contact == NULL)
245 {
246 DEBUG ("Can't create contact for (%u, %s) pair - CM does not have "
247 "immutable handles?", handle, id);
248 continue;
249 }
250
251 g_hash_table_insert (result, contact, value);
252 }
253
254 return result;
255 }
256
257 /* Convert GArray<TpHandle> to GPtrArray<TpContact>.
258 * Assuming the TpContact already exists. */
259 GPtrArray *
_tp_call_members_convert_array(TpConnection * connection,const GArray * array)260 _tp_call_members_convert_array (TpConnection *connection,
261 const GArray *array)
262 {
263 GPtrArray *result;
264 guint i;
265
266 result = g_ptr_array_new_full (array->len, g_object_unref);
267
268 for (i = 0; i < array->len; i++)
269 {
270 TpHandle handle = g_array_index (array, TpHandle, i);
271 TpContact *contact;
272
273 /* The contact is supposed to already exists */
274 contact = tp_connection_dup_contact_if_possible (connection,
275 handle, NULL);
276 if (contact == NULL)
277 {
278 DEBUG ("No TpContact found for handle %u", handle);
279 continue;
280 }
281
282 g_ptr_array_add (result, contact);
283 }
284
285 return result;
286 }
287
288 static TpCallContent *
ensure_content(TpCallChannel * self,const gchar * object_path)289 ensure_content (TpCallChannel *self,
290 const gchar *object_path)
291 {
292 TpCallContent *content;
293 guint i;
294
295 for (i = 0; i < self->priv->contents->len; i++)
296 {
297 content = g_ptr_array_index (self->priv->contents, i);
298
299 if (!tp_strdiff (tp_proxy_get_object_path (content), object_path))
300 return content;
301 }
302
303 DEBUG ("Content added: %s", object_path);
304
305 content = _tp_call_content_new (self, object_path);
306 g_ptr_array_add (self->priv->contents, content);
307 g_signal_emit (self, _signals[CONTENT_ADDED], 0, content);
308
309 return content;
310 }
311
312 static void
content_added_cb(TpChannel * channel,const gchar * object_path,gpointer user_data,GObject * weak_object)313 content_added_cb (TpChannel *channel,
314 const gchar *object_path,
315 gpointer user_data,
316 GObject *weak_object)
317 {
318 TpCallChannel *self = (TpCallChannel *) channel;
319
320 if (!self->priv->properties_retrieved)
321 return;
322
323 ensure_content (self, object_path);
324 }
325
326 static void
content_removed_cb(TpChannel * channel,const gchar * object_path,const GValueArray * reason,gpointer user_data,GObject * weak_object)327 content_removed_cb (TpChannel *channel,
328 const gchar *object_path,
329 const GValueArray *reason,
330 gpointer user_data,
331 GObject *weak_object)
332 {
333 TpCallChannel *self = (TpCallChannel *) channel;
334 guint i;
335
336 if (!self->priv->properties_retrieved)
337 return;
338
339 for (i = 0; i < self->priv->contents->len; i++)
340 {
341 TpCallContent *content = g_ptr_array_index (self->priv->contents, i);
342
343 if (!tp_strdiff (tp_proxy_get_object_path (content), object_path))
344 {
345 TpCallStateReason *r;
346
347 DEBUG ("Content removed: %s", object_path);
348
349 r = _tp_call_state_reason_new (reason);
350
351 g_object_ref (content);
352 g_ptr_array_remove_index_fast (self->priv->contents, i);
353 g_signal_emit (self, _signals[CONTENT_REMOVED], 0, content, r);
354 g_signal_emit_by_name (content, "removed");
355 g_object_unref (content);
356
357 _tp_call_state_reason_unref (r);
358
359 return;
360 }
361 }
362
363 DEBUG ("Content '%s' removed but not found", object_path);
364 }
365
366 static const gchar *
call_state_to_string(TpCallState state)367 call_state_to_string (TpCallState state)
368 {
369 switch (state)
370 {
371 case TP_CALL_STATE_UNKNOWN:
372 return "unknown";
373 case TP_CALL_STATE_PENDING_INITIATOR:
374 return "pending-initiator";
375 case TP_CALL_STATE_INITIALISING:
376 return "initialising";
377 case TP_CALL_STATE_INITIALISED:
378 return "initialised";
379 case TP_CALL_STATE_ACCEPTED:
380 return "accepted";
381 case TP_CALL_STATE_ACTIVE:
382 return "active";
383 case TP_CALL_STATE_ENDED:
384 return "ended";
385 }
386 return "invalid";
387 }
388
389 static void
call_state_changed_cb(TpChannel * channel,guint state,guint flags,const GValueArray * reason,GHashTable * details,gpointer user_data,GObject * weak_object)390 call_state_changed_cb (TpChannel *channel,
391 guint state,
392 guint flags,
393 const GValueArray *reason,
394 GHashTable *details,
395 gpointer user_data,
396 GObject *weak_object)
397 {
398 TpCallChannel *self = (TpCallChannel *) channel;
399
400 if (!self->priv->properties_retrieved)
401 return;
402
403 DEBUG ("Call state changed to %s (flags: %u)", call_state_to_string (state),
404 flags);
405
406 tp_clear_pointer (&self->priv->state_reason, _tp_call_state_reason_unref);
407 tp_clear_pointer (&self->priv->state_details, g_hash_table_unref);
408
409 self->priv->state = state;
410 self->priv->flags = flags;
411 self->priv->state_reason = _tp_call_state_reason_new (reason);
412 self->priv->state_details = g_hash_table_ref (details);
413
414 g_object_notify ((GObject *) self, "state");
415 g_object_notify ((GObject *) self, "flags");
416 g_object_notify ((GObject *) self, "state-reason");
417 g_object_notify ((GObject *) self, "state-details");
418
419 g_signal_emit (self, _signals[STATE_CHANGED], 0, self->priv->state,
420 self->priv->flags, self->priv->state_reason, self->priv->state_details);
421 }
422
423 typedef struct
424 {
425 TpCallChannel *self;
426 GHashTable *updates;
427 GPtrArray *removed;
428 TpCallStateReason *reason;
429 } UpdateCallMembersData;
430
431
432 static void
channel_maybe_core_prepared(TpCallChannel * self)433 channel_maybe_core_prepared (TpCallChannel *self)
434 {
435 if (self->priv->core_result == NULL)
436 return;
437
438 if (self->priv->initial_members_retrieved &&
439 self->priv->properties_retrieved &&
440 self->priv->hold_state_retrieved)
441 {
442 g_simple_async_result_complete (self->priv->core_result);
443 g_clear_object (&self->priv->core_result);
444 }
445 }
446
447 static void
update_call_members_prepared_cb(GObject * object,GAsyncResult * result,gpointer user_data)448 update_call_members_prepared_cb (GObject *object,
449 GAsyncResult *result,
450 gpointer user_data)
451 {
452 UpdateCallMembersData *data = user_data;
453 TpCallChannel *self = data->self;
454 GError *error = NULL;
455
456 if (!_tp_channel_contacts_queue_prepare_finish ((TpChannel *) self,
457 result, NULL, &error))
458 {
459 DEBUG ("Error preparing call members: %s", error->message);
460 g_clear_error (&error);
461 }
462
463 tp_g_hash_table_update (self->priv->members, data->updates,
464 g_object_ref, NULL);
465
466 if (data->removed != NULL)
467 {
468 guint i;
469
470 for (i = 0; i < data->removed->len; i++)
471 {
472 g_hash_table_remove (self->priv->members,
473 g_ptr_array_index (data->removed, i));
474 }
475 }
476
477 if (!self->priv->initial_members_retrieved)
478 {
479 self->priv->initial_members_retrieved = TRUE;
480
481 channel_maybe_core_prepared (self);
482 }
483 else
484 {
485 g_signal_emit (self, _signals[MEMBERS_CHANGED], 0,
486 data->updates, data->removed, data->reason);
487 }
488
489 tp_clear_pointer (&data->updates, g_hash_table_unref);
490 tp_clear_pointer (&data->removed, g_ptr_array_unref);
491 tp_clear_pointer (&data->reason, _tp_call_state_reason_unref);
492 g_slice_free (UpdateCallMembersData, data);
493 }
494
495 static void
update_call_members(TpCallChannel * self,GHashTable * updates,GPtrArray * removed,TpCallStateReason * reason)496 update_call_members (TpCallChannel *self,
497 GHashTable *updates,
498 GPtrArray *removed,
499 TpCallStateReason *reason)
500 {
501 GHashTableIter iter;
502 gpointer key, value;
503 GPtrArray *contacts;
504 UpdateCallMembersData *data;
505
506 /* We want to expose only prepared contacts. Collect TpContact objects and
507 * prepared them in the channel's queue to make sure it does not reorder
508 * events.
509 * Applications where delay to display the call is critical shouldn't set
510 * contact features on the factory, in which case this becomes no-op.
511 */
512
513 contacts = g_ptr_array_new_full (g_hash_table_size (updates),
514 g_object_unref);
515
516 g_hash_table_iter_init (&iter, updates);
517 while (g_hash_table_iter_next (&iter, &key, &value))
518 g_ptr_array_add (contacts, g_object_ref (key));
519
520 data = g_slice_new0 (UpdateCallMembersData);
521 data->self = self;
522 data->updates = g_hash_table_ref (updates);
523 data->removed = removed != NULL ? g_ptr_array_ref (removed) : NULL;
524 data->reason = reason != NULL ? _tp_call_state_reason_ref (reason) : NULL;
525
526 _tp_channel_contacts_queue_prepare_async ((TpChannel *) self,
527 contacts, update_call_members_prepared_cb, data);
528 }
529
530 static void
call_members_changed_cb(TpChannel * channel,GHashTable * updates,GHashTable * identifiers,const GArray * removed,const GValueArray * reason,gpointer user_data,GObject * weak_object)531 call_members_changed_cb (TpChannel *channel,
532 GHashTable *updates,
533 GHashTable *identifiers,
534 const GArray *removed,
535 const GValueArray *reason,
536 gpointer user_data,
537 GObject *weak_object)
538 {
539 TpCallChannel *self = (TpCallChannel *) channel;
540 TpConnection *connection;
541 GHashTable *updates_contacts;
542 GPtrArray *removed_contacts;
543 TpCallStateReason *r;
544
545 DEBUG ("Call members: %d changed, %d removed",
546 g_hash_table_size (updates), removed->len);
547
548 connection = tp_channel_get_connection (channel);
549 updates_contacts = _tp_call_members_convert_table (connection,
550 updates, identifiers);
551 removed_contacts = _tp_call_members_convert_array (connection,
552 removed);
553 r = _tp_call_state_reason_new (reason);
554
555 update_call_members (self, updates_contacts, removed_contacts, r);
556
557 g_hash_table_unref (updates_contacts);
558 g_ptr_array_unref (removed_contacts);
559 _tp_call_state_reason_unref (r);
560 }
561
562 static void
got_all_properties_cb(TpProxy * proxy,GHashTable * properties,const GError * error,gpointer user_data,GObject * weak_object)563 got_all_properties_cb (TpProxy *proxy,
564 GHashTable *properties,
565 const GError *error,
566 gpointer user_data,
567 GObject *weak_object)
568 {
569 TpCallChannel *self = (TpCallChannel *) proxy;
570 TpConnection *connection;
571 GPtrArray *contents;
572 GHashTable *members;
573 GHashTable *identifiers;
574 GHashTable *contacts;
575 guint i;
576
577 if (error != NULL)
578 {
579 DEBUG ("Could not get the call channel properties: %s", error->message);
580 g_simple_async_result_set_from_error (self->priv->core_result, error);
581 g_simple_async_result_complete (self->priv->core_result);
582 g_clear_object (&self->priv->core_result);
583 return;
584 }
585
586 connection = tp_channel_get_connection ((TpChannel *) self);
587 g_assert (tp_connection_has_immortal_handles (connection));
588
589 self->priv->properties_retrieved = TRUE;
590
591 contents = tp_asv_get_boxed (properties,
592 "Contents", TP_ARRAY_TYPE_OBJECT_PATH_LIST);
593 self->priv->state = tp_asv_get_uint32 (properties,
594 "CallState", NULL);
595 self->priv->flags = tp_asv_get_uint32 (properties,
596 "CallFlags", NULL);
597 self->priv->state_details = g_hash_table_ref (tp_asv_get_boxed (properties,
598 "CallStateDetails", TP_HASH_TYPE_STRING_VARIANT_MAP));
599 self->priv->state_reason = _tp_call_state_reason_new (tp_asv_get_boxed (properties,
600 "CallStateReason", TP_STRUCT_TYPE_CALL_STATE_REASON));
601 members = tp_asv_get_boxed (properties,
602 "CallMembers", TP_HASH_TYPE_CALL_MEMBER_MAP);
603 identifiers = tp_asv_get_boxed (properties,
604 "MemberIdentifiers", TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP);
605
606 contacts = _tp_call_members_convert_table (connection, members, identifiers);
607 update_call_members (self, contacts, NULL, NULL);
608 g_hash_table_unref (contacts);
609
610 for (i = 0; i < contents->len; i++)
611 {
612 const gchar *object_path = g_ptr_array_index (contents, i);
613
614 DEBUG ("Initial content added: %s", object_path);
615
616 g_ptr_array_add (self->priv->contents,
617 _tp_call_content_new (self, object_path));
618 }
619
620 /* core_result will be complete in update_call_members_prepared_cb() when
621 * the initial members are prepared or when the hold state is retrived. */
622 }
623
624 static void
hold_state_changed_cb(TpChannel * proxy,guint arg_HoldState,guint arg_Reason,gpointer user_data,GObject * weak_object)625 hold_state_changed_cb (TpChannel *proxy,
626 guint arg_HoldState,
627 guint arg_Reason,
628 gpointer user_data, GObject *weak_object)
629 {
630 TpCallChannel *self = TP_CALL_CHANNEL (proxy);
631
632 if (!self->priv->hold_state_retrieved)
633 return;
634
635 self->priv->hold_state = arg_HoldState;
636 self->priv->hold_state_reason = arg_Reason;
637
638 g_object_notify (G_OBJECT (proxy), "hold-state");
639 g_object_notify (G_OBJECT (proxy), "hold-state-reason");
640 }
641
642 static void
got_hold_state_cb(TpChannel * proxy,guint arg_HoldState,guint arg_Reason,const GError * error,gpointer user_data,GObject * weak_object)643 got_hold_state_cb (TpChannel *proxy, guint arg_HoldState, guint arg_Reason,
644 const GError *error, gpointer user_data, GObject *weak_object)
645 {
646 TpCallChannel *self = TP_CALL_CHANNEL (proxy);
647
648 if (error != NULL)
649 {
650 DEBUG ("Could not get the call channel hold state: %s", error->message);
651 g_simple_async_result_set_from_error (self->priv->core_result, error);
652 g_simple_async_result_complete (self->priv->core_result);
653 g_clear_object (&self->priv->core_result);
654 return;
655 }
656
657 self->priv->hold_state = arg_HoldState;
658 self->priv->hold_state_reason = arg_Reason;
659 self->priv->hold_state_retrieved = TRUE;
660
661 channel_maybe_core_prepared (self);
662 }
663
664 static void
_tp_call_channel_prepare_core_async(TpProxy * proxy,const TpProxyFeature * feature,GAsyncReadyCallback callback,gpointer user_data)665 _tp_call_channel_prepare_core_async (TpProxy *proxy,
666 const TpProxyFeature *feature,
667 GAsyncReadyCallback callback,
668 gpointer user_data)
669 {
670 TpCallChannel *self = (TpCallChannel *) proxy;
671 TpChannel *channel = (TpChannel *) self;
672
673 tp_cli_channel_type_call_connect_to_content_added (channel,
674 content_added_cb, NULL, NULL, NULL, NULL);
675 tp_cli_channel_type_call_connect_to_content_removed (channel,
676 content_removed_cb, NULL, NULL, NULL, NULL);
677 tp_cli_channel_type_call_connect_to_call_state_changed (channel,
678 call_state_changed_cb, NULL, NULL, NULL, NULL);
679 tp_cli_channel_type_call_connect_to_call_members_changed (channel,
680 call_members_changed_cb, NULL, NULL, NULL, NULL);
681
682 g_assert (self->priv->core_result == NULL);
683 self->priv->core_result = g_simple_async_result_new ((GObject *) self,
684 callback, user_data, _tp_call_channel_prepare_core_async);
685
686 tp_cli_dbus_properties_call_get_all (self, -1,
687 TP_IFACE_CHANNEL_TYPE_CALL,
688 got_all_properties_cb, NULL, NULL, NULL);
689
690 if (tp_proxy_has_interface_by_id (proxy,
691 TP_IFACE_QUARK_CHANNEL_INTERFACE_HOLD))
692 {
693 tp_cli_channel_interface_hold_connect_to_hold_state_changed (channel,
694 hold_state_changed_cb, NULL, NULL, NULL, NULL);
695
696 tp_cli_channel_interface_hold_call_get_hold_state (channel, -1,
697 got_hold_state_cb, NULL, NULL, NULL);
698 }
699 }
700
701 static void
tp_call_channel_constructed(GObject * obj)702 tp_call_channel_constructed (GObject *obj)
703 {
704 TpCallChannel *self = (TpCallChannel *) obj;
705 GHashTable *properties = _tp_channel_get_immutable_properties (
706 (TpChannel *) self);
707
708 G_OBJECT_CLASS (tp_call_channel_parent_class)->constructed (obj);
709
710 /* We can already set immutable properties */
711 self->priv->hardware_streaming = tp_asv_get_boolean (properties,
712 TP_PROP_CHANNEL_TYPE_CALL_HARDWARE_STREAMING, NULL);
713 self->priv->initial_audio = tp_asv_get_boolean (properties,
714 TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, NULL);
715 self->priv->initial_video = tp_asv_get_boolean (properties,
716 TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, NULL);
717 self->priv->initial_audio_name = g_strdup (tp_asv_get_string (properties,
718 TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO_NAME));
719 self->priv->initial_video_name = g_strdup (tp_asv_get_string (properties,
720 TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO_NAME));
721 self->priv->mutable_contents = tp_asv_get_boolean (properties,
722 TP_PROP_CHANNEL_TYPE_CALL_MUTABLE_CONTENTS, NULL);
723
724 if (!self->priv->initial_audio)
725 tp_clear_pointer (&self->priv->initial_audio_name, g_free);
726 if (!self->priv->initial_video)
727 tp_clear_pointer (&self->priv->initial_video_name, g_free);
728 }
729
730 static void
tp_call_channel_dispose(GObject * obj)731 tp_call_channel_dispose (GObject *obj)
732 {
733 TpCallChannel *self = (TpCallChannel *) obj;
734
735 g_assert (self->priv->core_result == NULL);
736
737 tp_clear_pointer (&self->priv->contents, g_ptr_array_unref);
738 tp_clear_pointer (&self->priv->state_details, g_hash_table_unref);
739 tp_clear_pointer (&self->priv->state_reason, _tp_call_state_reason_unref);
740 tp_clear_pointer (&self->priv->members, g_hash_table_unref);
741 tp_clear_pointer (&self->priv->initial_audio_name, g_free);
742 tp_clear_pointer (&self->priv->initial_video_name, g_free);
743
744 G_OBJECT_CLASS (tp_call_channel_parent_class)->dispose (obj);
745 }
746
747 static void
tp_call_channel_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)748 tp_call_channel_get_property (GObject *object,
749 guint property_id,
750 GValue *value,
751 GParamSpec *pspec)
752 {
753 TpCallChannel *self = (TpCallChannel *) object;
754
755 switch (property_id)
756 {
757 case PROP_CONTENTS:
758 g_value_set_boxed (value, self->priv->contents);
759 break;
760
761 case PROP_STATE:
762 g_value_set_uint (value, self->priv->state);
763 break;
764
765 case PROP_FLAGS:
766 g_value_set_uint (value, self->priv->flags);
767 break;
768
769 case PROP_STATE_DETAILS:
770 g_value_set_boxed (value, self->priv->state_details);
771 break;
772
773 case PROP_STATE_REASON:
774 g_value_set_boxed (value, self->priv->state_reason);
775 break;
776
777 case PROP_HARDWARE_STREAMING:
778 g_value_set_boolean (value, self->priv->hardware_streaming);
779 break;
780
781 case PROP_INITIAL_AUDIO:
782 g_value_set_boolean (value, self->priv->initial_audio);
783 break;
784
785 case PROP_INITIAL_VIDEO:
786 g_value_set_boolean (value, self->priv->initial_video);
787 break;
788
789 case PROP_INITIAL_AUDIO_NAME:
790 g_value_set_string (value, self->priv->initial_audio_name);
791 break;
792
793 case PROP_INITIAL_VIDEO_NAME:
794 g_value_set_string (value, self->priv->initial_video_name);
795 break;
796
797 case PROP_MUTABLE_CONTENTS:
798 g_value_set_boolean (value, self->priv->mutable_contents);
799 break;
800
801 case PROP_HOLD_STATE:
802 g_value_set_uint (value, self->priv->hold_state);
803 break;
804
805 case PROP_HOLD_STATE_REASON:
806 g_value_set_uint (value, self->priv->hold_state_reason);
807 break;
808
809 default:
810 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
811 break;
812 }
813 }
814
815 enum {
816 FEAT_CORE,
817 N_FEAT
818 };
819
820 static const TpProxyFeature *
tp_call_channel_list_features(TpProxyClass * cls G_GNUC_UNUSED)821 tp_call_channel_list_features (TpProxyClass *cls G_GNUC_UNUSED)
822 {
823 static TpProxyFeature features[N_FEAT + 1] = { { 0 } };
824
825 if (G_LIKELY (features[0].name != 0))
826 return features;
827
828 /* started from constructed */
829 features[FEAT_CORE].name = TP_CALL_CHANNEL_FEATURE_CORE;
830 features[FEAT_CORE].prepare_async = _tp_call_channel_prepare_core_async;
831 features[FEAT_CORE].core = TRUE;
832
833 /* assert that the terminator at the end is there */
834 g_assert (features[N_FEAT].name == 0);
835
836 return features;
837 }
838
839 static void
tp_call_channel_class_init(TpCallChannelClass * klass)840 tp_call_channel_class_init (TpCallChannelClass *klass)
841 {
842 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
843 TpProxyClass *proxy_class = (TpProxyClass *) klass;
844 GParamSpec *param_spec;
845
846 gobject_class->constructed = tp_call_channel_constructed;
847 gobject_class->get_property = tp_call_channel_get_property;
848 gobject_class->dispose = tp_call_channel_dispose;
849
850 proxy_class->list_features = tp_call_channel_list_features;
851
852 g_type_class_add_private (gobject_class, sizeof (TpCallChannelPrivate));
853
854 /* FIXME: Should be annoted with
855 *
856 * Type: GLib.PtrArray<TelepathyGLib.CallContent>
857 * Transfer: container
858 *
859 * But it does not work (bgo#663846) and makes gtkdoc fail myserably.
860 */
861
862 /**
863 * TpCallChannel:contents:
864 *
865 * #GPtrArray of #TpCallContent objects. The list of content objects that are
866 * part of this call.
867 *
868 * It is NOT guaranteed that %TP_CALL_CONTENT_FEATURE_CORE is prepared on
869 * those objects.
870 *
871 * Since: 0.17.5
872 */
873 param_spec = g_param_spec_boxed ("contents", "Contents",
874 "The content objects of this call",
875 G_TYPE_PTR_ARRAY,
876 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
877 g_object_class_install_property (gobject_class, PROP_CONTENTS, param_spec);
878
879 /**
880 * TpCallChannel:state:
881 *
882 * A #TpCallState specifying the state of the call.
883 *
884 * Since: 0.17.5
885 */
886 param_spec = g_param_spec_uint ("state", "Call state",
887 "The state of the call",
888 0, G_MAXUINT, 0,
889 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
890 g_object_class_install_property (gobject_class, PROP_STATE, param_spec);
891
892 /**
893 * TpCallChannel:flags:
894 *
895 * A #TpCallFlags specifying the flags of the call state.
896 *
897 * Since: 0.17.5
898 */
899 param_spec = g_param_spec_uint ("flags", "Call flags",
900 "The flags of the call",
901 0, G_MAXUINT, 0,
902 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
903 g_object_class_install_property (gobject_class, PROP_FLAGS, param_spec);
904
905 /**
906 * TpCallChannel:state-details:
907 *
908 * Detailed infoermation about #TpCallChannel:state. It is a #GHashTable
909 * mapping gchar*->GValue, it can be accessed using the tp_asv_* functions.
910 *
911 * Since: 0.17.5
912 */
913 param_spec = g_param_spec_boxed ("state-details", "State details",
914 "The details of the call",
915 G_TYPE_HASH_TABLE,
916 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
917 g_object_class_install_property (gobject_class,
918 PROP_STATE_DETAILS, param_spec);
919
920 /**
921 * TpCallChannel:state-reason:
922 *
923 * Reason why #TpCallChannel:state last changed.
924 *
925 * Since: 0.17.5
926 */
927 param_spec = g_param_spec_boxed ("state-reason", "State reason",
928 "The reason of the call's state",
929 TP_TYPE_CALL_STATE_REASON,
930 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
931 g_object_class_install_property (gobject_class,
932 PROP_STATE_REASON, param_spec);
933
934 /**
935 * TpCallChannel:hardware-streaming:
936 *
937 * Whether or not the streaming is done by dedicated hardware.
938 *
939 * Since: 0.17.5
940 */
941 param_spec = g_param_spec_boolean ("hardware-streaming", "Hardware streaming",
942 "Hardware streaming",
943 FALSE,
944 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
945 g_object_class_install_property (gobject_class,
946 PROP_HARDWARE_STREAMING, param_spec);
947
948 /**
949 * TpCallChannel:initial-audio:
950 *
951 * Whether or not the Call was started with audio.
952 *
953 * Since: 0.17.5
954 */
955 param_spec = g_param_spec_boolean ("initial-audio", "Initial audio",
956 "Initial audio",
957 FALSE,
958 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
959 g_object_class_install_property (gobject_class,
960 PROP_INITIAL_AUDIO, param_spec);
961
962 /**
963 * TpCallChannel:initial-video:
964 *
965 * Whether or not the Call was started with video.
966 *
967 * Since: 0.17.5
968 */
969 param_spec = g_param_spec_boolean ("initial-video", "Initial video",
970 "Initial video",
971 FALSE,
972 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
973 g_object_class_install_property (gobject_class,
974 PROP_INITIAL_VIDEO, param_spec);
975
976 /**
977 * TpCallChannel:initial-audio-name:
978 *
979 * If #TpCallChannel:initial-audio is set to %TRUE, then this property will
980 * is the name of the intial audio content, %NULL otherwise.
981 *
982 * Since: 0.17.5
983 */
984 param_spec = g_param_spec_string ("initial-audio-name", "Initial audio name",
985 "Initial audio name",
986 NULL,
987 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
988 g_object_class_install_property (gobject_class,
989 PROP_INITIAL_AUDIO_NAME, param_spec);
990
991 /**
992 * TpCallChannel:initial-video-name:
993 *
994 * If #TpCallChannel:initial-video is set to %TRUE, then this property will
995 * is the name of the intial video content, %NULL otherwise.
996 *
997 * Since: 0.17.5
998 */
999 param_spec = g_param_spec_string ("initial-video-name", "Initial video name",
1000 "Initial video name",
1001 NULL,
1002 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1003 g_object_class_install_property (gobject_class,
1004 PROP_INITIAL_VIDEO_NAME, param_spec);
1005
1006 /**
1007 * TpCallChannel:mutable-contents:
1008 *
1009 * Whether or not call contents can be added or removed.
1010 *
1011 * Since: 0.17.5
1012 */
1013 param_spec = g_param_spec_boolean ("mutable-contents", "Mutable contents",
1014 "Mutable contents",
1015 FALSE,
1016 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1017 g_object_class_install_property (gobject_class,
1018 PROP_MUTABLE_CONTENTS, param_spec);
1019
1020
1021 /**
1022 * TpCallChannel:hold-state:
1023 *
1024 * A #TpLocalHoldState specifying if the Call is currently held
1025 *
1026 * Since: 0.17.6
1027 */
1028 param_spec = g_param_spec_uint ("hold-state", "Hold State",
1029 "The Hold state of the call",
1030 0, G_MAXUINT, TP_LOCAL_HOLD_STATE_UNHELD,
1031 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1032 g_object_class_install_property (gobject_class, PROP_HOLD_STATE, param_spec);
1033
1034
1035 /**
1036 * TpCallChannel:hold-state-reason:
1037 *
1038 * A #TpLocalHoldStateReason specifying why the Call is currently held.
1039 *
1040 * Since: 0.17.6
1041 */
1042 param_spec = g_param_spec_uint ("hold-state-reason", "Hold State Reason",
1043 "The reason for the current hold state",
1044 0, G_MAXUINT, TP_LOCAL_HOLD_STATE_REASON_NONE,
1045 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1046 g_object_class_install_property (gobject_class, PROP_HOLD_STATE_REASON,
1047 param_spec);
1048
1049
1050 /**
1051 * TpCallChannel::content-added:
1052 * @self: the #TpCallChannel
1053 * @content: the newly added #TpCallContent
1054 *
1055 * The ::content-added signal is emitted whenever a
1056 * #TpCallContent is added to @self.
1057 *
1058 * It is NOT guaranteed that %TP_CALL_CONTENT_FEATURE_CORE is prepared on
1059 * @content.
1060 *
1061 * Since: 0.17.5
1062 */
1063 _signals[CONTENT_ADDED] = g_signal_new ("content-added",
1064 G_OBJECT_CLASS_TYPE (klass),
1065 G_SIGNAL_RUN_LAST,
1066 0, NULL, NULL, NULL,
1067 G_TYPE_NONE,
1068 1, G_TYPE_OBJECT);
1069
1070 /**
1071 * TpCallChannel::content-removed:
1072 * @self: the #TpCallChannel
1073 * @content: the newly removed #TpCallContent
1074 * @reason: a #TpCallStateReason
1075 *
1076 * The ::content-removed signal is emitted whenever a
1077 * #TpCallContent is removed from @self.
1078 *
1079 * It is NOT guaranteed that %TP_CALL_CONTENT_FEATURE_CORE is prepared on
1080 * @content.
1081 *
1082 * Since: 0.17.5
1083 */
1084 _signals[CONTENT_REMOVED] = g_signal_new ("content-removed",
1085 G_OBJECT_CLASS_TYPE (klass),
1086 G_SIGNAL_RUN_LAST,
1087 0, NULL, NULL, NULL,
1088 G_TYPE_NONE,
1089 2, G_TYPE_OBJECT, TP_TYPE_CALL_STATE_REASON);
1090
1091 /**
1092 * TpCallChannel::state-changed:
1093 * @self: the #TpCallChannel
1094 * @state: the new #TpCallState
1095 * @flags: the new #TpCallFlags
1096 * @reason: the #TpCallStateReason for the change
1097 * @details: (element-type utf8 GObject.Value): additional details as a
1098 * #GHashTable readable using the tp_asv_* functions.
1099 *
1100 * The ::state-changed signal is emitted whenever the
1101 * call state changes.
1102 *
1103 * Since: 0.17.5
1104 */
1105 _signals[STATE_CHANGED] = g_signal_new ("state-changed",
1106 G_OBJECT_CLASS_TYPE (klass),
1107 G_SIGNAL_RUN_LAST,
1108 0, NULL, NULL, NULL,
1109 G_TYPE_NONE,
1110 4, G_TYPE_UINT, G_TYPE_UINT, TP_TYPE_CALL_STATE_REASON,
1111 G_TYPE_HASH_TABLE);
1112
1113 /**
1114 * TpCallChannel::members-changed:
1115 * @self: the #TpCallChannel
1116 * @updates: (type GLib.HashTable) (element-type TelepathyGLib.Contact uint):
1117 * #GHashTable mapping #TpContact to its new #TpCallMemberFlags
1118 * @removed: (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
1119 * #GPtrArray of #TpContact removed from the call members
1120 * @reason: the #TpCallStateReason for the change
1121 *
1122 * The ::members-changed signal is emitted whenever the call's members
1123 * changes.
1124 *
1125 * The #TpContact objects are guaranteed to have all of the features
1126 * previously passed to tp_simple_client_factory_add_contact_features()
1127 * prepared.
1128 *
1129 * Since: 0.17.5
1130 */
1131 _signals[MEMBERS_CHANGED] = g_signal_new ("members-changed",
1132 G_OBJECT_CLASS_TYPE (klass),
1133 G_SIGNAL_RUN_LAST,
1134 0, NULL, NULL, NULL,
1135 G_TYPE_NONE,
1136 3, G_TYPE_HASH_TABLE, G_TYPE_PTR_ARRAY, TP_TYPE_CALL_STATE_REASON);
1137 }
1138
1139 static void
tp_call_channel_init(TpCallChannel * self)1140 tp_call_channel_init (TpCallChannel *self)
1141 {
1142 self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), TP_TYPE_CALL_CHANNEL,
1143 TpCallChannelPrivate);
1144
1145 self->priv->contents = g_ptr_array_new_with_free_func (g_object_unref);
1146 self->priv->members = g_hash_table_new_full (NULL, NULL,
1147 g_object_unref, NULL);
1148 }
1149
1150 TpCallChannel *
_tp_call_channel_new_with_factory(TpSimpleClientFactory * factory,TpConnection * conn,const gchar * object_path,const GHashTable * immutable_properties,GError ** error)1151 _tp_call_channel_new_with_factory (TpSimpleClientFactory *factory,
1152 TpConnection *conn,
1153 const gchar *object_path,
1154 const GHashTable *immutable_properties,
1155 GError **error)
1156 {
1157 TpProxy *conn_proxy = (TpProxy *) conn;
1158
1159 g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL);
1160 g_return_val_if_fail (object_path != NULL, NULL);
1161 g_return_val_if_fail (immutable_properties != NULL, NULL);
1162
1163 if (!tp_dbus_check_valid_object_path (object_path, error))
1164 return NULL;
1165
1166 return g_object_new (TP_TYPE_CALL_CHANNEL,
1167 "connection", conn,
1168 "dbus-daemon", conn_proxy->dbus_daemon,
1169 "bus-name", conn_proxy->bus_name,
1170 "object-path", object_path,
1171 "handle-type", (guint) TP_UNKNOWN_HANDLE_TYPE,
1172 "channel-properties", immutable_properties,
1173 "factory", factory,
1174 NULL);
1175 }
1176
1177 /**
1178 * TP_CALL_CHANNEL_FEATURE_CORE:
1179 *
1180 * Expands to a call to a function that returns a quark for the "core"
1181 * feature on a #TpCallChannel.
1182 *
1183 * One can ask for a feature to be prepared using the tp_proxy_prepare_async()
1184 * function, and waiting for it to trigger the callback.
1185 */
1186 GQuark
tp_call_channel_get_feature_quark_core(void)1187 tp_call_channel_get_feature_quark_core (void)
1188 {
1189 return g_quark_from_static_string ("tp-call-channel-feature-core");
1190 }
1191
1192 /**
1193 * tp_call_channel_get_contents:
1194 * @self: a #TpCallChannel
1195 *
1196 * <!-- -->
1197 *
1198 * Returns: (transfer none) (type GLib.PtrArray) (element-type TelepathyGLib.CallContent):
1199 * the value of #TpCallChannel:contents
1200 * Since: 0.17.5
1201 */
1202 GPtrArray *
tp_call_channel_get_contents(TpCallChannel * self)1203 tp_call_channel_get_contents (TpCallChannel *self)
1204 {
1205 g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), NULL);
1206
1207 return self->priv->contents;
1208 }
1209
1210 /**
1211 * tp_call_channel_get_state:
1212 * @self: a #TpCallChannel
1213 * @flags: (out) (allow-none) (transfer none): a place to set the value of
1214 * #TpCallChannel:flags
1215 * @details: (out) (allow-none) (transfer none): a place to set the value of
1216 * #TpCallChannel:state-details
1217 * @reason: (out) (allow-none) (transfer none): a place to set the value of
1218 * #TpCallChannel:state-reason
1219 *
1220 * <!-- -->
1221 *
1222 * Returns: the value of #TpCallChannel:state
1223 * Since: 0.17.5
1224 */
1225 TpCallState
tp_call_channel_get_state(TpCallChannel * self,TpCallFlags * flags,GHashTable ** details,TpCallStateReason ** reason)1226 tp_call_channel_get_state (TpCallChannel *self,
1227 TpCallFlags *flags,
1228 GHashTable **details,
1229 TpCallStateReason **reason)
1230 {
1231 g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), TP_CALL_STATE_UNKNOWN);
1232
1233 if (flags != NULL)
1234 *flags = self->priv->flags;
1235 if (details != NULL)
1236 *details = self->priv->state_details;
1237 if (reason != NULL)
1238 *reason = self->priv->state_reason;
1239
1240 return self->priv->state;
1241 }
1242
1243 /**
1244 * tp_call_channel_has_hardware_streaming:
1245 * @self: a #TpCallChannel
1246 *
1247 * <!-- -->
1248 *
1249 * Returns: the value of #TpCallChannel:hardware-streaming
1250 * Since: 0.17.5
1251 */
1252 gboolean
tp_call_channel_has_hardware_streaming(TpCallChannel * self)1253 tp_call_channel_has_hardware_streaming (TpCallChannel *self)
1254 {
1255 g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), FALSE);
1256
1257 return self->priv->hardware_streaming;
1258 }
1259
1260 /**
1261 * tp_call_channel_has_initial_audio:
1262 * @self: a #TpCallChannel
1263 * @initial_audio_name: (out) (allow-none) (transfer none): a place to set the
1264 * value of #TpCallChannel:initial-audio-name
1265 *
1266 * <!-- -->
1267 *
1268 * Returns: the value of #TpCallChannel:initial-audio
1269 * Since: 0.17.5
1270 */
1271 gboolean
tp_call_channel_has_initial_audio(TpCallChannel * self,const gchar ** initial_audio_name)1272 tp_call_channel_has_initial_audio (TpCallChannel *self,
1273 const gchar **initial_audio_name)
1274 {
1275 g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), FALSE);
1276
1277 if (initial_audio_name != NULL)
1278 *initial_audio_name = self->priv->initial_audio_name;
1279
1280 return self->priv->initial_audio;
1281 }
1282
1283 /**
1284 * tp_call_channel_has_initial_video:
1285 * @self: a #TpCallChannel
1286 * @initial_video_name: (out) (allow-none) (transfer none): a place to set the
1287 * value of #TpCallChannel:initial-video-name
1288 *
1289 * <!-- -->
1290 *
1291 * Returns: the value of #TpCallChannel:initial-video
1292 * Since: 0.17.5
1293 */
1294 gboolean
tp_call_channel_has_initial_video(TpCallChannel * self,const gchar ** initial_video_name)1295 tp_call_channel_has_initial_video (TpCallChannel *self,
1296 const gchar **initial_video_name)
1297 {
1298 g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), FALSE);
1299
1300 if (initial_video_name != NULL)
1301 *initial_video_name = self->priv->initial_video_name;
1302
1303 return self->priv->initial_video;
1304 }
1305
1306 /**
1307 * tp_call_channel_has_mutable_contents:
1308 * @self: a #TpCallChannel
1309 *
1310 * <!-- -->
1311 *
1312 * Returns: the value of #TpCallChannel:mutable-contents
1313 * Since: 0.17.5
1314 */
1315 gboolean
tp_call_channel_has_mutable_contents(TpCallChannel * self)1316 tp_call_channel_has_mutable_contents (TpCallChannel *self)
1317 {
1318 g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), FALSE);
1319
1320 return self->priv->mutable_contents;
1321 }
1322
1323 /**
1324 * tp_call_channel_get_members:
1325 * @self: a #TpCallChannel
1326 *
1327 * Get the members of this call.
1328 *
1329 * The #TpContact objects are guaranteed to have all of the features
1330 * previously passed to tp_simple_client_factory_add_contact_features()
1331 * prepared.
1332 *
1333 * Returns: (transfer none) (type GLib.HashTable) (element-type TelepathyGLib.Contact uint):
1334 * #GHashTable mapping #TpContact to its new #TpCallMemberFlags
1335 * Since: 0.17.5
1336 */
1337 GHashTable *
tp_call_channel_get_members(TpCallChannel * self)1338 tp_call_channel_get_members (TpCallChannel *self)
1339 {
1340 g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), NULL);
1341
1342 return self->priv->members;
1343 }
1344
1345 /**
1346 * tp_call_channel_has_dtmf:
1347 * @self: a #TpCallChannel
1348 *
1349 * Whether or not @self can send DTMF tones using
1350 * tp_call_channel_send_tones_async(). To be able to send DTMF tones, at least
1351 * one of @self's #TpCallChannel:contents must implement
1352 * %TP_IFACE_CALL_CONTENT_INTERFACE_DTMF interface.
1353 *
1354 * Returns: whether or not @self can send DTMF tones.
1355 * Since: 0.17.5
1356 */
1357 gboolean
tp_call_channel_has_dtmf(TpCallChannel * self)1358 tp_call_channel_has_dtmf (TpCallChannel *self)
1359 {
1360 guint i;
1361
1362 g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), FALSE);
1363
1364 for (i = 0; i < self->priv->contents->len; i++)
1365 {
1366 TpCallContent *content = g_ptr_array_index (self->priv->contents, i);
1367
1368 if (tp_proxy_has_interface_by_id (content,
1369 TP_IFACE_QUARK_CALL_CONTENT_INTERFACE_DTMF))
1370 return TRUE;
1371 }
1372
1373 return FALSE;
1374 }
1375
1376
1377 /**
1378 * tp_call_channel_has_hold:
1379 * @self: a #TpCallChannel
1380 *
1381 * Whether or not @self has the %TP_IFACE_CHANNEL_INTERFACE_HOLD
1382 * interfaces
1383 *
1384 * Returns: whether or not @self supports Hold
1385 * Since: 0.17.6
1386 */
1387 gboolean
tp_call_channel_has_hold(TpCallChannel * self)1388 tp_call_channel_has_hold (TpCallChannel *self)
1389 {
1390 g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), FALSE);
1391 g_return_val_if_fail (
1392 tp_proxy_is_prepared (self, TP_CALL_CHANNEL_FEATURE_CORE), FALSE);
1393
1394 return tp_proxy_has_interface_by_id (self,
1395 TP_IFACE_QUARK_CHANNEL_INTERFACE_HOLD);
1396 }
1397
1398 static void
generic_async_cb(TpChannel * channel,const GError * error,gpointer user_data,GObject * weak_object)1399 generic_async_cb (TpChannel *channel,
1400 const GError *error,
1401 gpointer user_data,
1402 GObject *weak_object)
1403 {
1404 GSimpleAsyncResult *result = user_data;
1405
1406 if (error != NULL)
1407 {
1408 DEBUG ("Error: %s", error->message);
1409 g_simple_async_result_set_from_error (result, error);
1410 }
1411
1412 g_simple_async_result_complete (result);
1413 }
1414
1415 /**
1416 * tp_call_channel_set_ringing_async:
1417 * @self: a #TpCallChannel
1418 * @callback: a callback to call when the operation finishes
1419 * @user_data: data to pass to @callback
1420 *
1421 * Indicate that the local user has been alerted about the incoming call.
1422 *
1423 * Since: 0.17.5
1424 */
1425 void
tp_call_channel_set_ringing_async(TpCallChannel * self,GAsyncReadyCallback callback,gpointer user_data)1426 tp_call_channel_set_ringing_async (TpCallChannel *self,
1427 GAsyncReadyCallback callback,
1428 gpointer user_data)
1429 {
1430 GSimpleAsyncResult *result;
1431
1432 g_return_if_fail (TP_IS_CALL_CHANNEL (self));
1433
1434 result = g_simple_async_result_new (G_OBJECT (self), callback,
1435 user_data, tp_call_channel_set_ringing_async);
1436
1437 tp_cli_channel_type_call_call_set_ringing (TP_CHANNEL (self), -1,
1438 generic_async_cb, result, g_object_unref, G_OBJECT (self));
1439 }
1440
1441 /**
1442 * tp_call_channel_set_ringing_finish:
1443 * @self: a #TpCallChannel
1444 * @result: a #GAsyncResult
1445 * @error: a #GError to fill
1446 *
1447 * Finishes tp_call_channel_set_ringing_async().
1448 *
1449 * Since: 0.17.5
1450 */
1451 gboolean
tp_call_channel_set_ringing_finish(TpCallChannel * self,GAsyncResult * result,GError ** error)1452 tp_call_channel_set_ringing_finish (TpCallChannel *self,
1453 GAsyncResult *result,
1454 GError **error)
1455 {
1456 _tp_implement_finish_void (self, tp_call_channel_set_ringing_async);
1457 }
1458
1459 /**
1460 * tp_call_channel_set_queued_async:
1461 * @self: a #TpCallChannel
1462 * @callback: a callback to call when the operation finishes
1463 * @user_data: data to pass to @callback
1464 *
1465 * Notifies the CM that the local user is already in a call, so this call has
1466 * been put in a call-waiting style queue.
1467 *
1468 * Since: 0.17.5
1469 */
1470 void
tp_call_channel_set_queued_async(TpCallChannel * self,GAsyncReadyCallback callback,gpointer user_data)1471 tp_call_channel_set_queued_async (TpCallChannel *self,
1472 GAsyncReadyCallback callback,
1473 gpointer user_data)
1474 {
1475 GSimpleAsyncResult *result;
1476
1477 g_return_if_fail (TP_IS_CALL_CHANNEL (self));
1478
1479 result = g_simple_async_result_new (G_OBJECT (self), callback,
1480 user_data, tp_call_channel_set_queued_async);
1481
1482 tp_cli_channel_type_call_call_set_queued (TP_CHANNEL (self), -1,
1483 generic_async_cb, result, g_object_unref, G_OBJECT (self));
1484 }
1485
1486 /**
1487 * tp_call_channel_set_queued_finish:
1488 * @self: a #TpCallChannel
1489 * @result: a #GAsyncResult
1490 * @error: a #GError to fill
1491 *
1492 * Finishes tp_call_channel_set_queued_async().
1493 *
1494 * Since: 0.17.5
1495 */
1496 gboolean
tp_call_channel_set_queued_finish(TpCallChannel * self,GAsyncResult * result,GError ** error)1497 tp_call_channel_set_queued_finish (TpCallChannel *self,
1498 GAsyncResult *result,
1499 GError **error)
1500 {
1501 _tp_implement_finish_void (self, tp_call_channel_set_queued_async);
1502 }
1503
1504 /**
1505 * tp_call_channel_accept_async:
1506 * @self: a #TpCallChannel
1507 * @callback: a callback to call when the operation finishes
1508 * @user_data: data to pass to @callback
1509 *
1510 * For incoming calls with #TpCallChannel:state set to
1511 * %TP_CALL_STATE_INITIALISED, accept the incoming call. This changes
1512 * #TpCallChannel:state to %TP_CALL_STATE_ACCEPTED.
1513 *
1514 * For outgoing calls with #TpCallChannel:state set to
1515 * %TP_CALL_STATE_PENDING_INITIATOR, actually call the remote contact; this
1516 * changes #TpCallChannel:state to
1517 * %TP_CALL_STATE_INITIALISING.
1518 *
1519 * Since: 0.17.5
1520 */
1521 void
tp_call_channel_accept_async(TpCallChannel * self,GAsyncReadyCallback callback,gpointer user_data)1522 tp_call_channel_accept_async (TpCallChannel *self,
1523 GAsyncReadyCallback callback,
1524 gpointer user_data)
1525 {
1526 GSimpleAsyncResult *result;
1527
1528 g_return_if_fail (TP_IS_CALL_CHANNEL (self));
1529
1530 result = g_simple_async_result_new (G_OBJECT (self), callback,
1531 user_data, tp_call_channel_accept_async);
1532
1533 tp_cli_channel_type_call_call_accept (TP_CHANNEL (self), -1,
1534 generic_async_cb, result, g_object_unref, G_OBJECT (self));
1535 }
1536
1537 /**
1538 * tp_call_channel_accept_finish:
1539 * @self: a #TpCallChannel
1540 * @result: a #GAsyncResult
1541 * @error: a #GError to fill
1542 *
1543 * Finishes tp_call_channel_accept_async().
1544 *
1545 * Since: 0.17.5
1546 */
1547 gboolean
tp_call_channel_accept_finish(TpCallChannel * self,GAsyncResult * result,GError ** error)1548 tp_call_channel_accept_finish (TpCallChannel *self,
1549 GAsyncResult *result,
1550 GError **error)
1551 {
1552 _tp_implement_finish_void (self, tp_call_channel_accept_async);
1553 }
1554
1555 /**
1556 * tp_call_channel_hangup_async:
1557 * @self: a #TpCallChannel
1558 * @reason: a TpCallStateChangeReason
1559 * @detailed_reason: a more specific reason for the call hangup, if one is
1560 * available, or an empty or %NULL string otherwise
1561 * @message: a human-readable message to be sent to the remote contact(s)
1562 * @callback: a callback to call when the operation finishes
1563 * @user_data: data to pass to @callback
1564 *
1565 * Request that the call is ended. All contents will be removed from @self so
1566 * that the #TpCallChannel:contents property will be the empty list.
1567 *
1568 * Since: 0.17.5
1569 */
1570 void
tp_call_channel_hangup_async(TpCallChannel * self,TpCallStateChangeReason reason,const gchar * detailed_reason,const gchar * message,GAsyncReadyCallback callback,gpointer user_data)1571 tp_call_channel_hangup_async (TpCallChannel *self,
1572 TpCallStateChangeReason reason,
1573 const gchar *detailed_reason,
1574 const gchar *message,
1575 GAsyncReadyCallback callback,
1576 gpointer user_data)
1577 {
1578 GSimpleAsyncResult *result;
1579
1580 g_return_if_fail (TP_IS_CALL_CHANNEL (self));
1581
1582 result = g_simple_async_result_new (G_OBJECT (self), callback,
1583 user_data, tp_call_channel_hangup_async);
1584
1585 tp_cli_channel_type_call_call_hangup (TP_CHANNEL (self), -1,
1586 reason, detailed_reason, message,
1587 generic_async_cb, result, g_object_unref, G_OBJECT (self));
1588 }
1589
1590 /**
1591 * tp_call_channel_hangup_finish:
1592 * @self: a #TpCallChannel
1593 * @result: a #GAsyncResult
1594 * @error: a #GError to fill
1595 *
1596 * Finishes tp_call_channel_hangup_async().
1597 *
1598 * Since: 0.17.5
1599 */
1600 gboolean
tp_call_channel_hangup_finish(TpCallChannel * self,GAsyncResult * result,GError ** error)1601 tp_call_channel_hangup_finish (TpCallChannel *self,
1602 GAsyncResult *result,
1603 GError **error)
1604 {
1605 _tp_implement_finish_void (self, tp_call_channel_hangup_async);
1606 }
1607
1608 static void
add_content_cb(TpChannel * channel,const gchar * object_path,const GError * error,gpointer user_data,GObject * weak_object)1609 add_content_cb (TpChannel *channel,
1610 const gchar *object_path,
1611 const GError *error,
1612 gpointer user_data,
1613 GObject *weak_object)
1614 {
1615 TpCallChannel *self = (TpCallChannel *) channel;
1616 GSimpleAsyncResult *result = user_data;
1617
1618 if (error != NULL)
1619 {
1620 DEBUG ("Error: %s", error->message);
1621 g_simple_async_result_set_from_error (result, error);
1622 }
1623 else
1624 {
1625 g_simple_async_result_set_op_res_gpointer (result,
1626 g_object_ref (ensure_content (self, object_path)),
1627 g_object_unref);
1628 }
1629
1630 g_simple_async_result_complete (result);
1631 }
1632
1633 /**
1634 * tp_call_channel_add_content_async:
1635 * @self: a #TpCallChannel
1636 * @name: the suggested name of the content to add
1637 * @type: the media stream type of the content to be added to the call, from
1638 * #TpMediaStreamType
1639 * @initial_direction: The initial direction of the content
1640 * @callback: a callback to call when the operation finishes
1641 * @user_data: data to pass to @callback
1642 *
1643 * Request that a new Content of type @type is added to @self. Callers should
1644 * check the value of the #TpCallChannel:mutable-contents property before trying
1645 * to add another content as it might not be allowed.
1646 *
1647 * Since: 0.17.5
1648 */
1649 void
tp_call_channel_add_content_async(TpCallChannel * self,const gchar * name,TpMediaStreamType type,TpMediaStreamDirection initial_direction,GAsyncReadyCallback callback,gpointer user_data)1650 tp_call_channel_add_content_async (TpCallChannel *self,
1651 const gchar *name,
1652 TpMediaStreamType type,
1653 TpMediaStreamDirection initial_direction,
1654 GAsyncReadyCallback callback,
1655 gpointer user_data)
1656 {
1657 GSimpleAsyncResult *result;
1658
1659 g_return_if_fail (TP_IS_CALL_CHANNEL (self));
1660
1661 result = g_simple_async_result_new (G_OBJECT (self), callback,
1662 user_data, tp_call_channel_add_content_async);
1663
1664 tp_cli_channel_type_call_call_add_content (TP_CHANNEL (self), -1,
1665 name, type, initial_direction,
1666 add_content_cb, result, g_object_unref, G_OBJECT (self));
1667 }
1668
1669 /**
1670 * tp_call_channel_add_content_finish:
1671 * @self: a #TpCallChannel
1672 * @result: a #GAsyncResult
1673 * @error: a #GError to fill
1674 *
1675 * Finishes tp_call_channel_add_content_async().
1676 *
1677 * The returned #TpCallContent is NOT guaranteed to have
1678 * %TP_CALL_CONTENT_FEATURE_CORE prepared.
1679 *
1680 * Returns: (transfer full): reference to the new #TpCallContent.
1681 * Since: 0.17.5
1682 */
1683 TpCallContent *
tp_call_channel_add_content_finish(TpCallChannel * self,GAsyncResult * result,GError ** error)1684 tp_call_channel_add_content_finish (TpCallChannel *self,
1685 GAsyncResult *result,
1686 GError **error)
1687 {
1688 _tp_implement_finish_return_copy_pointer (self,
1689 tp_call_channel_add_content_async, g_object_ref);
1690 }
1691
1692 static void
send_tones_cb(GObject * source,GAsyncResult * res,gpointer user_data)1693 send_tones_cb (GObject *source,
1694 GAsyncResult *res,
1695 gpointer user_data)
1696 {
1697 GSimpleAsyncResult *result = user_data;
1698 guint count;
1699 GError *error = NULL;
1700
1701 if (!tp_call_content_send_tones_finish ((TpCallContent *) source, res,
1702 &error))
1703 g_simple_async_result_take_error (result, error);
1704
1705 /* Decrement the op count */
1706 count = GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (result));
1707 g_simple_async_result_set_op_res_gpointer (result, GUINT_TO_POINTER (--count),
1708 NULL);
1709
1710 if (count == 0)
1711 g_simple_async_result_complete (result);
1712
1713 g_object_unref (result);
1714 }
1715
1716 /**
1717 * tp_call_channel_send_tones_async:
1718 * @self: a #TpCallChannel
1719 * @tones: a string representation of one or more DTMF events.
1720 * @cancellable: optional #GCancellable object, %NULL to ignore
1721 * @callback: a callback to call when the operation finishes
1722 * @user_data: data to pass to @callback
1723 *
1724 * Send @tones on every of @self's contents which have the
1725 * %TP_IFACE_CALL_CONTENT_INTERFACE_DTMF interface.
1726 *
1727 * For more details, see tp_call_content_send_tones_async().
1728 *
1729 * Since: 0.17.5
1730 */
1731 void
tp_call_channel_send_tones_async(TpCallChannel * self,const gchar * tones,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1732 tp_call_channel_send_tones_async (TpCallChannel *self,
1733 const gchar *tones,
1734 GCancellable *cancellable,
1735 GAsyncReadyCallback callback,
1736 gpointer user_data)
1737 {
1738 GSimpleAsyncResult *result;
1739 guint i;
1740 guint count = 0;
1741
1742 g_return_if_fail (TP_IS_CALL_CHANNEL (self));
1743 g_return_if_fail (tp_call_channel_has_dtmf (self));
1744
1745 result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
1746 tp_call_channel_send_tones_async);
1747
1748 for (i = 0; i < self->priv->contents->len; i++)
1749 {
1750 TpCallContent *content = g_ptr_array_index (self->priv->contents, i);
1751
1752 if (!tp_proxy_has_interface_by_id (content,
1753 TP_IFACE_QUARK_CALL_CONTENT_INTERFACE_DTMF))
1754 continue;
1755
1756 count++;
1757 tp_call_content_send_tones_async (content, tones, cancellable,
1758 send_tones_cb, g_object_ref (result));
1759 }
1760
1761 g_assert (count > 0);
1762 g_simple_async_result_set_op_res_gpointer (result,
1763 GUINT_TO_POINTER (count), NULL);
1764
1765 g_object_unref (result);
1766 }
1767
1768 /**
1769 * tp_call_channel_send_tones_finish:
1770 * @self: a #TpCallChannel
1771 * @result: a #GAsyncResult
1772 * @error: a #GError to fill
1773 *
1774 * Finishes tp_call_channel_send_tones_async().
1775 *
1776 * Returns: %TRUE on success, %FALSE otherwise.
1777 * Since: 0.17.5
1778 */
1779 gboolean
tp_call_channel_send_tones_finish(TpCallChannel * self,GAsyncResult * result,GError ** error)1780 tp_call_channel_send_tones_finish (TpCallChannel *self,
1781 GAsyncResult *result,
1782 GError **error)
1783 {
1784 _tp_implement_finish_void (self, tp_call_channel_send_tones_async)
1785 }
1786
1787 /**
1788 * tp_call_channel_request_hold_async:
1789 * @self: a #TpCallChannel
1790 * @hold: Whether to request a hold or a unhold
1791 * @callback: a callback to call when the operation finishes
1792 * @user_data: data to pass to @callback
1793 *
1794 * Requests that the connection manager holds or unholds the call. Watch
1795 * #TpCallChannel:hold-state property to know when the channel goes on
1796 * hold or is unheld. Unholding may fail if the streaming implementation
1797 * can not obtain all the resources needed to restart the call.
1798 *
1799 * Since: 0.17.6
1800 */
1801
1802 void
tp_call_channel_request_hold_async(TpCallChannel * self,gboolean hold,GAsyncReadyCallback callback,gpointer user_data)1803 tp_call_channel_request_hold_async (TpCallChannel *self,
1804 gboolean hold,
1805 GAsyncReadyCallback callback,
1806 gpointer user_data)
1807 {
1808 GSimpleAsyncResult *result;
1809
1810 g_return_if_fail (TP_IS_CALL_CHANNEL (self));
1811
1812 result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
1813 tp_call_channel_request_hold_async);
1814
1815 if (tp_call_channel_has_hold (self))
1816 {
1817 tp_cli_channel_interface_hold_call_request_hold (TP_CHANNEL (self), -1,
1818 hold, generic_async_cb, g_object_ref (result), g_object_unref,
1819 G_OBJECT (self));
1820 }
1821 else
1822 {
1823 g_simple_async_result_set_error (result,
1824 TP_ERROR, TP_ERROR_NOT_CAPABLE,
1825 "Channel does NOT implement the Hold interface");
1826 g_simple_async_result_complete_in_idle (result);
1827 }
1828
1829 g_object_unref (result);
1830 }
1831
1832
1833 /**
1834 * tp_call_channel_request_hold_finish:
1835 * @self: a #TpCallChannel
1836 * @result: a #GAsyncResult
1837 * @error: a #GError to fill
1838 *
1839 * Finishes tp_call_channel_request_hold_async
1840 *
1841 * Since: 0.17.6
1842 */
1843 gboolean
tp_call_channel_request_hold_finish(TpCallChannel * self,GAsyncResult * result,GError ** error)1844 tp_call_channel_request_hold_finish (TpCallChannel *self,
1845 GAsyncResult *result,
1846 GError **error)
1847 {
1848 _tp_implement_finish_void (self, tp_call_channel_request_hold_async);
1849 }
1850