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