/* * call-channel.h - high level API for Call channels * * Copyright (C) 2011 Collabora Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** * SECTION:call-channel * @title: TpCallChannel * @short_description: proxy object for a call channel * * #TpCallChannel is a sub-class of #TpChannel providing convenient API * to make calls */ /** * TpCallChannel: * * Data structure representing a #TpCallChannel. * * Since: 0.17.5 */ /** * TpCallChannelClass: * * The class of a #TpCallChannel. * * Since: 0.17.5 */ #include "config.h" #include "telepathy-glib/call-channel.h" #include #include #include #include #include #include #include #include #define DEBUG_FLAG TP_DEBUG_CALL #include "telepathy-glib/automatic-client-factory-internal.h" #include "telepathy-glib/call-internal.h" #include "telepathy-glib/channel-internal.h" #include "telepathy-glib/connection-internal.h" #include "telepathy-glib/debug-internal.h" #include "telepathy-glib/proxy-internal.h" #include "telepathy-glib/util-internal.h" G_DEFINE_TYPE (TpCallChannel, tp_call_channel, TP_TYPE_CHANNEL) struct _TpCallChannelPrivate { /* Array of TpCallContents */ GPtrArray *contents; TpCallState state; TpCallFlags flags; GHashTable *state_details; TpCallStateReason *state_reason; gboolean hardware_streaming; /* TpContact -> TpCallMemberFlags */ GHashTable *members; gboolean initial_audio; gboolean initial_video; gchar *initial_audio_name; gchar *initial_video_name; gboolean mutable_contents; TpLocalHoldState hold_state; TpLocalHoldStateReason hold_state_reason; GSimpleAsyncResult *core_result; gboolean properties_retrieved; gboolean initial_members_retrieved; gboolean hold_state_retrieved; }; enum /* props */ { PROP_CONTENTS = 1, PROP_STATE, PROP_FLAGS, PROP_STATE_DETAILS, PROP_STATE_REASON, PROP_HARDWARE_STREAMING, PROP_INITIAL_AUDIO, PROP_INITIAL_VIDEO, PROP_INITIAL_AUDIO_NAME, PROP_INITIAL_VIDEO_NAME, PROP_MUTABLE_CONTENTS, PROP_HOLD_STATE, PROP_HOLD_STATE_REASON, }; enum /* signals */ { CONTENT_ADDED, CONTENT_REMOVED, STATE_CHANGED, MEMBERS_CHANGED, LAST_SIGNAL }; static guint _signals[LAST_SIGNAL] = { 0, }; static TpCallContent * _tp_call_content_new (TpCallChannel *self, const gchar *object_path) { return g_object_new (TP_TYPE_CALL_CONTENT, "bus-name", tp_proxy_get_bus_name (self), "dbus-daemon", tp_proxy_get_dbus_daemon (self), "dbus-connection", tp_proxy_get_dbus_connection (self), "object-path", object_path, "connection", tp_channel_get_connection ((TpChannel *) self), "channel", self, "factory", tp_proxy_get_factory (self), NULL); } /** * TpCallStateReason: * @actor: the contact responsible for the change, or 0 if no contact was * responsible * @reason: the reason for the change. If * #TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED then the @actor member will * dictate whether it was the local user or a remote contact responsible * @dbus_reason: A specific reason for the change, which may be a D-Bus error in * the Telepathy namespace, a D-Bus error in any other namespace * (for implementation-specific errors), or the empty string to indicate that * the state change was not an error * @message: A developer readable debug message giving the reason for the state * change. * * Data structure representing the reason for a call state change. * * Since: 0.17.5 */ static TpCallStateReason * _tp_call_state_reason_new_full (TpHandle actor, TpCallStateChangeReason reason, const gchar *dbus_reason, const gchar *message) { TpCallStateReason *r; r = g_slice_new0 (TpCallStateReason); r->actor = actor; r->reason = reason; r->dbus_reason = g_strdup (dbus_reason); r->message = g_strdup (message); r->ref_count = 1; return r; } TpCallStateReason * _tp_call_state_reason_new (const GValueArray *value_array) { TpHandle handle; TpCallStateChangeReason reason; const gchar *dbus_reason; const gchar *message; tp_value_array_unpack ((GValueArray *) value_array, 4, &handle, &reason, &dbus_reason, &message); return _tp_call_state_reason_new_full (handle, reason, dbus_reason, message); } TpCallStateReason * _tp_call_state_reason_ref (TpCallStateReason *r) { g_atomic_int_inc (&r->ref_count); return r; } void _tp_call_state_reason_unref (TpCallStateReason *r) { g_return_if_fail (r != NULL); if (g_atomic_int_dec_and_test (&r->ref_count)) { g_free (r->dbus_reason); g_free (r->message); g_slice_free (TpCallStateReason, r); } } G_DEFINE_BOXED_TYPE (TpCallStateReason, tp_call_state_reason, _tp_call_state_reason_ref, _tp_call_state_reason_unref); /* Convert GHashTable to GHashTable. * Assuming value does not need to be copied */ GHashTable * _tp_call_members_convert_table (TpConnection *connection, GHashTable *table, GHashTable *identifiers) { GHashTable *result; GHashTableIter iter; gpointer key, value; result = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL); g_hash_table_iter_init (&iter, table); while (g_hash_table_iter_next (&iter, &key, &value)) { TpHandle handle = GPOINTER_TO_UINT (key); const gchar *id; TpContact *contact; id = g_hash_table_lookup (identifiers, key); if (id == NULL) { DEBUG ("Missing identifier for member %u - broken CM", handle); continue; } contact = tp_connection_dup_contact_if_possible (connection, handle, id); if (contact == NULL) { DEBUG ("Can't create contact for (%u, %s) pair - CM does not have " "immutable handles?", handle, id); continue; } g_hash_table_insert (result, contact, value); } return result; } /* Convert GArray to GPtrArray. * Assuming the TpContact already exists. */ GPtrArray * _tp_call_members_convert_array (TpConnection *connection, const GArray *array) { GPtrArray *result; guint i; result = g_ptr_array_new_full (array->len, g_object_unref); for (i = 0; i < array->len; i++) { TpHandle handle = g_array_index (array, TpHandle, i); TpContact *contact; /* The contact is supposed to already exists */ contact = tp_connection_dup_contact_if_possible (connection, handle, NULL); if (contact == NULL) { DEBUG ("No TpContact found for handle %u", handle); continue; } g_ptr_array_add (result, contact); } return result; } static TpCallContent * ensure_content (TpCallChannel *self, const gchar *object_path) { TpCallContent *content; guint i; for (i = 0; i < self->priv->contents->len; i++) { content = g_ptr_array_index (self->priv->contents, i); if (!tp_strdiff (tp_proxy_get_object_path (content), object_path)) return content; } DEBUG ("Content added: %s", object_path); content = _tp_call_content_new (self, object_path); g_ptr_array_add (self->priv->contents, content); g_signal_emit (self, _signals[CONTENT_ADDED], 0, content); return content; } static void content_added_cb (TpChannel *channel, const gchar *object_path, gpointer user_data, GObject *weak_object) { TpCallChannel *self = (TpCallChannel *) channel; if (!self->priv->properties_retrieved) return; ensure_content (self, object_path); } static void content_removed_cb (TpChannel *channel, const gchar *object_path, const GValueArray *reason, gpointer user_data, GObject *weak_object) { TpCallChannel *self = (TpCallChannel *) channel; guint i; if (!self->priv->properties_retrieved) return; for (i = 0; i < self->priv->contents->len; i++) { TpCallContent *content = g_ptr_array_index (self->priv->contents, i); if (!tp_strdiff (tp_proxy_get_object_path (content), object_path)) { TpCallStateReason *r; DEBUG ("Content removed: %s", object_path); r = _tp_call_state_reason_new (reason); g_object_ref (content); g_ptr_array_remove_index_fast (self->priv->contents, i); g_signal_emit (self, _signals[CONTENT_REMOVED], 0, content, r); g_signal_emit_by_name (content, "removed"); g_object_unref (content); _tp_call_state_reason_unref (r); return; } } DEBUG ("Content '%s' removed but not found", object_path); } static const gchar * call_state_to_string (TpCallState state) { switch (state) { case TP_CALL_STATE_UNKNOWN: return "unknown"; case TP_CALL_STATE_PENDING_INITIATOR: return "pending-initiator"; case TP_CALL_STATE_INITIALISING: return "initialising"; case TP_CALL_STATE_INITIALISED: return "initialised"; case TP_CALL_STATE_ACCEPTED: return "accepted"; case TP_CALL_STATE_ACTIVE: return "active"; case TP_CALL_STATE_ENDED: return "ended"; } return "invalid"; } static void call_state_changed_cb (TpChannel *channel, guint state, guint flags, const GValueArray *reason, GHashTable *details, gpointer user_data, GObject *weak_object) { TpCallChannel *self = (TpCallChannel *) channel; if (!self->priv->properties_retrieved) return; DEBUG ("Call state changed to %s (flags: %u)", call_state_to_string (state), flags); tp_clear_pointer (&self->priv->state_reason, _tp_call_state_reason_unref); tp_clear_pointer (&self->priv->state_details, g_hash_table_unref); self->priv->state = state; self->priv->flags = flags; self->priv->state_reason = _tp_call_state_reason_new (reason); self->priv->state_details = g_hash_table_ref (details); g_object_notify ((GObject *) self, "state"); g_object_notify ((GObject *) self, "flags"); g_object_notify ((GObject *) self, "state-reason"); g_object_notify ((GObject *) self, "state-details"); g_signal_emit (self, _signals[STATE_CHANGED], 0, self->priv->state, self->priv->flags, self->priv->state_reason, self->priv->state_details); } typedef struct { TpCallChannel *self; GHashTable *updates; GPtrArray *removed; TpCallStateReason *reason; } UpdateCallMembersData; static void channel_maybe_core_prepared (TpCallChannel *self) { if (self->priv->core_result == NULL) return; if (self->priv->initial_members_retrieved && self->priv->properties_retrieved && self->priv->hold_state_retrieved) { g_simple_async_result_complete (self->priv->core_result); g_clear_object (&self->priv->core_result); } } static void update_call_members_prepared_cb (GObject *object, GAsyncResult *result, gpointer user_data) { UpdateCallMembersData *data = user_data; TpCallChannel *self = data->self; GError *error = NULL; if (!_tp_channel_contacts_queue_prepare_finish ((TpChannel *) self, result, NULL, &error)) { DEBUG ("Error preparing call members: %s", error->message); g_clear_error (&error); } tp_g_hash_table_update (self->priv->members, data->updates, g_object_ref, NULL); if (data->removed != NULL) { guint i; for (i = 0; i < data->removed->len; i++) { g_hash_table_remove (self->priv->members, g_ptr_array_index (data->removed, i)); } } if (!self->priv->initial_members_retrieved) { self->priv->initial_members_retrieved = TRUE; channel_maybe_core_prepared (self); } else { g_signal_emit (self, _signals[MEMBERS_CHANGED], 0, data->updates, data->removed, data->reason); } tp_clear_pointer (&data->updates, g_hash_table_unref); tp_clear_pointer (&data->removed, g_ptr_array_unref); tp_clear_pointer (&data->reason, _tp_call_state_reason_unref); g_slice_free (UpdateCallMembersData, data); } static void update_call_members (TpCallChannel *self, GHashTable *updates, GPtrArray *removed, TpCallStateReason *reason) { GHashTableIter iter; gpointer key, value; GPtrArray *contacts; UpdateCallMembersData *data; /* We want to expose only prepared contacts. Collect TpContact objects and * prepared them in the channel's queue to make sure it does not reorder * events. * Applications where delay to display the call is critical shouldn't set * contact features on the factory, in which case this becomes no-op. */ contacts = g_ptr_array_new_full (g_hash_table_size (updates), g_object_unref); g_hash_table_iter_init (&iter, updates); while (g_hash_table_iter_next (&iter, &key, &value)) g_ptr_array_add (contacts, g_object_ref (key)); data = g_slice_new0 (UpdateCallMembersData); data->self = self; data->updates = g_hash_table_ref (updates); data->removed = removed != NULL ? g_ptr_array_ref (removed) : NULL; data->reason = reason != NULL ? _tp_call_state_reason_ref (reason) : NULL; _tp_channel_contacts_queue_prepare_async ((TpChannel *) self, contacts, update_call_members_prepared_cb, data); g_ptr_array_unref (contacts); } static void call_members_changed_cb (TpChannel *channel, GHashTable *updates, GHashTable *identifiers, const GArray *removed, const GValueArray *reason, gpointer user_data, GObject *weak_object) { TpCallChannel *self = (TpCallChannel *) channel; TpConnection *connection; GHashTable *updates_contacts; GPtrArray *removed_contacts; TpCallStateReason *r; DEBUG ("Call members: %d changed, %d removed", g_hash_table_size (updates), removed->len); connection = tp_channel_get_connection (channel); updates_contacts = _tp_call_members_convert_table (connection, updates, identifiers); removed_contacts = _tp_call_members_convert_array (connection, removed); r = _tp_call_state_reason_new (reason); update_call_members (self, updates_contacts, removed_contacts, r); g_hash_table_unref (updates_contacts); g_ptr_array_unref (removed_contacts); _tp_call_state_reason_unref (r); } static void got_all_properties_cb (TpProxy *proxy, GHashTable *properties, const GError *error, gpointer user_data, GObject *weak_object) { TpCallChannel *self = (TpCallChannel *) proxy; TpConnection *connection; GPtrArray *contents; GHashTable *members; GHashTable *identifiers; GHashTable *contacts; guint i; if (error != NULL) { DEBUG ("Could not get the call channel properties: %s", error->message); g_simple_async_result_set_from_error (self->priv->core_result, error); g_simple_async_result_complete (self->priv->core_result); g_clear_object (&self->priv->core_result); return; } connection = tp_channel_get_connection ((TpChannel *) self); g_assert (tp_connection_has_immortal_handles (connection)); self->priv->properties_retrieved = TRUE; contents = tp_asv_get_boxed (properties, "Contents", TP_ARRAY_TYPE_OBJECT_PATH_LIST); self->priv->state = tp_asv_get_uint32 (properties, "CallState", NULL); self->priv->flags = tp_asv_get_uint32 (properties, "CallFlags", NULL); self->priv->state_details = g_hash_table_ref (tp_asv_get_boxed (properties, "CallStateDetails", TP_HASH_TYPE_STRING_VARIANT_MAP)); self->priv->state_reason = _tp_call_state_reason_new (tp_asv_get_boxed (properties, "CallStateReason", TP_STRUCT_TYPE_CALL_STATE_REASON)); members = tp_asv_get_boxed (properties, "CallMembers", TP_HASH_TYPE_CALL_MEMBER_MAP); identifiers = tp_asv_get_boxed (properties, "MemberIdentifiers", TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP); contacts = _tp_call_members_convert_table (connection, members, identifiers); update_call_members (self, contacts, NULL, NULL); g_hash_table_unref (contacts); for (i = 0; i < contents->len; i++) { const gchar *object_path = g_ptr_array_index (contents, i); DEBUG ("Initial content added: %s", object_path); g_ptr_array_add (self->priv->contents, _tp_call_content_new (self, object_path)); } /* core_result will be complete in update_call_members_prepared_cb() when * the initial members are prepared or when the hold state is retrived. */ } static void hold_state_changed_cb (TpChannel *proxy, guint arg_HoldState, guint arg_Reason, gpointer user_data, GObject *weak_object) { TpCallChannel *self = TP_CALL_CHANNEL (proxy); if (!self->priv->hold_state_retrieved) return; self->priv->hold_state = arg_HoldState; self->priv->hold_state_reason = arg_Reason; g_object_notify (G_OBJECT (proxy), "hold-state"); g_object_notify (G_OBJECT (proxy), "hold-state-reason"); } static void got_hold_state_cb (TpChannel *proxy, guint arg_HoldState, guint arg_Reason, const GError *error, gpointer user_data, GObject *weak_object) { TpCallChannel *self = TP_CALL_CHANNEL (proxy); if (error != NULL) { DEBUG ("Could not get the call channel hold state: %s", error->message); g_simple_async_result_set_from_error (self->priv->core_result, error); g_simple_async_result_complete (self->priv->core_result); g_clear_object (&self->priv->core_result); return; } self->priv->hold_state = arg_HoldState; self->priv->hold_state_reason = arg_Reason; self->priv->hold_state_retrieved = TRUE; channel_maybe_core_prepared (self); } static void _tp_call_channel_prepare_core_async (TpProxy *proxy, const TpProxyFeature *feature, GAsyncReadyCallback callback, gpointer user_data) { TpCallChannel *self = (TpCallChannel *) proxy; TpChannel *channel = (TpChannel *) self; tp_cli_channel_type_call_connect_to_content_added (channel, content_added_cb, NULL, NULL, NULL, NULL); tp_cli_channel_type_call_connect_to_content_removed (channel, content_removed_cb, NULL, NULL, NULL, NULL); tp_cli_channel_type_call_connect_to_call_state_changed (channel, call_state_changed_cb, NULL, NULL, NULL, NULL); tp_cli_channel_type_call_connect_to_call_members_changed (channel, call_members_changed_cb, NULL, NULL, NULL, NULL); g_assert (self->priv->core_result == NULL); self->priv->core_result = g_simple_async_result_new ((GObject *) self, callback, user_data, _tp_call_channel_prepare_core_async); tp_cli_dbus_properties_call_get_all (self, -1, TP_IFACE_CHANNEL_TYPE_CALL, got_all_properties_cb, NULL, NULL, NULL); if (tp_proxy_has_interface_by_id (proxy, TP_IFACE_QUARK_CHANNEL_INTERFACE_HOLD)) { tp_cli_channel_interface_hold_connect_to_hold_state_changed (channel, hold_state_changed_cb, NULL, NULL, NULL, NULL); tp_cli_channel_interface_hold_call_get_hold_state (channel, -1, got_hold_state_cb, NULL, NULL, NULL); } } static void tp_call_channel_constructed (GObject *obj) { TpCallChannel *self = (TpCallChannel *) obj; GHashTable *properties = _tp_channel_get_immutable_properties ( (TpChannel *) self); G_OBJECT_CLASS (tp_call_channel_parent_class)->constructed (obj); /* We can already set immutable properties */ self->priv->hardware_streaming = tp_asv_get_boolean (properties, TP_PROP_CHANNEL_TYPE_CALL_HARDWARE_STREAMING, NULL); self->priv->initial_audio = tp_asv_get_boolean (properties, TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, NULL); self->priv->initial_video = tp_asv_get_boolean (properties, TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, NULL); self->priv->initial_audio_name = g_strdup (tp_asv_get_string (properties, TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO_NAME)); self->priv->initial_video_name = g_strdup (tp_asv_get_string (properties, TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO_NAME)); self->priv->mutable_contents = tp_asv_get_boolean (properties, TP_PROP_CHANNEL_TYPE_CALL_MUTABLE_CONTENTS, NULL); if (!self->priv->initial_audio) tp_clear_pointer (&self->priv->initial_audio_name, g_free); if (!self->priv->initial_video) tp_clear_pointer (&self->priv->initial_video_name, g_free); } static void tp_call_channel_dispose (GObject *obj) { TpCallChannel *self = (TpCallChannel *) obj; g_assert (self->priv->core_result == NULL); tp_clear_pointer (&self->priv->contents, g_ptr_array_unref); tp_clear_pointer (&self->priv->state_details, g_hash_table_unref); tp_clear_pointer (&self->priv->state_reason, _tp_call_state_reason_unref); tp_clear_pointer (&self->priv->members, g_hash_table_unref); tp_clear_pointer (&self->priv->initial_audio_name, g_free); tp_clear_pointer (&self->priv->initial_video_name, g_free); G_OBJECT_CLASS (tp_call_channel_parent_class)->dispose (obj); } static void tp_call_channel_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { TpCallChannel *self = (TpCallChannel *) object; switch (property_id) { case PROP_CONTENTS: g_value_set_boxed (value, self->priv->contents); break; case PROP_STATE: g_value_set_uint (value, self->priv->state); break; case PROP_FLAGS: g_value_set_uint (value, self->priv->flags); break; case PROP_STATE_DETAILS: g_value_set_boxed (value, self->priv->state_details); break; case PROP_STATE_REASON: g_value_set_boxed (value, self->priv->state_reason); break; case PROP_HARDWARE_STREAMING: g_value_set_boolean (value, self->priv->hardware_streaming); break; case PROP_INITIAL_AUDIO: g_value_set_boolean (value, self->priv->initial_audio); break; case PROP_INITIAL_VIDEO: g_value_set_boolean (value, self->priv->initial_video); break; case PROP_INITIAL_AUDIO_NAME: g_value_set_string (value, self->priv->initial_audio_name); break; case PROP_INITIAL_VIDEO_NAME: g_value_set_string (value, self->priv->initial_video_name); break; case PROP_MUTABLE_CONTENTS: g_value_set_boolean (value, self->priv->mutable_contents); break; case PROP_HOLD_STATE: g_value_set_uint (value, self->priv->hold_state); break; case PROP_HOLD_STATE_REASON: g_value_set_uint (value, self->priv->hold_state_reason); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } enum { FEAT_CORE, N_FEAT }; static const TpProxyFeature * tp_call_channel_list_features (TpProxyClass *cls G_GNUC_UNUSED) { static TpProxyFeature features[N_FEAT + 1] = { { 0 } }; if (G_LIKELY (features[0].name != 0)) return features; /* started from constructed */ features[FEAT_CORE].name = TP_CALL_CHANNEL_FEATURE_CORE; features[FEAT_CORE].prepare_async = _tp_call_channel_prepare_core_async; features[FEAT_CORE].core = TRUE; /* assert that the terminator at the end is there */ g_assert (features[N_FEAT].name == 0); return features; } static void tp_call_channel_class_init (TpCallChannelClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); TpProxyClass *proxy_class = (TpProxyClass *) klass; GParamSpec *param_spec; gobject_class->constructed = tp_call_channel_constructed; gobject_class->get_property = tp_call_channel_get_property; gobject_class->dispose = tp_call_channel_dispose; proxy_class->list_features = tp_call_channel_list_features; g_type_class_add_private (gobject_class, sizeof (TpCallChannelPrivate)); /* FIXME: Should be annoted with * * Type: GLib.PtrArray * Transfer: container * * But it does not work (bgo#663846) and makes gtkdoc fail myserably. */ /** * TpCallChannel:contents: * * #GPtrArray of #TpCallContent objects. The list of content objects that are * part of this call. * * It is NOT guaranteed that %TP_CALL_CONTENT_FEATURE_CORE is prepared on * those objects. * * Since: 0.17.5 */ param_spec = g_param_spec_boxed ("contents", "Contents", "The content objects of this call", G_TYPE_PTR_ARRAY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_CONTENTS, param_spec); /** * TpCallChannel:state: * * A #TpCallState specifying the state of the call. * * Since: 0.17.5 */ param_spec = g_param_spec_uint ("state", "Call state", "The state of the call", 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_STATE, param_spec); /** * TpCallChannel:flags: * * A #TpCallFlags specifying the flags of the call state. * * Since: 0.17.5 */ param_spec = g_param_spec_uint ("flags", "Call flags", "The flags of the call", 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_FLAGS, param_spec); /** * TpCallChannel:state-details: * * Detailed infoermation about #TpCallChannel:state. It is a #GHashTable * mapping gchar*->GValue, it can be accessed using the tp_asv_* functions. * * Since: 0.17.5 */ param_spec = g_param_spec_boxed ("state-details", "State details", "The details of the call", G_TYPE_HASH_TABLE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_STATE_DETAILS, param_spec); /** * TpCallChannel:state-reason: * * Reason why #TpCallChannel:state last changed. * * Since: 0.17.5 */ param_spec = g_param_spec_boxed ("state-reason", "State reason", "The reason of the call's state", TP_TYPE_CALL_STATE_REASON, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_STATE_REASON, param_spec); /** * TpCallChannel:hardware-streaming: * * Whether or not the streaming is done by dedicated hardware. * * Since: 0.17.5 */ param_spec = g_param_spec_boolean ("hardware-streaming", "Hardware streaming", "Hardware streaming", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_HARDWARE_STREAMING, param_spec); /** * TpCallChannel:initial-audio: * * Whether or not the Call was started with audio. * * Since: 0.17.5 */ param_spec = g_param_spec_boolean ("initial-audio", "Initial audio", "Initial audio", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_INITIAL_AUDIO, param_spec); /** * TpCallChannel:initial-video: * * Whether or not the Call was started with video. * * Since: 0.17.5 */ param_spec = g_param_spec_boolean ("initial-video", "Initial video", "Initial video", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_INITIAL_VIDEO, param_spec); /** * TpCallChannel:initial-audio-name: * * If #TpCallChannel:initial-audio is set to %TRUE, then this property will * is the name of the intial audio content, %NULL otherwise. * * Since: 0.17.5 */ param_spec = g_param_spec_string ("initial-audio-name", "Initial audio name", "Initial audio name", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_INITIAL_AUDIO_NAME, param_spec); /** * TpCallChannel:initial-video-name: * * If #TpCallChannel:initial-video is set to %TRUE, then this property will * is the name of the intial video content, %NULL otherwise. * * Since: 0.17.5 */ param_spec = g_param_spec_string ("initial-video-name", "Initial video name", "Initial video name", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_INITIAL_VIDEO_NAME, param_spec); /** * TpCallChannel:mutable-contents: * * Whether or not call contents can be added or removed. * * Since: 0.17.5 */ param_spec = g_param_spec_boolean ("mutable-contents", "Mutable contents", "Mutable contents", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_MUTABLE_CONTENTS, param_spec); /** * TpCallChannel:hold-state: * * A #TpLocalHoldState specifying if the Call is currently held * * Since: 0.17.6 */ param_spec = g_param_spec_uint ("hold-state", "Hold State", "The Hold state of the call", 0, G_MAXUINT, TP_LOCAL_HOLD_STATE_UNHELD, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_HOLD_STATE, param_spec); /** * TpCallChannel:hold-state-reason: * * A #TpLocalHoldStateReason specifying why the Call is currently held. * * Since: 0.17.6 */ param_spec = g_param_spec_uint ("hold-state-reason", "Hold State Reason", "The reason for the current hold state", 0, G_MAXUINT, TP_LOCAL_HOLD_STATE_REASON_NONE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_HOLD_STATE_REASON, param_spec); /** * TpCallChannel::content-added: * @self: the #TpCallChannel * @content: the newly added #TpCallContent * * The ::content-added signal is emitted whenever a * #TpCallContent is added to @self. * * It is NOT guaranteed that %TP_CALL_CONTENT_FEATURE_CORE is prepared on * @content. * * Since: 0.17.5 */ _signals[CONTENT_ADDED] = g_signal_new ("content-added", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT); /** * TpCallChannel::content-removed: * @self: the #TpCallChannel * @content: the newly removed #TpCallContent * @reason: a #TpCallStateReason * * The ::content-removed signal is emitted whenever a * #TpCallContent is removed from @self. * * It is NOT guaranteed that %TP_CALL_CONTENT_FEATURE_CORE is prepared on * @content. * * Since: 0.17.5 */ _signals[CONTENT_REMOVED] = g_signal_new ("content-removed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_OBJECT, TP_TYPE_CALL_STATE_REASON); /** * TpCallChannel::state-changed: * @self: the #TpCallChannel * @state: the new #TpCallState * @flags: the new #TpCallFlags * @reason: the #TpCallStateReason for the change * @details: (element-type utf8 GObject.Value): additional details as a * #GHashTable readable using the tp_asv_* functions. * * The ::state-changed signal is emitted whenever the * call state changes. * * Since: 0.17.5 */ _signals[STATE_CHANGED] = g_signal_new ("state-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_UINT, TP_TYPE_CALL_STATE_REASON, G_TYPE_HASH_TABLE); /** * TpCallChannel::members-changed: * @self: the #TpCallChannel * @updates: (type GLib.HashTable) (element-type TelepathyGLib.Contact uint): * #GHashTable mapping #TpContact to its new #TpCallMemberFlags * @removed: (type GLib.PtrArray) (element-type TelepathyGLib.Contact): * #GPtrArray of #TpContact removed from the call members * @reason: the #TpCallStateReason for the change * * The ::members-changed signal is emitted whenever the call's members * changes. * * The #TpContact objects are guaranteed to have all of the features * previously passed to tp_simple_client_factory_add_contact_features() * prepared. * * Since: 0.17.5 */ _signals[MEMBERS_CHANGED] = g_signal_new ("members-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_HASH_TABLE, G_TYPE_PTR_ARRAY, TP_TYPE_CALL_STATE_REASON); } static void tp_call_channel_init (TpCallChannel *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), TP_TYPE_CALL_CHANNEL, TpCallChannelPrivate); self->priv->contents = g_ptr_array_new_with_free_func (g_object_unref); self->priv->members = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL); } TpCallChannel * _tp_call_channel_new_with_factory (TpSimpleClientFactory *factory, TpConnection *conn, const gchar *object_path, const GHashTable *immutable_properties, GError **error) { TpProxy *conn_proxy = (TpProxy *) conn; g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL); g_return_val_if_fail (object_path != NULL, NULL); g_return_val_if_fail (immutable_properties != NULL, NULL); if (!tp_dbus_check_valid_object_path (object_path, error)) return NULL; return g_object_new (TP_TYPE_CALL_CHANNEL, "connection", conn, "dbus-daemon", conn_proxy->dbus_daemon, "bus-name", conn_proxy->bus_name, "object-path", object_path, "handle-type", (guint) TP_UNKNOWN_HANDLE_TYPE, "channel-properties", immutable_properties, "factory", factory, NULL); } /** * TP_CALL_CHANNEL_FEATURE_CORE: * * Expands to a call to a function that returns a quark for the "core" * feature on a #TpCallChannel. * * One can ask for a feature to be prepared using the tp_proxy_prepare_async() * function, and waiting for it to trigger the callback. */ GQuark tp_call_channel_get_feature_quark_core (void) { return g_quark_from_static_string ("tp-call-channel-feature-core"); } /** * tp_call_channel_get_contents: * @self: a #TpCallChannel * * * * Returns: (transfer none) (type GLib.PtrArray) (element-type TelepathyGLib.CallContent): * the value of #TpCallChannel:contents * Since: 0.17.5 */ GPtrArray * tp_call_channel_get_contents (TpCallChannel *self) { g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), NULL); return self->priv->contents; } /** * tp_call_channel_get_state: * @self: a #TpCallChannel * @flags: (out) (allow-none) (transfer none): a place to set the value of * #TpCallChannel:flags * @details: (out) (allow-none) (transfer none): a place to set the value of * #TpCallChannel:state-details * @reason: (out) (allow-none) (transfer none): a place to set the value of * #TpCallChannel:state-reason * * * * Returns: the value of #TpCallChannel:state * Since: 0.17.5 */ TpCallState tp_call_channel_get_state (TpCallChannel *self, TpCallFlags *flags, GHashTable **details, TpCallStateReason **reason) { g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), TP_CALL_STATE_UNKNOWN); if (flags != NULL) *flags = self->priv->flags; if (details != NULL) *details = self->priv->state_details; if (reason != NULL) *reason = self->priv->state_reason; return self->priv->state; } /** * tp_call_channel_has_hardware_streaming: * @self: a #TpCallChannel * * * * Returns: the value of #TpCallChannel:hardware-streaming * Since: 0.17.5 */ gboolean tp_call_channel_has_hardware_streaming (TpCallChannel *self) { g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), FALSE); return self->priv->hardware_streaming; } /** * tp_call_channel_has_initial_audio: * @self: a #TpCallChannel * @initial_audio_name: (out) (allow-none) (transfer none): a place to set the * value of #TpCallChannel:initial-audio-name * * * * Returns: the value of #TpCallChannel:initial-audio * Since: 0.17.5 */ gboolean tp_call_channel_has_initial_audio (TpCallChannel *self, const gchar **initial_audio_name) { g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), FALSE); if (initial_audio_name != NULL) *initial_audio_name = self->priv->initial_audio_name; return self->priv->initial_audio; } /** * tp_call_channel_has_initial_video: * @self: a #TpCallChannel * @initial_video_name: (out) (allow-none) (transfer none): a place to set the * value of #TpCallChannel:initial-video-name * * * * Returns: the value of #TpCallChannel:initial-video * Since: 0.17.5 */ gboolean tp_call_channel_has_initial_video (TpCallChannel *self, const gchar **initial_video_name) { g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), FALSE); if (initial_video_name != NULL) *initial_video_name = self->priv->initial_video_name; return self->priv->initial_video; } /** * tp_call_channel_has_mutable_contents: * @self: a #TpCallChannel * * * * Returns: the value of #TpCallChannel:mutable-contents * Since: 0.17.5 */ gboolean tp_call_channel_has_mutable_contents (TpCallChannel *self) { g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), FALSE); return self->priv->mutable_contents; } /** * tp_call_channel_get_members: * @self: a #TpCallChannel * * Get the members of this call. * * The #TpContact objects are guaranteed to have all of the features * previously passed to tp_simple_client_factory_add_contact_features() * prepared. * * Returns: (transfer none) (type GLib.HashTable) (element-type TelepathyGLib.Contact uint): * #GHashTable mapping #TpContact to its new #TpCallMemberFlags * Since: 0.17.5 */ GHashTable * tp_call_channel_get_members (TpCallChannel *self) { g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), NULL); return self->priv->members; } /** * tp_call_channel_has_dtmf: * @self: a #TpCallChannel * * Whether or not @self can send DTMF tones using * tp_call_channel_send_tones_async(). To be able to send DTMF tones, at least * one of @self's #TpCallChannel:contents must implement * %TP_IFACE_CALL_CONTENT_INTERFACE_DTMF interface. * * Returns: whether or not @self can send DTMF tones. * Since: 0.17.5 */ gboolean tp_call_channel_has_dtmf (TpCallChannel *self) { guint i; g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), FALSE); for (i = 0; i < self->priv->contents->len; i++) { TpCallContent *content = g_ptr_array_index (self->priv->contents, i); if (tp_proxy_has_interface_by_id (content, TP_IFACE_QUARK_CALL_CONTENT_INTERFACE_DTMF)) return TRUE; } return FALSE; } /** * tp_call_channel_has_hold: * @self: a #TpCallChannel * * Whether or not @self has the %TP_IFACE_CHANNEL_INTERFACE_HOLD * interfaces * * Returns: whether or not @self supports Hold * Since: 0.17.6 */ gboolean tp_call_channel_has_hold (TpCallChannel *self) { g_return_val_if_fail (TP_IS_CALL_CHANNEL (self), FALSE); g_return_val_if_fail ( tp_proxy_is_prepared (self, TP_CALL_CHANNEL_FEATURE_CORE), FALSE); return tp_proxy_has_interface_by_id (self, TP_IFACE_QUARK_CHANNEL_INTERFACE_HOLD); } static void generic_async_cb (TpChannel *channel, const GError *error, gpointer user_data, GObject *weak_object) { GSimpleAsyncResult *result = user_data; if (error != NULL) { DEBUG ("Error: %s", error->message); g_simple_async_result_set_from_error (result, error); } g_simple_async_result_complete (result); } /** * tp_call_channel_set_ringing_async: * @self: a #TpCallChannel * @callback: a callback to call when the operation finishes * @user_data: data to pass to @callback * * Indicate that the local user has been alerted about the incoming call. * * Since: 0.17.5 */ void tp_call_channel_set_ringing_async (TpCallChannel *self, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; g_return_if_fail (TP_IS_CALL_CHANNEL (self)); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_call_channel_set_ringing_async); tp_cli_channel_type_call_call_set_ringing (TP_CHANNEL (self), -1, generic_async_cb, result, g_object_unref, G_OBJECT (self)); } /** * tp_call_channel_set_ringing_finish: * @self: a #TpCallChannel * @result: a #GAsyncResult * @error: a #GError to fill * * Finishes tp_call_channel_set_ringing_async(). * * Since: 0.17.5 */ gboolean tp_call_channel_set_ringing_finish (TpCallChannel *self, GAsyncResult *result, GError **error) { _tp_implement_finish_void (self, tp_call_channel_set_ringing_async); } /** * tp_call_channel_set_queued_async: * @self: a #TpCallChannel * @callback: a callback to call when the operation finishes * @user_data: data to pass to @callback * * Notifies the CM that the local user is already in a call, so this call has * been put in a call-waiting style queue. * * Since: 0.17.5 */ void tp_call_channel_set_queued_async (TpCallChannel *self, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; g_return_if_fail (TP_IS_CALL_CHANNEL (self)); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_call_channel_set_queued_async); tp_cli_channel_type_call_call_set_queued (TP_CHANNEL (self), -1, generic_async_cb, result, g_object_unref, G_OBJECT (self)); } /** * tp_call_channel_set_queued_finish: * @self: a #TpCallChannel * @result: a #GAsyncResult * @error: a #GError to fill * * Finishes tp_call_channel_set_queued_async(). * * Since: 0.17.5 */ gboolean tp_call_channel_set_queued_finish (TpCallChannel *self, GAsyncResult *result, GError **error) { _tp_implement_finish_void (self, tp_call_channel_set_queued_async); } /** * tp_call_channel_accept_async: * @self: a #TpCallChannel * @callback: a callback to call when the operation finishes * @user_data: data to pass to @callback * * For incoming calls with #TpCallChannel:state set to * %TP_CALL_STATE_INITIALISED, accept the incoming call. This changes * #TpCallChannel:state to %TP_CALL_STATE_ACCEPTED. * * For outgoing calls with #TpCallChannel:state set to * %TP_CALL_STATE_PENDING_INITIATOR, actually call the remote contact; this * changes #TpCallChannel:state to * %TP_CALL_STATE_INITIALISING. * * Since: 0.17.5 */ void tp_call_channel_accept_async (TpCallChannel *self, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; g_return_if_fail (TP_IS_CALL_CHANNEL (self)); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_call_channel_accept_async); tp_cli_channel_type_call_call_accept (TP_CHANNEL (self), -1, generic_async_cb, result, g_object_unref, G_OBJECT (self)); } /** * tp_call_channel_accept_finish: * @self: a #TpCallChannel * @result: a #GAsyncResult * @error: a #GError to fill * * Finishes tp_call_channel_accept_async(). * * Since: 0.17.5 */ gboolean tp_call_channel_accept_finish (TpCallChannel *self, GAsyncResult *result, GError **error) { _tp_implement_finish_void (self, tp_call_channel_accept_async); } /** * tp_call_channel_hangup_async: * @self: a #TpCallChannel * @reason: a TpCallStateChangeReason * @detailed_reason: a more specific reason for the call hangup, if one is * available, or an empty or %NULL string otherwise * @message: a human-readable message to be sent to the remote contact(s) * @callback: a callback to call when the operation finishes * @user_data: data to pass to @callback * * Request that the call is ended. All contents will be removed from @self so * that the #TpCallChannel:contents property will be the empty list. * * Since: 0.17.5 */ void tp_call_channel_hangup_async (TpCallChannel *self, TpCallStateChangeReason reason, const gchar *detailed_reason, const gchar *message, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; g_return_if_fail (TP_IS_CALL_CHANNEL (self)); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_call_channel_hangup_async); tp_cli_channel_type_call_call_hangup (TP_CHANNEL (self), -1, reason, detailed_reason, message, generic_async_cb, result, g_object_unref, G_OBJECT (self)); } /** * tp_call_channel_hangup_finish: * @self: a #TpCallChannel * @result: a #GAsyncResult * @error: a #GError to fill * * Finishes tp_call_channel_hangup_async(). * * Since: 0.17.5 */ gboolean tp_call_channel_hangup_finish (TpCallChannel *self, GAsyncResult *result, GError **error) { _tp_implement_finish_void (self, tp_call_channel_hangup_async); } static void add_content_cb (TpChannel *channel, const gchar *object_path, const GError *error, gpointer user_data, GObject *weak_object) { TpCallChannel *self = (TpCallChannel *) channel; GSimpleAsyncResult *result = user_data; if (error != NULL) { DEBUG ("Error: %s", error->message); g_simple_async_result_set_from_error (result, error); } else { g_simple_async_result_set_op_res_gpointer (result, g_object_ref (ensure_content (self, object_path)), g_object_unref); } g_simple_async_result_complete (result); } /** * tp_call_channel_add_content_async: * @self: a #TpCallChannel * @name: the suggested name of the content to add * @type: the media stream type of the content to be added to the call, from * #TpMediaStreamType * @initial_direction: The initial direction of the content * @callback: a callback to call when the operation finishes * @user_data: data to pass to @callback * * Request that a new Content of type @type is added to @self. Callers should * check the value of the #TpCallChannel:mutable-contents property before trying * to add another content as it might not be allowed. * * Since: 0.17.5 */ void tp_call_channel_add_content_async (TpCallChannel *self, const gchar *name, TpMediaStreamType type, TpMediaStreamDirection initial_direction, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; g_return_if_fail (TP_IS_CALL_CHANNEL (self)); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_call_channel_add_content_async); tp_cli_channel_type_call_call_add_content (TP_CHANNEL (self), -1, name, type, initial_direction, add_content_cb, result, g_object_unref, G_OBJECT (self)); } /** * tp_call_channel_add_content_finish: * @self: a #TpCallChannel * @result: a #GAsyncResult * @error: a #GError to fill * * Finishes tp_call_channel_add_content_async(). * * The returned #TpCallContent is NOT guaranteed to have * %TP_CALL_CONTENT_FEATURE_CORE prepared. * * Returns: (transfer full): reference to the new #TpCallContent. * Since: 0.17.5 */ TpCallContent * tp_call_channel_add_content_finish (TpCallChannel *self, GAsyncResult *result, GError **error) { _tp_implement_finish_return_copy_pointer (self, tp_call_channel_add_content_async, g_object_ref); } static void send_tones_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GSimpleAsyncResult *result = user_data; guint count; GError *error = NULL; if (!tp_call_content_send_tones_finish ((TpCallContent *) source, res, &error)) g_simple_async_result_take_error (result, error); /* Decrement the op count */ count = GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (result)); g_simple_async_result_set_op_res_gpointer (result, GUINT_TO_POINTER (--count), NULL); if (count == 0) g_simple_async_result_complete (result); g_object_unref (result); } /** * tp_call_channel_send_tones_async: * @self: a #TpCallChannel * @tones: a string representation of one or more DTMF events. * @cancellable: optional #GCancellable object, %NULL to ignore * @callback: a callback to call when the operation finishes * @user_data: data to pass to @callback * * Send @tones on every of @self's contents which have the * %TP_IFACE_CALL_CONTENT_INTERFACE_DTMF interface. * * For more details, see tp_call_content_send_tones_async(). * * Since: 0.17.5 */ void tp_call_channel_send_tones_async (TpCallChannel *self, const gchar *tones, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; guint i; guint count = 0; g_return_if_fail (TP_IS_CALL_CHANNEL (self)); g_return_if_fail (tp_call_channel_has_dtmf (self)); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_call_channel_send_tones_async); for (i = 0; i < self->priv->contents->len; i++) { TpCallContent *content = g_ptr_array_index (self->priv->contents, i); if (!tp_proxy_has_interface_by_id (content, TP_IFACE_QUARK_CALL_CONTENT_INTERFACE_DTMF)) continue; count++; tp_call_content_send_tones_async (content, tones, cancellable, send_tones_cb, g_object_ref (result)); } g_assert (count > 0); g_simple_async_result_set_op_res_gpointer (result, GUINT_TO_POINTER (count), NULL); g_object_unref (result); } /** * tp_call_channel_send_tones_finish: * @self: a #TpCallChannel * @result: a #GAsyncResult * @error: a #GError to fill * * Finishes tp_call_channel_send_tones_async(). * * Returns: %TRUE on success, %FALSE otherwise. * Since: 0.17.5 */ gboolean tp_call_channel_send_tones_finish (TpCallChannel *self, GAsyncResult *result, GError **error) { _tp_implement_finish_void (self, tp_call_channel_send_tones_async) } /** * tp_call_channel_request_hold_async: * @self: a #TpCallChannel * @hold: Whether to request a hold or a unhold * @callback: a callback to call when the operation finishes * @user_data: data to pass to @callback * * Requests that the connection manager holds or unholds the call. Watch * #TpCallChannel:hold-state property to know when the channel goes on * hold or is unheld. Unholding may fail if the streaming implementation * can not obtain all the resources needed to restart the call. * * Since: 0.17.6 */ void tp_call_channel_request_hold_async (TpCallChannel *self, gboolean hold, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; g_return_if_fail (TP_IS_CALL_CHANNEL (self)); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_call_channel_request_hold_async); if (tp_call_channel_has_hold (self)) { tp_cli_channel_interface_hold_call_request_hold (TP_CHANNEL (self), -1, hold, generic_async_cb, g_object_ref (result), g_object_unref, G_OBJECT (self)); } else { g_simple_async_result_set_error (result, TP_ERROR, TP_ERROR_NOT_CAPABLE, "Channel does NOT implement the Hold interface"); g_simple_async_result_complete_in_idle (result); } g_object_unref (result); } /** * tp_call_channel_request_hold_finish: * @self: a #TpCallChannel * @result: a #GAsyncResult * @error: a #GError to fill * * Finishes tp_call_channel_request_hold_async * * Since: 0.17.6 */ gboolean tp_call_channel_request_hold_finish (TpCallChannel *self, GAsyncResult *result, GError **error) { _tp_implement_finish_void (self, tp_call_channel_request_hold_async); }