/*
* 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");
}