/* * call-channel.c - Source for TpyCallChannel * Copyright (C) 2011 Collabora Ltd. * @author Emilio Pozuelo Monfort * * 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: TpyCallChannel * @short_description: help class for a call channel * * #TpyCallChannel is a sub-class of #TpChannel providing convenient API * to make audio and video calls. * * Since: */ /** * TpyCallChannel: * * Data structure representing a #TpyCallChannel. * * Since: */ /** * TpyCallChannelClass: * * The class of a #TpyCallChannel. * * Since: */ #include #include "telepathy-yell/call-channel.h" #include "telepathy-yell/call-stream.h" #include #include #include #include #include #include #include "extensions.h" #include "interfaces.h" #include "_gen/signals-marshal.h" #define DEBUG_FLAG TPY_DEBUG_CALL #include "debug.h" G_DEFINE_TYPE (TpyCallChannel, tpy_call_channel, TP_TYPE_CHANNEL) struct _TpyCallChannelPrivate { TpyCallState state; TpyCallFlags flags; GHashTable *details; gboolean initial_audio; gboolean initial_video; /* Hash of Handle => CallMemberFlags */ GHashTable *members; /* Array of TpyCallContents */ GPtrArray *contents; gboolean properties_retrieved; gboolean ready; }; enum /* props */ { PROP_CONTENTS = 1, PROP_STATE, PROP_STATE_DETAILS, PROP_STATE_REASON, PROP_FLAGS, PROP_HARDWARE_STREAMING, PROP_MEMBERS, PROP_INITIAL_TRANSPORT, PROP_INITIAL_AUDIO, PROP_INITIAL_AUDIO_NAME, PROP_INITIAL_VIDEO, PROP_INITIAL_VIDEO_NAME, PROP_MUTABLE_CONTENTS, PROP_READY }; enum /* signals */ { CONTENT_ADDED, CONTENT_REMOVED, STATE_CHANGED, MEMBERS_CHANGED, LAST_SIGNAL }; static guint _signals[LAST_SIGNAL] = { 0, }; static void update_call_members (TpyCallChannel *self, GHashTable *flags_changed, const GArray *removed) { GHashTableIter iter; gpointer key, value; TpHandle handle; guint i; if (flags_changed != NULL) { g_hash_table_iter_init (&iter, flags_changed); while (g_hash_table_iter_next (&iter, &key, &value)) g_hash_table_insert (self->priv->members, key, value); } if (removed != NULL) { for (i = 0; i < removed->len; i++) { handle = g_array_index (removed, TpHandle, i); g_hash_table_remove (self->priv->members, GUINT_TO_POINTER (handle)); } } } static void maybe_go_to_ready (TpyCallChannel *self) { TpyCallChannelPrivate *priv = self->priv; guint i; if (priv->ready) return; if (!priv->properties_retrieved) return; for (i = 0 ; i < priv->contents->len; i++) { TpyCallContent *c = g_ptr_array_index (self->priv->contents, i); gboolean ready; g_object_get (c, "ready", &ready, NULL); if (!ready) return; } priv->ready = TRUE; g_object_notify (G_OBJECT (self), "ready"); } static void on_content_ready_cb (TpyCallContent *content, GParamSpec *spec, TpyCallChannel *self) { maybe_go_to_ready (self); } static void on_content_added_cb (TpProxy *proxy, const gchar *content_path, gpointer user_data, GObject *weak_object) { TpyCallChannel *self = TPY_CALL_CHANNEL (proxy); TpyCallContent *content; DEBUG ("Content added: %s", content_path); content = g_object_new (TPY_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", content_path, NULL); if (content == NULL) { g_warning ("Could not create a CallContent for path %s", content_path); return; } g_ptr_array_add (self->priv->contents, content); tp_g_signal_connect_object (content, "notify::ready", G_CALLBACK (on_content_ready_cb), self, 0); g_signal_emit (self, _signals[CONTENT_ADDED], 0, content); } static void on_content_removed_cb (TpProxy *proxy, const gchar *content_path, gpointer user_data, GObject *weak_object) { TpyCallChannel *self = TPY_CALL_CHANNEL (proxy); TpyCallContent *content = NULL, *c; guint i; DEBUG ("Content removed: %s", content_path); for (i = 0; i < self->priv->contents->len; i++) { c = g_ptr_array_index (self->priv->contents, i); if (g_strcmp0 (tp_proxy_get_object_path (c), content_path) == 0) { content = c; break; } } if (content != NULL) { g_signal_emit (self, _signals[CONTENT_REMOVED], 0, content); g_ptr_array_remove (self->priv->contents, content); } else { g_warning ("The removed content '%s' isn't in the call!", content_path); } } static void on_call_members_changed_cb (TpProxy *proxy, GHashTable *flags_changed, const GArray *removed, gpointer user_data, GObject *weak_object) { TpyCallChannel *self = TPY_CALL_CHANNEL (proxy); DEBUG ("Call members: %d changed, %d removed", g_hash_table_size (flags_changed), removed->len); update_call_members (self, flags_changed, removed); g_signal_emit (self, _signals[MEMBERS_CHANGED], 0, self->priv->members); } static void on_call_state_changed_cb (TpProxy *proxy, guint call_state, guint call_flags, const GValueArray *call_state_reason, GHashTable *call_state_details, gpointer user_data, GObject *weak_object) { TpyCallChannel *self = TPY_CALL_CHANNEL (proxy); DEBUG ("Call state changed"); self->priv->state = call_state; self->priv->flags = call_flags; tp_clear_pointer (&self->priv->details, g_hash_table_unref); self->priv->details = g_hash_table_ref (call_state_details); g_signal_emit (self, _signals[STATE_CHANGED], 0, call_state, call_flags, call_state_reason, call_state_details); } static void on_call_channel_get_all_properties_cb (TpProxy *proxy, GHashTable *properties, const GError *error, gpointer user_data, GObject *weak_object) { TpyCallChannel *self = TPY_CALL_CHANNEL (proxy); GSimpleAsyncResult *result = user_data; GHashTable *hash_table; GPtrArray *contents; guint i; if (error != NULL) { g_warning ("Could not get the channel properties: %s", error->message); g_simple_async_result_set_from_error (result, error); goto out; } self->priv->state = tp_asv_get_uint32 (properties, "CallState", NULL); self->priv->flags = tp_asv_get_uint32 (properties, "CallFlags", NULL); self->priv->initial_audio = tp_asv_get_boolean (properties, "InitialAudio", NULL); self->priv->initial_video = tp_asv_get_boolean (properties, "InitialVideo", NULL); hash_table = tp_asv_get_boxed (properties, "CallStateDetails", TP_HASH_TYPE_STRING_VARIANT_MAP); if (hash_table != NULL) self->priv->details = g_boxed_copy (TP_HASH_TYPE_STRING_VARIANT_MAP, hash_table); hash_table = tp_asv_get_boxed (properties, "CallMembers", TPY_HASH_TYPE_CALL_MEMBER_MAP); update_call_members (self, hash_table, NULL); contents = tp_asv_get_boxed (properties, "Contents", TP_ARRAY_TYPE_OBJECT_PATH_LIST); for (i = 0; i < contents->len; i++) { const gchar *content_path = g_ptr_array_index (contents, i); TpyCallContent *content; DEBUG ("Content added: %s", content_path); content = g_object_new (TPY_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", content_path, NULL); if (content == NULL) { g_warning ("Could not create a CallContent for path %s", content_path); g_simple_async_result_set_error (result, TP_ERRORS, TP_ERROR_CONFUSED, "Could not create a CallContent for path %s", content_path); goto out; } g_ptr_array_add (self->priv->contents, content); tp_g_signal_connect_object (content, "notify::ready", G_CALLBACK (on_content_ready_cb), self, 0); } g_signal_emit (self, _signals[MEMBERS_CHANGED], 0, self->priv->members); self->priv->properties_retrieved = TRUE; maybe_go_to_ready (self); out: /* TODO; ideally we should get rid of the ready property and complete once * all the contents have been prepared. Or maybe that should be another * feature? */ g_simple_async_result_complete (result); } static void tpy_call_channel_dispose (GObject *obj) { TpyCallChannel *self = (TpyCallChannel *) obj; tp_clear_pointer (&self->priv->contents, g_ptr_array_unref); tp_clear_pointer (&self->priv->details, g_hash_table_unref); tp_clear_pointer (&self->priv->members, g_hash_table_unref); G_OBJECT_CLASS (tpy_call_channel_parent_class)->dispose (obj); } static void tpy_call_channel_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { TpyCallChannel *self = (TpyCallChannel *) 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->details); break; case PROP_MEMBERS: g_value_set_boxed (value, self->priv->members); 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_READY: g_value_set_boolean (value, self->priv->ready); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void tpy_call_channel_prepare_core_async (TpProxy *proxy, const TpProxyFeature *feature, GAsyncReadyCallback callback, gpointer user_data) { TpyCallChannel *self = (TpyCallChannel *) proxy; GSimpleAsyncResult *result; GError *err = NULL; result = g_simple_async_result_new ((GObject *) proxy, callback, user_data, tpy_call_channel_prepare_core_async); tpy_cli_channel_type_call_connect_to_content_added (TP_PROXY (self), on_content_added_cb, NULL, NULL, NULL, &err); if (err != NULL) { g_critical ("Failed to connect to ContentAdded signal: %s", err->message); goto failed; } tpy_cli_channel_type_call_connect_to_content_removed (TP_PROXY (self), on_content_removed_cb, NULL, NULL, NULL, &err); if (err != NULL) { g_critical ("Failed to connect to ContentRemoved signal: %s", err->message); goto failed; } tpy_cli_channel_type_call_connect_to_call_state_changed (TP_PROXY (self), on_call_state_changed_cb, NULL, NULL, NULL, &err); if (err != NULL) { g_critical ("Failed to connect to CallStateChanged signal: %s", err->message); goto failed; } tpy_cli_channel_type_call_connect_to_call_members_changed (TP_PROXY (self), on_call_members_changed_cb, NULL, NULL, NULL, &err); if (err != NULL) { g_critical ("Failed to connect to CallMembersChanged signal: %s", err->message); goto failed; } tp_cli_dbus_properties_call_get_all (self, -1, TPY_IFACE_CHANNEL_TYPE_CALL, on_call_channel_get_all_properties_cb, result, g_object_unref, NULL); return; failed: g_simple_async_result_take_error (result, err); g_simple_async_result_complete_in_idle (result); g_object_unref (result); } enum { FEAT_CORE, N_FEAT }; static const TpProxyFeature * tpy_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; features[FEAT_CORE].name = TPY_CALL_CHANNEL_FEATURE_CORE; features[FEAT_CORE].prepare_async = tpy_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 tpy_call_channel_class_init (TpyCallChannelClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); TpProxyClass *proxy_class = (TpProxyClass *) klass; GParamSpec *param_spec; gobject_class->get_property = tpy_call_channel_get_property; gobject_class->dispose = tpy_call_channel_dispose; proxy_class->list_features = tpy_call_channel_list_features; g_type_class_add_private (klass, sizeof (TpyCallChannelPrivate)); /** * TpyCallChannel:contents: * * The list of content objects that are part of this call. * * Since: */ 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); /** * TpyCallChannel:state: * * A #TpChannelCallState specifying the state of the call. * * Since: */ 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); /** * TpyCallChannel:flags: * * A #TpChannelCallFlags specifying the flags of the call. * * Since: */ param_spec = g_param_spec_uint ("flags", "Call flags", "The flags for the call", 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_FLAGS, param_spec); /** * TpyCallChannel:state-details: * * The list of content objects that are part of this call. * * Since: */ 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); /** * TpyCallChannel:members: * * The call participants, and their respective flags. * * Since: */ param_spec = g_param_spec_boxed ("members", "Call members", "The call participants", G_TYPE_HASH_TABLE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_MEMBERS, param_spec); /** * TpyCallChannel:initial-audio: * * Whether or not the Call was started with audio. * * Since: */ 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); /** * TpyCallChannel:initial-video: * * Whether or not the Call was started with video. * * Since: */ 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); /** * TpyCallChannel:ready: * * Whether or call channel got all its async information * * Since: */ param_spec = g_param_spec_boolean ("ready", "Ready", "If true the call channel and all its contents have retrieved all " "all async information from the CM", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_READY, param_spec); /** * TpyCallChannel::content-added * @self: the #TpyCallChannel * @content: the newly added content * * The ::content-added signal is emitted whenever a * #TpyCallContent is added to @self. */ _signals[CONTENT_ADDED] = g_signal_new ("content-added", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); /** * TpyCallChannel::content-removed * @self: the #TpyCallChannel * @content: the newly removed content * * The ::content-removed signal is emitted whenever a * #TpyCallContent is removed from @self. */ _signals[CONTENT_REMOVED] = g_signal_new ("content-removed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); /** * TpyCallChannel::state-changed * @self: the #TpyCallChannel * @state: the new #TpyCallState * @flags: the new #TpyCallFlags * @state_reason: the reason for the change * @state_details: additional details * * The ::state-changed signal is emitted whenever the * call state changes. */ _signals[STATE_CHANGED] = g_signal_new ("state-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, _tpy_marshal_VOID__UINT_UINT_BOXED_BOXED, G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_VALUE_ARRAY, G_TYPE_HASH_TABLE); /** * TpyCallChannel::members-changed * @self: the #TpyCallChannel * @members: the call members * * The ::members-changed signal is emitted whenever participants * are added, removed, or their flags change. */ _signals[MEMBERS_CHANGED] = g_signal_new ("members-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, G_TYPE_HASH_TABLE); } static void tpy_call_channel_init (TpyCallChannel *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TPY_TYPE_CALL_CHANNEL, TpyCallChannelPrivate); self->priv->contents = g_ptr_array_new_with_free_func (g_object_unref); self->priv->members = g_hash_table_new (g_direct_hash, g_direct_equal); } /** * tpy_call_channel_new: * @conn: a #TpConnection; may not be %NULL * @object_path: the object path of the channel; may not be %NULL * @immutable_properties: (transfer none) (element-type utf8 GObject.Value): * the immutable properties of the channel, * as signalled by the NewChannel D-Bus signal or returned by the * CreateChannel and EnsureChannel D-Bus methods: a mapping from * strings (D-Bus interface name + "." + property name) to #GValue instances * @error: used to indicate the error if %NULL is returned * * Convenient function to create a new #TpyCallChannel * * Returns: (transfer full): a newly created #TpyCallChannel * * Since: */ TpyCallChannel * tpy_call_channel_new (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 (TPY_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, NULL); } static void channel_accept_cb (TpProxy *proxy, const GError *error, gpointer user_data, GObject *weak_object) { GSimpleAsyncResult *result = user_data; if (error != NULL) { DEBUG ("Failed to accept call: %s", error->message); g_simple_async_result_set_from_error (result, error); } g_simple_async_result_set_op_res_gboolean (result, error == NULL); g_simple_async_result_complete (result); } /** * tpy_call_channel_accept_async: * @self: an incoming #TpyCallChannel * @callback: a callback to call * @user_data: data to pass to @callback * * Accept an incoming call. When the call has been accepted, @callback * will be called. You can then call tpy_call_channel_accept_finish() * to finish the operation. * * Since: */ void tpy_call_channel_accept_async (TpyCallChannel *self, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; g_return_if_fail (TPY_IS_CALL_CHANNEL (self)); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tpy_call_channel_accept_async); tpy_cli_channel_type_call_call_accept (TP_PROXY (self), -1, channel_accept_cb, result, NULL, G_OBJECT (self)); } /** * tpy_call_channel_accept_finish: * @self: a #TpyCallChannel * @result: a #GAsyncResult * @error: a #GError to fill * * Finishes to accept a call. * * Since: */ gboolean tpy_call_channel_accept_finish (TpyCallChannel *self, GAsyncResult *result, GError **error) { if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error)) return FALSE; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), tpy_call_channel_accept_async), FALSE); return g_simple_async_result_get_op_res_gboolean ( G_SIMPLE_ASYNC_RESULT (result)); } static void channel_hangup_cb (TpProxy *proxy, const GError *error, gpointer user_data, GObject *weak_object) { GSimpleAsyncResult *result = user_data; if (error != NULL) { DEBUG ("Failed to hang up: %s", error->message); g_simple_async_result_set_from_error (result, error); } g_simple_async_result_set_op_res_gboolean (result, error == NULL); g_simple_async_result_complete (result); } void tpy_call_channel_hangup_async (TpyCallChannel *self, TpyCallStateChangeReason reason, gchar *detailed_reason, gchar *message, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; g_return_if_fail (TPY_IS_CALL_CHANNEL (self)); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tpy_call_channel_accept_async); tpy_cli_channel_type_call_call_hangup (TP_PROXY (self), -1, reason, detailed_reason, message, channel_hangup_cb, result, NULL, G_OBJECT (self)); } gboolean tpy_call_channel_hangup_finish (TpyCallChannel *self, GAsyncResult *result, GError **error) { if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error)) return FALSE; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), tpy_call_channel_hangup_async), FALSE); return g_simple_async_result_get_op_res_gboolean ( G_SIMPLE_ASYNC_RESULT (result)); } TpyCallState tpy_call_channel_get_state (TpyCallChannel *self, TpyCallFlags *flags, GHashTable **details) { g_return_val_if_fail (TPY_IS_CALL_CHANNEL (self), TPY_CALL_STATE_UNKNOWN); if (flags != NULL) *flags = self->priv->flags; if (details != NULL) { if (self->priv->details != NULL) g_hash_table_ref (self->priv->details); *details = self->priv->details; } return self->priv->state; } gboolean tpy_call_channel_has_initial_video (TpyCallChannel *self) { g_return_val_if_fail (TPY_IS_CALL_CHANNEL (self), FALSE); return self->priv->initial_video; } gboolean tpy_call_channel_has_initial_audio (TpyCallChannel *self) { g_return_val_if_fail (TPY_IS_CALL_CHANNEL (self), FALSE); return self->priv->initial_audio; } void tpy_call_channel_send_video (TpyCallChannel *self, gboolean send) { gboolean found = FALSE; guint i; g_return_if_fail (TPY_IS_CALL_CHANNEL (self)); /* Loop over all the contents, if some of them a video set all their * streams to sending, otherwise request a video channel in case we want to * sent */ for (i = 0 ; i < self->priv->contents->len ; i++) { TpyCallContent *content = g_ptr_array_index (self->priv->contents, i); if (tpy_call_content_get_media_type (content) == TP_MEDIA_STREAM_TYPE_VIDEO) { GList *l; found = TRUE; for (l = tpy_call_content_get_streams (content); l != NULL ; l = g_list_next (l)) { TpyCallStream *stream = TPY_CALL_STREAM (l->data); tpy_call_stream_set_sending_async (stream, send, NULL, NULL); } } } if (send && !found) tpy_cli_channel_type_call_call_add_content (TP_PROXY (self), -1, "video", TP_MEDIA_STREAM_TYPE_VIDEO, NULL, NULL, NULL, NULL); } TpySendingState tpy_call_channel_get_video_state (TpyCallChannel *self) { TpySendingState result = TPY_SENDING_STATE_NONE; guint i; g_return_val_if_fail (TPY_IS_CALL_CHANNEL (self), TPY_SENDING_STATE_NONE); for (i = 0 ; i < self->priv->contents->len ; i++) { TpyCallContent *content = g_ptr_array_index (self->priv->contents, i); if (tpy_call_content_get_media_type (content) == TP_MEDIA_STREAM_TYPE_VIDEO) { GList *l; for (l = tpy_call_content_get_streams (content); l != NULL ; l = g_list_next (l)) { TpyCallStream *stream = TPY_CALL_STREAM (l->data); TpySendingState state; g_object_get (stream, "local-sending-state", &state, NULL); if (state != TPY_SENDING_STATE_PENDING_STOP_SENDING && state > result) result = state; } } } return result; } gboolean tpy_call_channel_has_dtmf (TpyCallChannel *self) { g_return_val_if_fail (TPY_IS_CALL_CHANNEL (self), FALSE); return tp_proxy_has_interface_by_id (self, TP_IFACE_QUARK_CHANNEL_INTERFACE_DTMF); } static void on_dtmf_tone_cb (TpChannel *proxy, const GError *error, gpointer user_data, GObject *weak_object) { if (error) DEBUG ("Error %s: %s", (gchar *) user_data, error->message); } void tpy_call_channel_dtmf_start_tone (TpyCallChannel *self, TpDTMFEvent event) { g_return_if_fail (TPY_IS_CALL_CHANNEL (self)); tp_cli_channel_interface_dtmf_call_start_tone (TP_CHANNEL (self), -1, 0, event, on_dtmf_tone_cb, "starting tone", NULL, G_OBJECT (self)); } void tpy_call_channel_dtmf_stop_tone (TpyCallChannel *self) { g_return_if_fail (TPY_IS_CALL_CHANNEL (self)); tp_cli_channel_interface_dtmf_call_stop_tone (TP_CHANNEL (self), -1, 0, on_dtmf_tone_cb, "stoping tone", NULL, G_OBJECT (self)); } gboolean tpy_call_channel_has_hold (TpyCallChannel *self) { g_return_val_if_fail (TPY_IS_CALL_CHANNEL (self), FALSE); return tp_proxy_has_interface_by_id (self, TP_IFACE_QUARK_CHANNEL_INTERFACE_HOLD); } /** * tpy_call_channel_held_finish: * @self: a #TpyCallChannel * @result: a #GAsyncResult * @error: a #GError to fill * * Finishes to change the hold status of the call. * * Since: */ gboolean tpy_call_channel_held_finish (TpyCallChannel *self, GAsyncResult *result, GError **error) { if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error)) return FALSE; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), tpy_call_channel_held_async), FALSE); return g_simple_async_result_get_op_res_gboolean ( G_SIMPLE_ASYNC_RESULT (result)); } static void on_request_hold_cb (TpChannel *proxy, const GError *error, gpointer user_data, GObject *weak_object) { GSimpleAsyncResult *result = user_data; if (error != NULL) { DEBUG ("Failed to change hold status: %s", error->message); g_simple_async_result_set_from_error (result, error); } g_simple_async_result_set_op_res_gboolean (result, error == NULL); g_simple_async_result_complete (result); g_object_unref (result); } void tpy_call_channel_held_async (TpyCallChannel *self, gboolean held, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; g_return_if_fail (TPY_IS_CALL_CHANNEL (self)); result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tpy_call_channel_held_async); tp_cli_channel_interface_hold_call_request_hold (TP_CHANNEL (self), -1, held, on_request_hold_cb, result, g_object_unref, G_OBJECT (self)); } /** * TPY_CALL_CHANNEL_FEATURE_CORE: * * Expands to a call to a function that returns a quark for the "core" * feature on a #TpyCallChannel. * * 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 tpy_call_channel_get_feature_core (void) { return g_quark_from_static_string ("tpy-call-channel-feature-core"); }