/*
* call-stream.h - high level API for Call streams
*
* 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-stream
* @title: TpCallStream
* @short_description: proxy object for a call stream
*
* #TpCallStream is a sub-class of #TpProxy providing convenient API
* to represent #TpCallChannel's stream.
*/
/**
* TpCallStream:
*
* Data structure representing a #TpCallStream.
*
* Since: 0.17.5
*/
/**
* TpCallStreamClass:
*
* The class of a #TpCallStream.
*
* Since: 0.17.5
*/
#include "config.h"
#include "telepathy-glib/call-stream.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEBUG_FLAG TP_DEBUG_CALL
#include "telepathy-glib/debug-internal.h"
#include "telepathy-glib/call-internal.h"
#include "telepathy-glib/proxy-internal.h"
#include "telepathy-glib/util-internal.h"
#include "_gen/tp-cli-call-stream-body.h"
G_DEFINE_TYPE (TpCallStream, tp_call_stream, TP_TYPE_PROXY)
struct _TpCallStreamPrivate
{
TpConnection *connection;
TpCallContent *content;
/* TpContact -> TpSendingState */
GHashTable *remote_members;
TpSendingState local_sending_state;
gboolean can_request_receiving;
gboolean properties_retrieved;
};
enum
{
PROP_CONNECTION = 1,
PROP_LOCAL_SENDING_STATE,
PROP_CAN_REQUEST_RECEIVING,
PROP_CONTENT
};
enum /* signals */
{
LOCAL_SENDING_STATE_CHANGED,
REMOTE_MEMBERS_CHANGED,
LAST_SIGNAL
};
static guint _signals[LAST_SIGNAL] = { 0, };
static void
update_remote_members (TpCallStream *self,
GHashTable *updates,
GPtrArray *removed)
{
if (updates != NULL)
{
tp_g_hash_table_update (self->priv->remote_members, updates,
g_object_ref, NULL);
}
if (removed != NULL)
{
guint i;
for (i = 0; i < removed->len; i++)
{
g_hash_table_remove (self->priv->remote_members,
g_ptr_array_index (removed, i));
}
}
}
static void
remote_members_changed_cb (TpCallStream *self,
GHashTable *updates,
GHashTable *identifiers,
const GArray *removed,
const GValueArray *reason,
gpointer user_data,
GObject *weak_object)
{
GHashTable *updates_contacts;
GPtrArray *removed_contacts;
TpCallStateReason *r;
if (!self->priv->properties_retrieved)
return;
DEBUG ("Remote members: %d updated, %d removed",
g_hash_table_size (updates), removed->len);
updates_contacts = _tp_call_members_convert_table (self->priv->connection,
updates, identifiers);
removed_contacts = _tp_call_members_convert_array (self->priv->connection,
removed);
r = _tp_call_state_reason_new (reason);
update_remote_members (self, updates_contacts, removed_contacts);
g_signal_emit (self, _signals[REMOTE_MEMBERS_CHANGED], 0,
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
local_sending_state_changed_cb (TpCallStream *self,
guint state,
const GValueArray *reason,
gpointer user_data,
GObject *weak_object)
{
TpCallStateReason *r;
if (!self->priv->properties_retrieved)
return;
self->priv->local_sending_state = state;
g_object_notify (G_OBJECT (self), "local-sending-state");
r = _tp_call_state_reason_new (reason);
g_signal_emit (self, _signals[LOCAL_SENDING_STATE_CHANGED], 0,
self->priv->local_sending_state, r);
_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)
{
TpCallStream *self = (TpCallStream *) proxy;
const gchar * const *interfaces;
GHashTable *remote_members;
GHashTable *identifiers;
GHashTable *contacts;
if (error != NULL)
{
DEBUG ("Could not get the call stream properties: %s", error->message);
_tp_proxy_set_feature_prepared (proxy,
TP_CALL_STREAM_FEATURE_CORE, FALSE);
return;
}
self->priv->properties_retrieved = TRUE;
interfaces = tp_asv_get_boxed (properties,
"Interfaces", G_TYPE_STRV);
remote_members = tp_asv_get_boxed (properties,
"RemoteMembers", TP_HASH_TYPE_CONTACT_SENDING_STATE_MAP),
identifiers = tp_asv_get_boxed (properties,
"RemoteMemberIdentifiers", TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP);
self->priv->local_sending_state = tp_asv_get_uint32 (properties,
"LocalSendingState", NULL);
self->priv->can_request_receiving = tp_asv_get_boolean (properties,
"CanRequestReceiving", NULL);
tp_proxy_add_interfaces ((TpProxy *) self, interfaces);
contacts = _tp_call_members_convert_table (self->priv->connection,
remote_members, identifiers);
update_remote_members (self, contacts, NULL);
g_hash_table_unref (contacts);
_tp_proxy_set_feature_prepared (proxy, TP_CALL_STREAM_FEATURE_CORE, TRUE);
}
static void
tp_call_stream_constructed (GObject *obj)
{
TpCallStream *self = (TpCallStream *) obj;
((GObjectClass *) tp_call_stream_parent_class)->constructed (obj);
/* Connect signals for mutable properties */
tp_cli_call_stream_connect_to_remote_members_changed (self,
remote_members_changed_cb, NULL, NULL, G_OBJECT (self), NULL);
tp_cli_call_stream_connect_to_local_sending_state_changed (self,
local_sending_state_changed_cb, NULL, NULL, G_OBJECT (self), NULL);
tp_cli_dbus_properties_call_get_all (self, -1,
TP_IFACE_CALL_STREAM,
got_all_properties_cb, NULL, NULL, G_OBJECT (self));
}
static void
tp_call_stream_dispose (GObject *object)
{
TpCallStream *self = (TpCallStream *) object;
g_clear_object (&self->priv->content);
g_clear_object (&self->priv->connection);
tp_clear_pointer (&self->priv->remote_members, g_hash_table_unref);
G_OBJECT_CLASS (tp_call_stream_parent_class)->dispose (object);
}
static void
tp_call_stream_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
TpCallStream *self = (TpCallStream *) object;
TpCallStreamPrivate *priv = self->priv;
switch (property_id)
{
case PROP_CONNECTION:
g_value_set_object (value, self->priv->connection);
break;
case PROP_LOCAL_SENDING_STATE:
g_value_set_uint (value, priv->local_sending_state);
break;
case PROP_CAN_REQUEST_RECEIVING:
g_value_set_boolean (value, priv->can_request_receiving);
break;
case PROP_CONTENT:
g_value_set_object (value, self->priv->content);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
tp_call_stream_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
TpCallStream *self = (TpCallStream *) object;
switch (property_id)
{
case PROP_CONNECTION:
g_assert (self->priv->connection == NULL); /* construct-only */
self->priv->connection = g_value_dup_object (value);
break;
case PROP_CONTENT:
g_assert (self->priv->content == NULL); /* construct-only */
self->priv->content = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
enum {
FEAT_CORE,
N_FEAT
};
static const TpProxyFeature *
tp_call_stream_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_STREAM_FEATURE_CORE;
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_stream_class_init (TpCallStreamClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
TpProxyClass *proxy_class = (TpProxyClass *) klass;
GParamSpec *param_spec;
gobject_class->constructed = tp_call_stream_constructed;
gobject_class->get_property = tp_call_stream_get_property;
gobject_class->set_property = tp_call_stream_set_property;
gobject_class->dispose = tp_call_stream_dispose;
proxy_class->list_features = tp_call_stream_list_features;
proxy_class->interface = TP_IFACE_QUARK_CALL_STREAM;
g_type_class_add_private (gobject_class, sizeof (TpCallStreamPrivate));
tp_call_stream_init_known_interfaces ();
/**
* TpCallStream:connection:
*
* The #TpConnection of the call.
*
* Since: 0.17.5
*/
param_spec = g_param_spec_object ("connection", "Connection",
"The connection of this stream",
TP_TYPE_CONNECTION,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (gobject_class, PROP_CONNECTION,
param_spec);
/**
* TpCallStream:local-sending-state:
*
* The local user's sending state, from #TpSendingState.
*
* Since: 0.17.5
*/
param_spec = g_param_spec_uint ("local-sending-state", "LocalSendingState",
"Local sending state",
0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (gobject_class, PROP_LOCAL_SENDING_STATE,
param_spec);
/**
* TpCallStream:can-request-receiving:
*
* If %TRUE, the user can request that a remote contact starts sending on this
* stream.
*
* Since: 0.17.5
*/
param_spec = g_param_spec_boolean ("can-request-receiving",
"CanRequestReceiving",
"If true, the user can request that a remote contact starts sending on"
"this stream.",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (gobject_class, PROP_CAN_REQUEST_RECEIVING,
param_spec);
/**
* TpCallStream:content:
*
* The Content that this streams belongs to
*
* Since: 0.17.6
*/
param_spec = g_param_spec_object ("content",
"Content",
"The content that this Stream belongs to",
TP_TYPE_CALL_CONTENT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (gobject_class, PROP_CONTENT,
param_spec);
/**
* TpCallStream::local-sending-state-changed:
* @self: the #TpCallStream
* @state: the new #TpSendingState
* @reason: the #TpCallStateReason for the change
*
* The ::local-sending-state-changed signal is emitted whenever the
* stream sending state changes.
*
* Since: 0.17.5
*/
_signals[LOCAL_SENDING_STATE_CHANGED] = g_signal_new ("local-sending-state-changed",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE,
2, G_TYPE_UINT, TP_TYPE_CALL_STATE_REASON);
/**
* TpCallStream::remote-members-changed:
* @self: the #TpCallStream
* @updates: (type GLib.HashTable) (element-type TelepathyGLib.Contact uint):
* #GHashTable mapping #TpContact to its new #TpSendingState
* @removed: (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
* #GPtrArray of #TpContact removed from remote contacts
* @reason: the #TpCallStateReason for the change
*
* The ::remote-members-changed signal is emitted whenever the
* stream's remote members changes.
*
* It is NOT guaranteed that #TpContact objects have any feature prepared.
*
* Since: 0.17.5
*/
_signals[REMOTE_MEMBERS_CHANGED] = g_signal_new ("remote-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_stream_init (TpCallStream *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), TP_TYPE_CALL_STREAM,
TpCallStreamPrivate);
self->priv->remote_members = g_hash_table_new_full (NULL, NULL,
g_object_unref, NULL);
}
/**
* tp_call_stream_init_known_interfaces:
*
* Ensure that the known interfaces for #TpCallStream have been set up.
* This is done automatically when necessary, but for correct
* overriding of library interfaces by local extensions, you should
* call this function before calling
* tp_proxy_or_subclass_hook_on_interface_add() with first argument
* %TP_TYPE_CALL_STREAM.
*
* Since: 0.17.5
*/
void
tp_call_stream_init_known_interfaces (void)
{
static gsize once = 0;
if (g_once_init_enter (&once))
{
GType tp_type = TP_TYPE_CALL_STREAM;
tp_proxy_init_known_interfaces ();
tp_proxy_or_subclass_hook_on_interface_add (tp_type,
tp_cli_call_stream_add_signals);
tp_proxy_subclass_add_error_mapping (tp_type,
TP_ERROR_PREFIX, TP_ERROR, TP_TYPE_ERROR);
g_once_init_leave (&once, 1);
}
}
/**
* TP_CALL_STREAM_FEATURE_CORE:
*
* Expands to a call to a function that returns a quark for the "core"
* feature on a #TpCallStream.
*
* 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_stream_get_feature_quark_core (void)
{
return g_quark_from_static_string ("tp-call-stream-feature-core");
}
/**
* tp_call_stream_get_local_sending_state:
* @self: a #TpCallStream
*
*
*
* Returns: the value of #TpCallStream:local-sending-state
* Since: 0.17.5
*/
TpSendingState
tp_call_stream_get_local_sending_state (TpCallStream *self)
{
g_return_val_if_fail (TP_IS_CALL_STREAM (self), TP_SENDING_STATE_NONE);
return self->priv->local_sending_state;
}
/**
* tp_call_stream_can_request_receiving:
* @self: a #TpCallStream
*
*
*
* Returns: the value of #TpCallStream:can-request-receiving
* Since: 0.17.5
*/
gboolean
tp_call_stream_can_request_receiving (TpCallStream *self)
{
g_return_val_if_fail (TP_IS_CALL_STREAM (self), FALSE);
return self->priv->can_request_receiving;
}
/**
* tp_call_stream_get_remote_members:
* @self: a #TpCallStream
*
* Get the remote contacts to who this stream is connected, mapped to their
* sending state.
*
* It is NOT guaranteed that #TpContact objects have any feature prepared.
*
* Returns: (transfer none) (type GLib.HashTable) (element-type TelepathyGLib.Contact uint):
* #GHashTable mapping #TpContact to its new #TpSendingState
* Since: 0.17.5
*/
GHashTable *
tp_call_stream_get_remote_members (TpCallStream *self)
{
g_return_val_if_fail (TP_IS_CALL_STREAM (self), NULL);
return self->priv->remote_members;
}
static void
generic_async_cb (TpCallStream *self,
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_stream_set_sending_async:
* @self: a #TpCallStream
* @send: the requested sending state
* @callback: a callback to call when the operation finishes
* @user_data: data to pass to @callback
*
* Set the stream to start or stop sending media from the local user to other
* contacts.
*
* If @send is %TRUE, #TpCallStream:local-sending-state should change to
* %TP_SENDING_STATE_SENDING, if it isn't already.
* If @send is %FALSE, #TpCallStream:local-sending-state should change to
* %TP_SENDING_STATE_NONE, if it isn't already.
*
* Since: 0.17.5
*/
void
tp_call_stream_set_sending_async (TpCallStream *self,
gboolean send,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
g_return_if_fail (TP_IS_CALL_STREAM (self));
result = g_simple_async_result_new (G_OBJECT (self), callback,
user_data, tp_call_stream_set_sending_async);
tp_cli_call_stream_call_set_sending (self, -1, send,
generic_async_cb, result, g_object_unref, G_OBJECT (self));
}
/**
* tp_call_stream_set_sending_finish:
* @self: a #TpCallStream
* @result: a #GAsyncResult
* @error: a #GError to fill
*
* Finishes tp_call_stream_set_sending_async().
*
* Since: 0.17.5
*/
gboolean
tp_call_stream_set_sending_finish (TpCallStream *self,
GAsyncResult *result,
GError **error)
{
_tp_implement_finish_void (self, tp_call_stream_set_sending_async);
}
/**
* tp_call_stream_request_receiving_async:
* @self: a #TpCallStream
* @contact: contact from which sending is requested
* @receive: the requested receiving state
* @callback: a callback to call when the operation finishes
* @user_data: data to pass to @callback
*
* Request that a remote contact stops or starts sending on this stream.
*
* The #TpCallStream:can-request-receiving property defines whether the protocol
* allows the local user to request the other side start sending on this stream.
*
* If @receive is %TRUE, request that the given contact starts to send media.
* If @receive is %FALSE, request that the given contact stops sending media.
*
* Since: 0.17.5
*/
void
tp_call_stream_request_receiving_async (TpCallStream *self,
TpContact *contact,
gboolean receive,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
g_return_if_fail (TP_IS_CALL_STREAM (self));
g_return_if_fail (TP_IS_CONTACT (contact));
g_return_if_fail (tp_contact_get_connection (contact) ==
self->priv->connection);
result = g_simple_async_result_new (G_OBJECT (self), callback,
user_data, tp_call_stream_set_sending_async);
tp_cli_call_stream_call_request_receiving (self, -1,
tp_contact_get_handle (contact), receive,
generic_async_cb, result, g_object_unref, G_OBJECT (self));
}
/**
* tp_call_stream_request_receiving_finish:
* @self: a #TpCallStream
* @result: a #GAsyncResult
* @error: a #GError to fill
*
* Finishes tp_call_stream_request_receiving_async().
*
* Since: 0.17.5
*/
gboolean
tp_call_stream_request_receiving_finish (TpCallStream *self,
GAsyncResult *result,
GError **error)
{
_tp_implement_finish_void (self, tp_call_stream_request_receiving_async);
}