/*
* connection.c - proxy for a Telepathy connection
*
* Copyright (C) 2007-2011 Collabora Ltd.
* Copyright (C) 2007-2011 Nokia Corporation
*
* 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
*/
#include "config.h"
#include "telepathy-glib/connection.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEBUG_FLAG TP_DEBUG_CONNECTION
#include "telepathy-glib/capabilities-internal.h"
#include "telepathy-glib/connection-internal.h"
#include "telepathy-glib/connection-contact-list.h"
#include "telepathy-glib/dbus-internal.h"
#include "telepathy-glib/debug-internal.h"
#include "telepathy-glib/proxy-internal.h"
#include "telepathy-glib/simple-client-factory-internal.h"
#include "telepathy-glib/contact-internal.h"
#include "telepathy-glib/util-internal.h"
#include "telepathy-glib/variant-util-internal.h"
#include "_gen/tp-cli-connection-body.h"
/**
* SECTION:connection
* @title: TpConnection
* @short_description: proxy object for a Telepathy connection
* @see_also: #TpConnectionManager, #TpChannel
*
* #TpConnection objects represent Telepathy instant messaging connections
* accessed via D-Bus.
*
* #TpConnection objects should be obtained from a #TpAccount, unless you
* are implementing a lower-level Telepathy component (such as the account
* manager service itself).
*
* Since 0.16, #TpConnection always has a non-%NULL #TpProxy:factory, and its
* #TpProxy:factory will be propagated to its #TpChannel objects
* (if any). Similarly, the #TpProxy:factory's features
* will be used for #TpContact objects.
* If a #TpConnection is created without going via the
* #TpAccount or specifying a #TpProxy:factory, the default
* is to use a new #TpAutomaticClientFactory.
*
* Since: 0.7.1
*/
/**
* TP_CONNECTION_FEATURE_CORE:
*
* Expands to a call to a function that returns a quark for the "core" feature
* on a #TpConnection.
*
* When this feature is prepared, the basic properties of the Connection have
* been retrieved and are available for use, and change-notification has been
* set up for those that can change.
*
* Specifically, this implies that:
*
*
* #TpConnection:status has a value other than
* %TP_UNKNOWN_CONNECTION_STATUS, and #TpConnection:status-reason is
* the reason for changing to that status
* interfaces that are always available have been added to the
* #TpProxy:interfaces (although the set of interfaces is not guaranteed
* to be complete until #TpConnection:status becomes
* %TP_CONNECTION_STATUS_CONNECTED))
*
*
*
* prepared does not imply connected
* Unlike the older #TpConnection:connection-ready mechanism, this
* feature does not imply that the connection has successfully connected.
* It only implies that an initial status (disconnected, connecting or
* connected) has been discovered. %TP_CONNECTION_FEATURE_CONNECTED
* is the closest equivalent of #TpConnection:connection-ready.
*
*
* One can ask for a feature to be prepared using the
* tp_proxy_prepare_async() function, and waiting for it to callback.
*
* Since: 0.11.3
*/
GQuark
tp_connection_get_feature_quark_core (void)
{
return g_quark_from_static_string ("tp-connection-feature-core");
}
/**
* TP_CONNECTION_FEATURE_CONNECTED:
*
* Expands to a call to a function that returns a #GQuark representing the
* "connected" feature.
*
* When this feature is prepared, it means that the connection has become
* connected to the appropriate real-time communications service, and all
* information requested via other features has been updated accordingly.
* In particular, the following aspects of %TP_CONNECTION_FEATURE_CORE
* will be up to date:
*
*
* #TpConnection:status is
* %TP_CONNECTION_STATUS_CONNECTED
* #TpConnection:self-handle is valid and non-zero
* #TpConnection:self-contact is non-%NULL
* all interfaces have been added to the set of
* #TpProxy:interfaces, and that set will not change again
*
*
*
* Someone still has to call Connect()
* Requesting this feature via tp_proxy_prepare_async() means that
* you want to wait for the connection to connect, but it doesn't actually
* start the process of connecting. For connections associated with
* a #TpAccount, the account manager service is responsible for
* doing that, but if you are constructing connections directly
* (e.g. if you are implementing an account manager), you must
* tp_cli_connection_call_connect() separately.
*
*
*
* One can ask for a feature to be prepared using the
* tp_proxy_prepare_async() function, and waiting for it to callback.
*
* Since: 0.11.3
*/
GQuark
tp_connection_get_feature_quark_connected (void)
{
return g_quark_from_static_string ("tp-connection-feature-connected");
}
/**
* TP_CONNECTION_FEATURE_CAPABILITIES:
*
* Expands to a call to a function that returns a #GQuark representing the
* "capabilities" feature.
*
* When this feature is prepared, the Requests.RequestableChannelClasses
* property of the Connection has been retrieved.
* In particular, the %TpConnection:capabilities property has been set.
*
* One can ask for a feature to be prepared using the
* tp_proxy_prepare_async() function, and waiting for it to callback.
*
* Since: 0.11.3
*/
GQuark
tp_connection_get_feature_quark_capabilities (void)
{
return g_quark_from_static_string ("tp-connection-feature-capabilities");
}
/**
* TP_CONNECTION_FEATURE_BALANCE:
*
* Expands to a call to a function that returns a #GQuark representing the
* "balance" feature.
*
* When this feature is prepared, the Balance.AccountBalance and
* Balance.ManageCreditURI properties of the Connection have been retrieved.
* In particular, the %TpConnection:balance, %TpConnection:balance-scale,
* %TpConnection:balance-currency and %TpConnection:balance-uri properties
* have been set and the TpConnection::balance-changed: will be emitted
* when they are changed.
*
* One can ask for a feature to be prepared using the
* tp_proxy_prepare_async() function, and waiting for it to callback.
*
* Since: 0.15.1
*/
GQuark
tp_connection_get_feature_quark_balance (void)
{
return g_quark_from_static_string ("tp-connection-feature-balance");
}
/**
* TP_ERRORS_DISCONNECTED:
*
* #GError domain representing a Telepathy connection becoming disconnected.
* The @code in a #GError with this domain must be a member of
* #TpConnectionStatusReason.
*
* This macro expands to a function call returning a #GQuark.
*
* Since 0.7.24, this error domain is only used if a connection manager emits
* a #TpConnectionStatusReason not known to telepathy-glib.
*
* Since: 0.7.1
*/
GQuark
tp_errors_disconnected_quark (void)
{
static GQuark q = 0;
if (q == 0)
q = g_quark_from_static_string ("tp_errors_disconnected_quark");
return q;
}
/**
* TP_UNKNOWN_CONNECTION_STATUS:
*
* An invalid connection status used in #TpConnection to indicate that the
* status has not yet been discovered.
*
* Since: 0.7.1
*/
/**
* TpConnectionClass:
* @parent_class: the parent class
*
* The class of a #TpConnection. In addition to @parent_class there are four
* pointers reserved for possible future use.
*
* (Changed in 0.7.12: the layout of the structure is visible, allowing
* subclassing.)
*
* Since: 0.7.1
*/
/**
* TpConnection:
*
* A proxy object for a Telepathy connection. There are no interesting
* public struct fields.
*
* (Changed in 0.7.12: the layout of the structure is visible, allowing
* subclassing.)
*
* Since: 0.7.1
*/
/* properties */
enum
{
PROP_STATUS = 1,
PROP_STATUS_REASON,
PROP_CONNECTION_MANAGER_NAME,
PROP_CM_NAME,
PROP_PROTOCOL_NAME,
PROP_CONNECTION_READY,
PROP_SELF_CONTACT,
PROP_SELF_HANDLE,
PROP_CAPABILITIES,
PROP_BALANCE,
PROP_BALANCE_SCALE,
PROP_BALANCE_CURRENCY,
PROP_BALANCE_URI,
PROP_CONTACT_LIST_STATE,
PROP_CONTACT_LIST_PERSISTS,
PROP_CAN_CHANGE_CONTACT_LIST,
PROP_REQUEST_USES_MESSAGE,
PROP_DISJOINT_GROUPS,
PROP_GROUP_STORAGE,
PROP_CONTACT_GROUPS,
PROP_CAN_REPORT_ABUSIVE,
PROP_BLOCKED_CONTACTS,
N_PROPS
};
enum {
SIGNAL_BALANCE_CHANGED,
SIGNAL_GROUPS_CREATED,
SIGNAL_GROUPS_REMOVED,
SIGNAL_GROUP_RENAMED,
SIGNAL_CONTACT_LIST_CHANGED,
SIGNAL_BLOCKED_CONTACTS_CHANGED,
N_SIGNALS
};
static guint signals[N_SIGNALS] = { 0 };
G_DEFINE_TYPE (TpConnection,
tp_connection,
TP_TYPE_PROXY)
static void
tp_connection_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
TpConnection *self = TP_CONNECTION (object);
/* Deprecated properties uses deprecated getters */
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
switch (property_id)
{
case PROP_CONNECTION_MANAGER_NAME:
g_value_set_string (value, self->priv->cm_name);
break;
case PROP_CM_NAME:
g_value_set_string (value, self->priv->cm_name);
break;
case PROP_PROTOCOL_NAME:
g_value_set_string (value, self->priv->proto_name);
break;
case PROP_CONNECTION_READY:
g_value_set_boolean (value, self->priv->ready);
break;
case PROP_STATUS:
g_value_set_uint (value, self->priv->status);
break;
case PROP_STATUS_REASON:
g_value_set_uint (value, self->priv->status_reason);
break;
case PROP_SELF_CONTACT:
g_value_set_object (value, tp_connection_get_self_contact (self));
break;
case PROP_SELF_HANDLE:
g_value_set_uint (value, tp_connection_get_self_handle (self));
break;
case PROP_CAPABILITIES:
g_value_set_object (value, self->priv->capabilities);
break;
case PROP_BALANCE:
g_value_set_int (value, self->priv->balance);
break;
case PROP_BALANCE_SCALE:
g_value_set_uint (value, self->priv->balance_scale);
break;
case PROP_BALANCE_CURRENCY:
g_value_set_string (value, self->priv->balance_currency);
break;
case PROP_BALANCE_URI:
g_value_set_string (value, self->priv->balance_uri);
break;
case PROP_CONTACT_LIST_STATE:
g_value_set_uint (value, self->priv->contact_list_state);
break;
case PROP_CONTACT_LIST_PERSISTS:
g_value_set_boolean (value, self->priv->contact_list_persists);
break;
case PROP_CAN_CHANGE_CONTACT_LIST:
g_value_set_boolean (value, self->priv->can_change_contact_list);
break;
case PROP_REQUEST_USES_MESSAGE:
g_value_set_boolean (value, self->priv->request_uses_message);
break;
case PROP_DISJOINT_GROUPS:
g_value_set_boolean (value, self->priv->disjoint_groups);
break;
case PROP_GROUP_STORAGE:
g_value_set_uint (value, self->priv->group_storage);
break;
case PROP_CONTACT_GROUPS:
g_value_set_boxed (value, self->priv->contact_groups);
break;
case PROP_CAN_REPORT_ABUSIVE:
g_value_set_boolean (value, tp_connection_can_report_abusive (self));
break;
case PROP_BLOCKED_CONTACTS:
g_value_set_boxed (value, tp_connection_get_blocked_contacts (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
G_GNUC_END_IGNORE_DEPRECATIONS
}
static void
tp_connection_unpack_balance (TpConnection *self,
GValueArray *balance_s)
{
gint balance = 0;
guint scale = G_MAXUINT32;
const char *currency = "";
gboolean changed = FALSE;
if (balance_s == NULL)
goto finally;
tp_value_array_unpack (balance_s, 3,
&balance, &scale, ¤cy);
finally:
g_object_freeze_notify ((GObject *) self);
if (self->priv->balance != balance)
{
self->priv->balance = balance;
g_object_notify ((GObject *) self, "balance");
changed = TRUE;
}
if (self->priv->balance_scale != scale)
{
self->priv->balance_scale = scale;
g_object_notify ((GObject *) self, "balance-scale");
changed = TRUE;
}
if (tp_strdiff (self->priv->balance_currency, currency))
{
g_free (self->priv->balance_currency);
self->priv->balance_currency = g_strdup (currency);
g_object_notify ((GObject *) self, "balance-currency");
changed = TRUE;
}
g_object_thaw_notify ((GObject *) self);
if (changed)
{
g_signal_emit (self, signals[SIGNAL_BALANCE_CHANGED], 0,
balance, scale, currency);
}
}
static void
tp_connection_get_balance_cb (TpProxy *proxy,
GHashTable *props,
const GError *in_error,
gpointer user_data,
GObject *weak_obj)
{
TpConnection *self = (TpConnection *) proxy;
GSimpleAsyncResult *result = user_data;
GValueArray *balance = NULL;
if (in_error != NULL)
{
DEBUG ("Failed to get Balance properties: %s", in_error->message);
g_simple_async_result_set_from_error (result, in_error);
goto finally;
}
balance =
tp_asv_get_boxed (props, "AccountBalance", TP_STRUCT_TYPE_CURRENCY_AMOUNT);
self->priv->balance_uri =
g_strdup (tp_asv_get_string (props, "ManageCreditURI"));
g_object_freeze_notify ((GObject *) self);
tp_connection_unpack_balance (self, balance);
_tp_proxy_set_feature_prepared (proxy, TP_CONNECTION_FEATURE_BALANCE,
TRUE);
g_object_notify ((GObject *) self, "balance-uri");
g_object_thaw_notify ((GObject *) self);
finally:
g_simple_async_result_complete_in_idle (result);
}
static void
tp_connection_balance_changed_cb (TpConnection *self,
const GValueArray *balance,
gpointer user_data,
GObject *weak_obj)
{
tp_connection_unpack_balance (self, (GValueArray *) balance);
}
static void
tp_connection_prepare_balance_async (TpProxy *proxy,
const TpProxyFeature *feature,
GAsyncReadyCallback callback,
gpointer user_data)
{
TpConnection *self = (TpConnection *) proxy;
GSimpleAsyncResult *result;
result = g_simple_async_result_new ((GObject *) proxy, callback, user_data,
tp_connection_prepare_balance_async);
g_assert (self->priv->balance_currency == NULL);
tp_cli_dbus_properties_call_get_all (self, -1,
TP_IFACE_CONNECTION_INTERFACE_BALANCE,
tp_connection_get_balance_cb, result, g_object_unref, NULL);
tp_cli_connection_interface_balance_connect_to_balance_changed (self,
tp_connection_balance_changed_cb,
NULL, NULL, NULL, NULL);
}
static void
tp_connection_get_rcc_cb (TpProxy *proxy,
const GValue *value,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
TpConnection *self = (TpConnection *) proxy;
GSimpleAsyncResult *result;
if (error != NULL)
{
DEBUG ("Failed to get RequestableChannelClasses property, using an "
"empty set: %s", error->message);
/* it's NULL-safe */
self->priv->capabilities = _tp_capabilities_new (NULL, FALSE);
goto finally;
}
g_assert (self->priv->capabilities == NULL);
if (!G_VALUE_HOLDS (value, TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST))
{
DEBUG ("RequestableChannelClasses is not of type a(a{sv}as), using an "
"empty set: %s", G_VALUE_TYPE_NAME (value));
/* it's NULL-safe */
self->priv->capabilities = _tp_capabilities_new (NULL, FALSE);
goto finally;
}
DEBUG ("CAPABILITIES ready");
self->priv->capabilities = _tp_capabilities_new (g_value_get_boxed (value),
FALSE);
finally:
while ((result = g_queue_pop_head (&self->priv->capabilities_queue)) != NULL)
{
g_simple_async_result_complete_in_idle (result);
g_object_unref (result);
}
g_object_notify ((GObject *) self, "capabilities");
}
static void
_tp_connection_do_get_capabilities_async (TpConnection *self,
GSimpleAsyncResult *result)
{
if (self->priv->capabilities != NULL)
{
/* been there, done that, bored now */
g_simple_async_result_complete_in_idle (result);
g_object_unref (result);
}
else
{
g_queue_push_tail (&self->priv->capabilities_queue, result);
if (g_queue_get_length (&self->priv->capabilities_queue) == 1)
{
DEBUG ("%s: Retrieving capabilities",
tp_proxy_get_object_path (self));
/* We don't check whether we actually have this interface here. The
* quark is dbus properties quark is guaranteed to be on every
* TpProxy and only very very old CMs won't have Requests, in case
* someone still has such a relic we'll we'll just handle it when
* they reply to us with an error */
tp_cli_dbus_properties_call_get (self, -1,
TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
"RequestableChannelClasses",
tp_connection_get_rcc_cb, NULL, NULL, NULL);
}
}
}
void
_tp_connection_get_capabilities_async (TpConnection *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
result = g_simple_async_result_new ((GObject *) self, callback, user_data,
_tp_connection_get_capabilities_async);
_tp_connection_do_get_capabilities_async (self, result);
}
gboolean
_tp_connection_get_capabilities_finish (TpConnection *self,
GAsyncResult *result, GError **error)
{
_tp_implement_finish_void (self, _tp_connection_get_capabilities_async);
}
static void
tp_connection_prepare_capabilities_async (TpProxy *proxy,
const TpProxyFeature *feature,
GAsyncReadyCallback callback,
gpointer user_data)
{
TpConnection *self = (TpConnection *) proxy;
GSimpleAsyncResult *result;
DEBUG ("%s: Preparing capabilities", tp_proxy_get_object_path (self));
result = g_simple_async_result_new ((GObject *) self, callback, user_data,
tp_connection_prepare_capabilities_async);
_tp_connection_do_get_capabilities_async (self, result);
}
static void
signal_connected (TpConnection *self)
{
/* we shouldn't have gone to status CONNECTED for any reason
* that isn't REQUESTED :-) */
DEBUG ("%s (%p): CORE and CONNECTED ready",
tp_proxy_get_object_path (self), self);
self->priv->status = TP_CONNECTION_STATUS_CONNECTED;
self->priv->status_reason = TP_CONNECTION_STATUS_REASON_REQUESTED;
self->priv->ready = TRUE;
_tp_proxy_set_feature_prepared ((TpProxy *) self,
TP_CONNECTION_FEATURE_CONNECTED, TRUE);
_tp_proxy_set_feature_prepared ((TpProxy *) self,
TP_CONNECTION_FEATURE_CORE, TRUE);
g_object_notify ((GObject *) self, "status");
g_object_notify ((GObject *) self, "status-reason");
g_object_notify ((GObject *) self, "connection-ready");
}
static void
will_announced_connected_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
TpConnection *self = (TpConnection *) source;
GError *error = NULL;
if (!_tp_proxy_will_announce_connected_finish ((TpProxy *) self, result,
&error))
{
DEBUG ("_tp_connection_prepare_contact_info_async failed: %s",
error->message);
g_error_free (error);
}
if (tp_proxy_get_invalidated (self) != NULL)
{
DEBUG ("Connection has been invalidated; we're done");
return;
}
signal_connected (self);
}
static void
tp_connection_continue_introspection (TpConnection *self)
{
if (tp_proxy_get_invalidated (self) != NULL)
{
DEBUG ("Already invalidated: not becoming ready");
return;
}
if (self->priv->introspect_needed == NULL)
{
if (!self->priv->introspecting_after_connected)
{
/* Introspection will restart when we become CONNECTED */
DEBUG ("CORE ready, but not CONNECTED");
_tp_proxy_set_feature_prepared ((TpProxy *) self,
TP_CONNECTION_FEATURE_CORE, TRUE);
return;
}
/* We'll announce CONNECTED state soon, but first give a chance to
* prepared feature to be updated, if needed */
_tp_proxy_will_announce_connected_async ((TpProxy *) self,
will_announced_connected_cb, NULL);
}
else
{
TpConnectionProc next = self->priv->introspect_needed->data;
self->priv->introspect_needed = g_list_delete_link (
self->priv->introspect_needed,
self->priv->introspect_needed);
next (self);
}
}
static void
got_contact_attribute_interfaces (TpProxy *proxy,
const GValue *value,
const GError *error,
gpointer user_data G_GNUC_UNUSED,
GObject *weak_object G_GNUC_UNUSED)
{
TpConnection *self = TP_CONNECTION (proxy);
GArray *arr;
g_assert (self->priv->introspection_call != NULL);
self->priv->introspection_call = NULL;
if (error == NULL && G_VALUE_HOLDS (value, G_TYPE_STRV))
{
gchar **interfaces = g_value_get_boxed (value);
gchar **iter;
arr = g_array_sized_new (FALSE, FALSE, sizeof (GQuark),
interfaces == NULL ? 0 : g_strv_length (interfaces));
if (interfaces != NULL)
{
for (iter = interfaces; *iter != NULL; iter++)
{
if (tp_dbus_check_valid_interface_name (*iter, NULL))
{
GQuark q = g_quark_from_string (*iter);
DEBUG ("%p: ContactAttributeInterfaces has %s", self,
*iter);
g_array_append_val (arr, q);
}
else
{
DEBUG ("%p: ignoring invalid interface: %s", self,
*iter);
}
}
}
}
else
{
if (error == NULL)
DEBUG ("%p: ContactAttributeInterfaces had wrong type %s, "
"ignoring", self, G_VALUE_TYPE_NAME (value));
else
DEBUG ("%p: Get(Contacts, ContactAttributeInterfaces) failed with "
"%s %d: %s", self, g_quark_to_string (error->domain), error->code,
error->message);
arr = g_array_sized_new (FALSE, FALSE, sizeof (GQuark), 0);
}
g_assert (self->priv->contact_attribute_interfaces == NULL);
self->priv->contact_attribute_interfaces = arr;
self->priv->ready_enough_for_contacts = TRUE;
tp_connection_continue_introspection (self);
}
static void
introspect_contacts (TpConnection *self)
{
/* "This cannot change during the lifetime of the Connection." -- tp-spec */
if (self->priv->contact_attribute_interfaces != NULL)
{
tp_connection_continue_introspection (self);
return;
}
g_assert (self->priv->introspection_call == NULL);
self->priv->introspection_call = tp_cli_dbus_properties_call_get (self, -1,
TP_IFACE_CONNECTION_INTERFACE_CONTACTS, "ContactAttributeInterfaces",
got_contact_attribute_interfaces, NULL, NULL, NULL);
}
static void
tp_connection_set_self_contact (TpConnection *self,
TpContact *contact)
{
if (contact != self->priv->self_contact)
{
TpContact *tmp = self->priv->self_contact;
self->priv->self_contact = g_object_ref (contact);
tp_clear_object (&tmp);
g_object_notify ((GObject *) self, "self-contact");
g_object_notify ((GObject *) self, "self-handle");
}
if (self->priv->introspecting_self_contact)
{
self->priv->introspecting_self_contact = FALSE;
tp_connection_continue_introspection (self);
}
}
static void
tp_connection_got_self_contact_cb (TpConnection *self,
guint n_contacts,
TpContact * const *contacts,
guint n_failed,
const TpHandle *failed,
const GError *error,
gpointer unused_data G_GNUC_UNUSED,
GObject *unused_object G_GNUC_UNUSED)
{
if (n_contacts != 0)
{
g_assert (n_contacts == 1);
g_assert (n_failed == 0);
g_assert (error == NULL);
if (tp_contact_get_handle (contacts[0]) ==
self->priv->last_known_self_handle)
{
tp_connection_set_self_contact (self, contacts[0]);
}
else
{
DEBUG ("SelfHandle is now %u, ignoring contact object for %u",
self->priv->last_known_self_handle,
tp_contact_get_handle (contacts[0]));
}
}
else if (error != NULL)
{
/* Unrecoverable error: we were probably invalidated, but in case
* we weren't... */
DEBUG ("Failed to hold the handle from GetSelfHandle(): %s",
error->message);
tp_proxy_invalidate ((TpProxy *) self, error);
}
else if (n_failed == 1 && failed[0] != self->priv->last_known_self_handle)
{
/* Since we tried to make the TpContact, our self-handle has changed,
* so it doesn't matter that we couldn't make a TpContact for the old
* one - carry on and make a TpContact for the new one instead. */
DEBUG ("Failed to hold handle %u from GetSelfHandle(), but it's "
"changed to %u anyway, so never mind", failed[0],
self->priv->last_known_self_handle);
}
else
{
GError e = { TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
"The handle from GetSelfHandle() was considered invalid" };
DEBUG ("%s", e.message);
tp_proxy_invalidate ((TpProxy *) self, &e);
}
}
static void
get_self_contact (TpConnection *self)
{
TpSimpleClientFactory *factory;
GArray *features;
factory = tp_proxy_get_factory (self);
features = tp_simple_client_factory_dup_contact_features (factory, self);
/* FIXME: We should use tp_simple_client_factory_ensure_contact(), but that would
* require immortal-handles and spec change to give the self identifier. */
/* This relies on the special case in tp_connection_get_contacts_by_handle()
* which makes it start working slightly early. */
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
tp_connection_get_contacts_by_handle (self,
1, &self->priv->last_known_self_handle,
features->len, (TpContactFeature *) features->data,
tp_connection_got_self_contact_cb, NULL, NULL, NULL);
G_GNUC_END_IGNORE_DEPRECATIONS
g_array_unref (features);
}
static void
introspect_self_contact (TpConnection *self)
{
self->priv->introspecting_self_contact = TRUE;
get_self_contact (self);
}
static void
got_self_handle (TpConnection *self,
guint self_handle,
const GError *error,
gpointer user_data G_GNUC_UNUSED,
GObject *user_object G_GNUC_UNUSED)
{
g_assert (self->priv->introspection_call != NULL);
self->priv->introspection_call = NULL;
if (error != NULL)
{
DEBUG ("%p: GetSelfHandle() failed: %s", self, error->message);
tp_proxy_invalidate ((TpProxy *) self, error);
return;
}
if (self_handle == 0)
{
GError e = { TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
"GetSelfHandle() returned 0" };
DEBUG ("%s", e.message);
tp_proxy_invalidate ((TpProxy *) self, &e);
return;
}
self->priv->last_known_self_handle = self_handle;
self->priv->introspect_needed = g_list_append (self->priv->introspect_needed,
introspect_self_contact);
tp_connection_continue_introspection (self);
}
static void
on_self_handle_changed (TpConnection *self,
guint self_handle,
gpointer user_data G_GNUC_UNUSED,
GObject *user_object G_GNUC_UNUSED)
{
if (self_handle == 0)
{
DEBUG ("Ignoring alleged change of self-handle to %u", self_handle);
return;
}
if (self->priv->last_known_self_handle == 0)
{
/* We're going to call GetAll(Connection) anyway, or if the CM
* is sufficiently deficient, GetSelfHandle(). */
DEBUG ("Ignoring early self-handle change to %u, we'll pick it up later",
self_handle);
return;
}
DEBUG ("SelfHandleChanged to %u, I wonder what that means?", self_handle);
self->priv->last_known_self_handle = self_handle;
get_self_contact (self);
}
static void
introspect_self_handle (TpConnection *self)
{
if (!self->priv->introspecting_after_connected)
{
tp_connection_continue_introspection (self);
return;
}
g_assert (self->priv->introspection_call == NULL);
self->priv->introspection_call = tp_cli_connection_call_get_self_handle (
self, -1, got_self_handle, NULL, NULL, NULL);
}
/* Appending callbacks to self->priv->introspect_needed relies on this */
G_STATIC_ASSERT (sizeof (TpConnectionProc) <= sizeof (gpointer));
static void
tp_connection_add_interfaces_from_introspection (TpConnection *self,
const gchar **interfaces)
{
TpProxy *proxy = (TpProxy *) self;
tp_proxy_add_interfaces (proxy, interfaces);
if (tp_proxy_has_interface_by_id (proxy,
TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS))
{
self->priv->introspect_needed = g_list_append (
self->priv->introspect_needed, introspect_contacts);
}
else
{
self->priv->ready_enough_for_contacts = TRUE;
}
}
static void
tp_connection_got_interfaces_cb (TpConnection *self,
const gchar **interfaces,
const GError *error,
gpointer user_data,
GObject *user_object)
{
g_assert (self->priv->introspection_call != NULL);
self->priv->introspection_call = NULL;
if (error != NULL)
{
DEBUG ("%p: GetInterfaces() failed, assuming no interfaces: %s",
self, error->message);
interfaces = NULL;
}
DEBUG ("%p: Introspected interfaces", self);
if (tp_proxy_get_invalidated (self) != NULL)
{
DEBUG ("%p: already invalidated, not trying to become ready: %s",
self, tp_proxy_get_invalidated (self)->message);
return;
}
g_assert (self->priv->introspect_needed == NULL);
if (interfaces != NULL)
tp_connection_add_interfaces_from_introspection (self, interfaces);
self->priv->introspect_needed = g_list_append (self->priv->introspect_needed,
introspect_self_handle);
/* FIXME: give subclasses a chance to influence the definition of "ready"
* now that we have our interfaces? */
tp_connection_continue_introspection (self);
}
static void
_tp_connection_got_properties (TpProxy *proxy,
GHashTable *asv,
const GError *error,
gpointer unused G_GNUC_UNUSED,
GObject *unused_object G_GNUC_UNUSED);
static void
tp_connection_status_changed (TpConnection *self,
guint status,
guint reason)
{
DEBUG ("%p: %d -> %d because %d", self, self->priv->status, status, reason);
if (status == TP_CONNECTION_STATUS_CONNECTED)
{
if (self->priv->introspection_call != NULL &&
!self->priv->introspecting_after_connected)
{
/* We thought we knew what was going on, but now the connection has
* gone to CONNECTED and all bets are off. Start again! */
DEBUG ("Cancelling pre-CONNECTED introspection and starting again");
tp_proxy_pending_call_cancel (self->priv->introspection_call);
self->priv->introspection_call = NULL;
g_list_free (self->priv->introspect_needed);
self->priv->introspect_needed = NULL;
}
self->priv->introspecting_after_connected = TRUE;
/* we defer the perceived change to CONNECTED until ready */
if (self->priv->introspection_call == NULL)
{
self->priv->introspection_call =
tp_cli_dbus_properties_call_get_all (self, -1,
TP_IFACE_CONNECTION, _tp_connection_got_properties, NULL, NULL, NULL);
}
}
else
{
self->priv->status = status;
self->priv->status_reason = reason;
g_object_notify ((GObject *) self, "status");
g_object_notify ((GObject *) self, "status-reason");
}
}
static void
tp_connection_connection_error_cb (TpConnection *self,
const gchar *error_name,
GHashTable *details,
gpointer user_data,
GObject *weak_object)
{
g_free (self->priv->connection_error);
self->priv->connection_error = g_strdup (error_name);
if (self->priv->connection_error_details != NULL)
g_hash_table_unref (self->priv->connection_error_details);
self->priv->connection_error_details = g_boxed_copy (
TP_HASH_TYPE_STRING_VARIANT_MAP, details);
}
void
_tp_connection_status_reason_to_gerror (TpConnectionStatusReason reason,
TpConnectionStatus prev_status,
const gchar **ret_str,
GError **error)
{
TpError code;
const gchar *message;
switch (reason)
{
case TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED:
code = TP_ERROR_DISCONNECTED;
message = "Disconnected for unspecified reason";
break;
case TP_CONNECTION_STATUS_REASON_REQUESTED:
code = TP_ERROR_CANCELLED;
message = "User requested disconnection";
break;
case TP_CONNECTION_STATUS_REASON_NETWORK_ERROR:
code = TP_ERROR_NETWORK_ERROR;
message = "Network error";
break;
case TP_CONNECTION_STATUS_REASON_ENCRYPTION_ERROR:
code = TP_ERROR_ENCRYPTION_ERROR;
message = "Encryption error";
break;
case TP_CONNECTION_STATUS_REASON_NAME_IN_USE:
if (prev_status == TP_CONNECTION_STATUS_CONNECTED)
{
code = TP_ERROR_CONNECTION_REPLACED;
message = "Connection replaced";
}
else
{
/* If the connection was with register=TRUE, we should ideally use
* REGISTRATION_EXISTS; but we can't actually tell that from here,
* so we'll have to rely on CMs supporting in-band registration
* (Gabble) to emit ConnectionError */
code = TP_ERROR_ALREADY_CONNECTED;
message = "Already connected (or if registering, registration "
"already exists)";
}
break;
case TP_CONNECTION_STATUS_REASON_CERT_NOT_PROVIDED:
code = TP_ERROR_CERT_NOT_PROVIDED;
message = "Server certificate not provided";
break;
case TP_CONNECTION_STATUS_REASON_CERT_UNTRUSTED:
code = TP_ERROR_CERT_UNTRUSTED;
message = "Server certificate CA not trusted";
break;
case TP_CONNECTION_STATUS_REASON_CERT_EXPIRED:
code = TP_ERROR_CERT_EXPIRED;
message = "Server certificate expired";
break;
case TP_CONNECTION_STATUS_REASON_CERT_NOT_ACTIVATED:
code = TP_ERROR_CERT_NOT_ACTIVATED;
message = "Server certificate not valid yet";
break;
case TP_CONNECTION_STATUS_REASON_CERT_HOSTNAME_MISMATCH:
code = TP_ERROR_CERT_HOSTNAME_MISMATCH;
message = "Server certificate has wrong hostname";
break;
case TP_CONNECTION_STATUS_REASON_CERT_FINGERPRINT_MISMATCH:
code = TP_ERROR_CERT_FINGERPRINT_MISMATCH;
message = "Server certificate fingerprint mismatch";
break;
case TP_CONNECTION_STATUS_REASON_CERT_SELF_SIGNED:
code = TP_ERROR_CERT_SELF_SIGNED;
message = "Server certificate is self-signed";
break;
case TP_CONNECTION_STATUS_REASON_CERT_OTHER_ERROR:
code = TP_ERROR_CERT_INVALID;
message = "Unspecified server certificate error";
break;
default:
g_set_error (error, TP_ERRORS_DISCONNECTED, reason,
"Unknown disconnection reason");
if (ret_str != NULL)
*ret_str = TP_ERROR_STR_DISCONNECTED;
return;
}
g_set_error (error, TP_ERROR, code, "%s", message);
if (ret_str != NULL)
*ret_str = tp_error_get_dbus_name (code);
}
static void
tp_connection_status_changed_cb (TpConnection *self,
guint status,
guint reason,
gpointer user_data,
GObject *weak_object)
{
TpConnectionStatus prev_status = self->priv->status;
/* The status is initially attempted to be discovered starting in the
* constructor. If we don't have the reply for that call yet, ignore this
* signal StatusChanged in order to run the interface introspection only one
* time. We will get the initial introspection reply later anyway. */
if (self->priv->status != TP_UNKNOWN_CONNECTION_STATUS)
{
tp_connection_status_changed (self, status, reason);
}
/* we only want to run this in response to a StatusChanged signal,
* not if the initial status is DISCONNECTED */
if (status == TP_CONNECTION_STATUS_DISCONNECTED)
{
GError *error = NULL;
if (self->priv->connection_error == NULL)
{
_tp_connection_status_reason_to_gerror (reason, prev_status,
NULL, &error);
}
else
{
g_assert (self->priv->connection_error_details != NULL);
tp_proxy_dbus_error_to_gerror (self, self->priv->connection_error,
tp_asv_get_string (self->priv->connection_error_details,
"debug-message"), &error);
/* ... but if we don't know anything about that D-Bus error
* name, we can still be more helpful by deriving an error code from
* TpConnectionStatusReason */
if (g_error_matches (error, TP_DBUS_ERRORS,
TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR))
{
GError *from_csr = NULL;
_tp_connection_status_reason_to_gerror (reason, prev_status,
NULL, &from_csr);
error->domain = from_csr->domain;
error->code = from_csr->code;
g_error_free (from_csr);
}
}
tp_proxy_invalidate ((TpProxy *) self, error);
g_error_free (error);
}
}
static void
tp_connection_got_status_cb (TpConnection *self,
guint status,
const GError *error,
gpointer unused,
GObject *user_object)
{
DEBUG ("%p", self);
g_assert (self->priv->introspection_call != NULL);
self->priv->introspection_call = NULL;
if (error == NULL)
{
DEBUG ("%p: Initial status is %d", self, status);
tp_connection_status_changed (self, status,
TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED);
/* try introspecting before CONNECTED - it might work... */
if (status != TP_CONNECTION_STATUS_CONNECTED &&
self->priv->introspection_call == NULL)
{
self->priv->introspection_call =
tp_cli_connection_call_get_interfaces (self, -1,
tp_connection_got_interfaces_cb, NULL, NULL, NULL);
}
}
else
{
DEBUG ("%p: GetStatus() failed with %s %d \"%s\"",
self, g_quark_to_string (error->domain), error->code,
error->message);
}
}
static void
tp_connection_invalidated (TpConnection *self)
{
if (self->priv->introspection_call != NULL)
{
DEBUG ("Cancelling introspection");
tp_proxy_pending_call_cancel (self->priv->introspection_call);
self->priv->introspection_call = NULL;
}
/* Drop the ref we have on all roster contacts, this is to break the refcycle
* we have between TpConnection and TpContact, otherwise self would never
* run dispose.
* Note that invalidated is also called from dispose, so self->priv->roster
* could already be NULL.
*
* FIXME: When we decide to break tp-glib API/guarantees, we should stop
* TpContact taking a strong ref on its TpConnection and force user to keep
* a ref on the TpConnection to use its TpContact, this would avoid the
* refcycle completely. */
if (self->priv->roster != NULL)
g_hash_table_remove_all (self->priv->roster);
g_clear_object (&self->priv->self_contact);
tp_clear_pointer (&self->priv->blocked_contacts, g_ptr_array_unref);
}
static gboolean
_tp_connection_extract_properties (TpConnection *self,
GHashTable *asv,
guint32 *status,
guint32 *self_handle,
const gchar ***interfaces)
{
gboolean sufficient;
/* has_immortal_handles is a bitfield, so we can't pass a pointer to it */
if (tp_asv_get_boolean (asv, "HasImmortalHandles", NULL))
self->priv->has_immortal_handles = TRUE;
*status = tp_asv_get_uint32 (asv, "Status", &sufficient);
if (!sufficient
|| *status > TP_CONNECTION_STATUS_DISCONNECTED)
return FALSE;
*interfaces = (const gchar **) tp_asv_get_strv (asv, "Interfaces");
if (*interfaces == NULL)
return FALSE;
if (*status == TP_CONNECTION_STATUS_CONNECTED)
{
*self_handle = tp_asv_get_uint32 (asv, "SelfHandle", &sufficient);
if (!sufficient || *self_handle == 0)
return FALSE;
}
else
{
*self_handle = 0;
}
return TRUE;
}
static void
_tp_connection_got_properties (TpProxy *proxy,
GHashTable *asv,
const GError *error,
gpointer unused G_GNUC_UNUSED,
GObject *unused_object G_GNUC_UNUSED)
{
TpConnection *self = TP_CONNECTION (proxy);
guint32 status;
guint32 self_handle;
const gchar **interfaces;
if (tp_proxy_get_invalidated (self) != NULL)
{
DEBUG ("%p: already invalidated, not trying to become ready: %s",
self, tp_proxy_get_invalidated (self)->message);
return;
}
if (self->priv->introspection_call)
self->priv->introspection_call = NULL;
if (error == NULL &&
_tp_connection_extract_properties (
self,
asv,
&status,
&self_handle,
&interfaces))
{
tp_connection_add_interfaces_from_introspection (self, interfaces);
if (status == TP_CONNECTION_STATUS_CONNECTED)
{
self->priv->introspecting_after_connected = TRUE;
self->priv->last_known_self_handle = self_handle;
self->priv->introspect_needed = g_list_append (
self->priv->introspect_needed, introspect_self_contact);
}
else
{
tp_connection_status_changed (self, status,
TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED);
}
tp_connection_continue_introspection (self);
return;
}
else if (error != NULL)
{
DEBUG ("GetAll failed: %s", error->message);
}
DEBUG ("Could not extract all required properties from GetAll return, "
"will use 0.18 API instead");
if (self->priv->introspection_call == NULL)
{
if (self->priv->status == TP_UNKNOWN_CONNECTION_STATUS &&
!self->priv->introspecting_after_connected)
{
/* get my initial status */
DEBUG ("Calling GetStatus");
self->priv->introspection_call =
tp_cli_connection_call_get_status (self, -1,
tp_connection_got_status_cb, NULL, NULL, NULL);
}
else
{
self->priv->introspection_call =
tp_cli_connection_call_get_interfaces (self, -1,
tp_connection_got_interfaces_cb, NULL, NULL, NULL);
}
}
}
static gboolean _tp_connection_parse (const gchar *path_or_bus_name,
char delimiter,
gchar **protocol,
gchar **cm_name);
static void
tp_connection_constructed (GObject *object)
{
GObjectClass *object_class = (GObjectClass *) tp_connection_parent_class;
TpConnection *self = TP_CONNECTION (object);
const gchar *object_path;
if (object_class->constructed != NULL)
object_class->constructed (object);
DEBUG ("%s (%p) constructed", tp_proxy_get_object_path (object), object);
_tp_proxy_ensure_factory (self, NULL);
/* Connect to my own StatusChanged signal.
* The connection hasn't had a chance to become invalid yet, so we can
* assume that this signal connection will work */
tp_cli_connection_connect_to_status_changed (self,
tp_connection_status_changed_cb, NULL, NULL, NULL, NULL);
tp_cli_connection_connect_to_connection_error (self,
tp_connection_connection_error_cb, NULL, NULL, NULL, NULL);
/* We need to connect to SelfHandleChanged early, too, so that we're
* already connected before we GetAll */
tp_cli_connection_connect_to_self_handle_changed (self,
on_self_handle_changed, NULL, NULL, NULL, NULL);
object_path = tp_proxy_get_object_path (TP_PROXY (self));
g_assert (_tp_connection_parse (object_path, '/',
&(self->priv->proto_name), &(self->priv->cm_name)));
tp_cli_dbus_properties_call_get_all (self, -1,
TP_IFACE_CONNECTION, _tp_connection_got_properties, NULL, NULL, NULL);
/* Give a chance to TpAccount to know about invalidated connection before we
* unref all roster contacts. This is to let applications properly remove all
* contacts at once instead of getting weak notify on each. */
g_signal_connect_after (self, "invalidated",
G_CALLBACK (tp_connection_invalidated), NULL);
}
static void
tp_connection_init (TpConnection *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_CONNECTION,
TpConnectionPrivate);
self->priv->status = TP_UNKNOWN_CONNECTION_STATUS;
self->priv->status_reason = TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED;
self->priv->contacts = g_hash_table_new (g_direct_hash, g_direct_equal);
self->priv->introspection_call = NULL;
self->priv->interests = tp_intset_new ();
self->priv->contact_groups = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (self->priv->contact_groups, NULL);
self->priv->roster = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, g_object_unref);
self->priv->contacts_changed_queue = g_queue_new ();
g_queue_init (&self->priv->capabilities_queue);
self->priv->blocked_contacts = g_ptr_array_new_with_free_func (
g_object_unref);
self->priv->blocked_changed_queue = g_queue_new ();
}
static void
tp_connection_finalize (GObject *object)
{
TpConnection *self = TP_CONNECTION (object);
DEBUG ("%p", self);
tp_clear_pointer (&self->priv->cm_name, g_free);
tp_clear_pointer (&self->priv->proto_name, g_free);
/* not true unless we were finalized before we were ready */
if (self->priv->introspect_needed != NULL)
{
g_list_free (self->priv->introspect_needed);
self->priv->introspect_needed = NULL;
}
if (self->priv->contact_attribute_interfaces != NULL)
{
g_array_unref (self->priv->contact_attribute_interfaces);
self->priv->contact_attribute_interfaces = NULL;
}
g_free (self->priv->connection_error);
self->priv->connection_error = NULL;
if (self->priv->connection_error_details != NULL)
{
g_hash_table_unref (self->priv->connection_error_details);
self->priv->connection_error_details = NULL;
}
if (self->priv->avatar_request_queue != NULL)
{
g_array_unref (self->priv->avatar_request_queue);
self->priv->avatar_request_queue = NULL;
}
if (self->priv->avatar_request_idle_id != 0)
{
g_source_remove (self->priv->avatar_request_idle_id);
self->priv->avatar_request_idle_id = 0;
}
tp_contact_info_spec_list_free (self->priv->contact_info_supported_fields);
self->priv->contact_info_supported_fields = NULL;
tp_clear_pointer (&self->priv->balance_currency, g_free);
tp_clear_pointer (&self->priv->balance_uri, g_free);
tp_clear_pointer (&self->priv->cm_name, g_free);
tp_clear_pointer (&self->priv->proto_name, g_free);
((GObjectClass *) tp_connection_parent_class)->finalize (object);
}
static void
contact_notify_disposed (gpointer k G_GNUC_UNUSED,
gpointer v,
gpointer d G_GNUC_UNUSED)
{
_tp_contact_connection_disposed (v);
}
static void
tp_connection_dispose (GObject *object)
{
TpConnection *self = TP_CONNECTION (object);
DEBUG ("%p", object);
if (self->priv->account != NULL)
{
g_object_remove_weak_pointer ((GObject *) self->priv->account,
(gpointer) &self->priv->account);
self->priv->account = NULL;
}
tp_clear_pointer (&self->priv->contact_groups, g_ptr_array_unref);
tp_clear_pointer (&self->priv->roster, g_hash_table_unref);
tp_clear_pointer (&self->priv->contacts_changed_queue,
_tp_connection_contacts_changed_queue_free);
tp_clear_pointer (&self->priv->blocked_changed_queue,
_tp_connection_blocked_changed_queue_free);
if (self->priv->contacts != NULL)
{
g_hash_table_foreach (self->priv->contacts, contact_notify_disposed,
NULL);
tp_clear_pointer (&self->priv->contacts, g_hash_table_unref);
}
tp_clear_object (&self->priv->capabilities);
tp_clear_pointer (&self->priv->avatar_requirements,
tp_avatar_requirements_destroy);
if (self->priv->interests != NULL)
{
guint size = tp_intset_size (self->priv->interests);
/* Before freeing the set of tokens in which we declared an
* interest, cancel those interests. We'll still get the signals
* if there's another interested TpConnection in this process,
* because the CM uses distributed refcounting. */
if (size > 0)
{
TpIntsetFastIter iter;
GPtrArray *strings;
guint element;
strings = g_ptr_array_sized_new (size + 1);
tp_intset_fast_iter_init (&iter, self->priv->interests);
while (tp_intset_fast_iter_next (&iter, &element))
g_ptr_array_add (strings,
(gchar *) g_quark_to_string (element));
g_ptr_array_add (strings, NULL);
/* no callback - if the CM replies, we'll ignore it anyway */
tp_cli_connection_call_remove_client_interest (self, -1,
(const gchar **) strings->pdata, NULL, NULL, NULL, NULL);
g_ptr_array_unref (strings);
}
tp_intset_destroy (self->priv->interests);
self->priv->interests = NULL;
}
tp_clear_pointer (&self->priv->blocked_contacts, g_ptr_array_unref);
g_clear_object (&self->priv->self_contact);
((GObjectClass *) tp_connection_parent_class)->dispose (object);
}
enum {
FEAT_CORE,
FEAT_CONNECTED,
FEAT_CAPABILITIES,
FEAT_AVATAR_REQUIREMENTS,
FEAT_CONTACT_INFO,
FEAT_BALANCE,
FEAT_CONTACT_LIST,
FEAT_CONTACT_LIST_PROPS,
FEAT_CONTACT_GROUPS,
FEAT_CONTACT_BLOCKING,
FEAT_ALIASING,
N_FEAT
};
static const TpProxyFeature *
tp_connection_list_features (TpProxyClass *cls G_GNUC_UNUSED)
{
static TpProxyFeature features[N_FEAT + 1] = { { 0 } };
static GQuark need_requests[2] = {0, 0};
static GQuark need_avatars[2] = {0, 0};
static GQuark need_contact_info[2] = {0, 0};
static GQuark need_balance[2] = {0, 0};
static GQuark need_contact_list[3] = {0, 0, 0};
static GQuark need_contact_groups[2] = {0, 0};
static GQuark need_contact_blocking[2] = {0, 0};
static GQuark depends_contact_list[2] = {0, 0};
static GQuark need_aliasing[2] = {0, 0};
if (G_LIKELY (features[0].name != 0))
return features;
features[FEAT_CORE].name = TP_CONNECTION_FEATURE_CORE;
features[FEAT_CORE].core = TRUE;
features[FEAT_CONNECTED].name = TP_CONNECTION_FEATURE_CONNECTED;
features[FEAT_CAPABILITIES].name = TP_CONNECTION_FEATURE_CAPABILITIES;
features[FEAT_CAPABILITIES].prepare_async =
tp_connection_prepare_capabilities_async;
need_requests[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS;
features[FEAT_CAPABILITIES].interfaces_needed = need_requests;
features[FEAT_AVATAR_REQUIREMENTS].name = TP_CONNECTION_FEATURE_AVATAR_REQUIREMENTS;
features[FEAT_AVATAR_REQUIREMENTS].prepare_async =
_tp_connection_prepare_avatar_requirements_async;
need_avatars[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS;
features[FEAT_AVATAR_REQUIREMENTS].interfaces_needed = need_avatars;
features[FEAT_CONTACT_INFO].name = TP_CONNECTION_FEATURE_CONTACT_INFO;
features[FEAT_CONTACT_INFO].prepare_async =
_tp_connection_prepare_contact_info_async;
need_contact_info[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO;
features[FEAT_CONTACT_INFO].interfaces_needed = need_contact_info;
features[FEAT_BALANCE].name = TP_CONNECTION_FEATURE_BALANCE;
features[FEAT_BALANCE].prepare_async = tp_connection_prepare_balance_async;
need_balance[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_BALANCE;
features[FEAT_BALANCE].interfaces_needed = need_balance;
features[FEAT_CONTACT_LIST].name = TP_CONNECTION_FEATURE_CONTACT_LIST;
features[FEAT_CONTACT_LIST].prepare_async = _tp_connection_prepare_contact_list_async;
need_contact_list[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_LIST;
need_contact_list[1] = TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS;
features[FEAT_CONTACT_LIST].interfaces_needed = need_contact_list;
depends_contact_list[0] = TP_CONNECTION_FEATURE_CONTACT_LIST_PROPERTIES;
features[FEAT_CONTACT_LIST].depends_on = depends_contact_list;
features[FEAT_CONTACT_LIST_PROPS].name = TP_CONNECTION_FEATURE_CONTACT_LIST_PROPERTIES;
features[FEAT_CONTACT_LIST_PROPS].prepare_async = _tp_connection_prepare_contact_list_props_async;
features[FEAT_CONTACT_LIST_PROPS].interfaces_needed = need_contact_list;
features[FEAT_CONTACT_GROUPS].name = TP_CONNECTION_FEATURE_CONTACT_GROUPS;
features[FEAT_CONTACT_GROUPS].prepare_async = _tp_connection_prepare_contact_groups_async;
need_contact_groups[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_GROUPS;
features[FEAT_CONTACT_GROUPS].interfaces_needed = need_contact_groups;
features[FEAT_CONTACT_BLOCKING].name = TP_CONNECTION_FEATURE_CONTACT_BLOCKING;
features[FEAT_CONTACT_BLOCKING].prepare_async = _tp_connection_prepare_contact_blocking_async;
need_contact_blocking[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING;
features[FEAT_CONTACT_BLOCKING].interfaces_needed = need_contact_blocking;
features[FEAT_ALIASING].name = TP_CONNECTION_FEATURE_ALIASING;
features[FEAT_ALIASING].prepare_async = _tp_connection_prepare_aliasing_async;
need_aliasing[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING;
features[FEAT_ALIASING].interfaces_needed = need_aliasing;
/* assert that the terminator at the end is there */
g_assert (features[N_FEAT].name == 0);
return features;
}
static void
tp_connection_class_init (TpConnectionClass *klass)
{
GParamSpec *param_spec;
TpProxyClass *proxy_class = (TpProxyClass *) klass;
GObjectClass *object_class = (GObjectClass *) klass;
tp_connection_init_known_interfaces ();
g_type_class_add_private (klass, sizeof (TpConnectionPrivate));
object_class->constructed = tp_connection_constructed;
object_class->get_property = tp_connection_get_property;
object_class->dispose = tp_connection_dispose;
object_class->finalize = tp_connection_finalize;
proxy_class->interface = TP_IFACE_QUARK_CONNECTION;
/* If you change this, you must also change TpChannel to stop asserting
* that its connection has a unique name */
proxy_class->must_have_unique_name = TRUE;
proxy_class->list_features = tp_connection_list_features;
/**
* TpConnection:status:
*
* This connection's status, or %TP_UNKNOWN_CONNECTION_STATUS if we don't
* know yet.
*
* To wait for a valid status (and other properties), call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_CORE.
*
* Since version 0.11.3, the change to status
* %TP_CONNECTION_STATUS_CONNECTED is delayed slightly, until introspection
* of the connection has finished.
*/
param_spec = g_param_spec_uint ("status", "Status",
"The status of this connection", 0, G_MAXUINT32,
TP_UNKNOWN_CONNECTION_STATUS,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_STATUS,
param_spec);
/**
* TpConnection:connection-manager-name:
*
* This connection's connection manager name.
*
* Since: 0.13.16
* Deprecated: Use #TpConnection:cm-name instead.
*/
g_object_class_install_property (object_class, PROP_CONNECTION_MANAGER_NAME,
g_param_spec_string ("connection-manager-name",
"Connection manager name",
"The connection's connection manager name",
NULL,
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
/**
* TpConnection:cm-name:
*
* This connection's connection manager name.
*
* Since: 0.19.3
*/
g_object_class_install_property (object_class, PROP_CM_NAME,
g_param_spec_string ("cm-name",
"Connection manager name",
"The connection's connection manager name",
NULL,
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
/**
* TpConnection:protocol-name:
*
* The connection's machine-readable protocol name, such as "jabber",
* "msn" or "local-xmpp". Recommended names for most protocols can be
* found in the Telepathy D-Bus Interface Specification.
*
* Since: 0.13.16
*
*/
g_object_class_install_property (object_class, PROP_PROTOCOL_NAME,
g_param_spec_string ("protocol-name",
"Protocol name",
"The connection's protocol name",
NULL,
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
/**
* TpConnection:self-handle:
*
* The %TP_HANDLE_TYPE_CONTACT handle of the local user on this connection,
* or 0 if we don't know yet or if the connection has become invalid.
*
* This may change if the local user's unique identifier changes (for
* instance by using /nick on IRC), in which case #GObject::notify will be
* emitted.
*
* To wait for a valid self-handle (and other properties), call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONNECTED.
*
* Deprecated: Use #TpConnection:self-contact instead.
*/
param_spec = g_param_spec_uint ("self-handle", "Self handle",
"The local user's Contact handle on this connection", 0, G_MAXUINT32,
0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_SELF_HANDLE,
param_spec);
/**
* TpConnection:self-contact:
*
* A #TpContact representing the local user on this connection,
* or %NULL if not yet available.
*
* If the local user's unique identifier changes (for instance by using
* /nick on IRC), this property will change to a different #TpContact object
* representing the new identifier, and #GObject::notify will be emitted.
*
* The #TpContact object is guaranteed to have all of the features previously
* passed to tp_simple_client_factory_add_contact_features() prepared.
*
* To wait for a non-%NULL self-contact (and other properties), call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONNECTED.
*
* Since: 0.13.9
*/
param_spec = g_param_spec_object ("self-contact", "Self contact",
"The local user's Contact object on this connection", TP_TYPE_CONTACT,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_SELF_CONTACT,
param_spec);
/**
* TpConnection:status-reason:
*
* To wait for a valid status (and other properties), call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_CORE.
*
* The reason why #TpConnection:status changed to its current value,
* or TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED if unknown.
* know yet.
*/
param_spec = g_param_spec_uint ("status-reason", "Last status change reason",
"The reason why #TpConnection:status changed to its current value",
0, G_MAXUINT32, TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_STATUS_REASON,
param_spec);
/**
* TpConnection:connection-ready:
*
* Initially %FALSE; changes to %TRUE when the connection has gone to
* CONNECTED status, introspection has finished and it's ready for use.
*
* By the time this property becomes %TRUE, any extra interfaces will
* have been set up and the #TpProxy:interfaces property will have been
* populated.
*
* This is similar to %TP_CONNECTION_FEATURE_CONNECTED, except that once
* it has changed to %TRUE, it remains %TRUE even if the connection has
* been invalidated.
*
* Deprecated: 0.17.6: use tp_proxy_is_prepared() with
* %TP_CHANNEL_FEATURE_CONNECTED for checks, or tp_proxy_prepare_async() for
* notification
*/
param_spec = g_param_spec_boolean ("connection-ready", "Connection ready?",
"Initially FALSE; changes to TRUE when introspection finishes", FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED);
g_object_class_install_property (object_class, PROP_CONNECTION_READY,
param_spec);
/**
* TpConnection:capabilities:
*
* The %TpCapabilities object representing the capabilities of this
* connection, or NULL if we don't know yet.
*
* To wait for valid capability information, call tp_proxy_prepare_async()
* with the feature %TP_CONNECTION_FEATURE_CAPABILITIES.
*/
param_spec = g_param_spec_object ("capabilities", "Capabilities",
"A TpCapabilities object representing the capabilities of the connection",
TP_TYPE_CAPABILITIES,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CAPABILITIES,
param_spec);
/**
* TpConnection:balance:
*
* The Amount field of the Balance.AccountBalance property.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_BALANCE.
*
* See Also: tp_connection_get_balance()
*/
param_spec = g_param_spec_int ("balance", "Balance Amount",
"The Amount field of the Account Balance",
G_MININT32, G_MAXINT32, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_BALANCE,
param_spec);
/**
* TpConnection:balance-scale:
*
* The Scale field of the Balance.AccountBalance property.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_BALANCE.
*
* See Also: tp_connection_get_balance()
*/
param_spec = g_param_spec_uint ("balance-scale", "Balance Scale",
"The Scale field of the Account Balance",
0, G_MAXUINT32, G_MAXUINT32,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_BALANCE_SCALE,
param_spec);
/**
* TpConnection:balance-currency:
*
* The Currency field of the Balance.AccountBalance property.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_BALANCE.
*
* See Also: tp_connection_get_balance()
*/
param_spec = g_param_spec_string ("balance-currency", "Balance Currency",
"The Currency field of the Account Balance",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_BALANCE_CURRENCY,
param_spec);
/**
* TpConnection:balance-uri:
*
* The Balance.ManageCreditURI property.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_BALANCE.
*/
param_spec = g_param_spec_string ("balance-uri", "Balance URI",
"The URI for managing the account balance",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_BALANCE_URI,
param_spec);
/**
* TpConnection::balance-changed:
* @self: a channel
* @balance: the value of the #TpConnection:balance property
* @balance_scale: the value of the #TpConnection:balance-scale property
* @balance_currency: the value of the #TpConnection:balance-currency property
*
* Emitted when at least one of the #TpConnection:balance,
* #TpConnection:balance-scale or #TpConnection:balance-currency
* property is changed.
*
* For this signal to be emitted, you must first call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_BALANCE.
*
* Since: 0.15.1
*/
signals[SIGNAL_BALANCE_CHANGED] = g_signal_new ("balance-changed",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_UINT, G_TYPE_STRING);
/**
* TpConnection:contact-list-state:
*
* The progress made in retrieving the contact list.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_LIST_PROPERTIES or
* %TP_CONNECTION_FEATURE_CONTACT_LIST.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_uint ("contact-list-state", "ContactList state",
"The state of the contact list",
0, G_MAXUINT, TP_CONTACT_LIST_STATE_NONE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CONTACT_LIST_STATE,
param_spec);
/**
* TpConnection:contact-list-persists:
*
* If true, presence subscriptions (in both directions) on this connection are
* stored by the server or other infrastructure.
*
* If false, presence subscriptions on this connection are not stored.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_LIST_PROPERTIES or
* %TP_CONNECTION_FEATURE_CONTACT_LIST.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_boolean ("contact-list-persists",
"ContactList persists", "Whether the contact list persists",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CONTACT_LIST_PERSISTS,
param_spec);
/**
* TpConnection:can-change-contact-list:
*
* If true, presence subscription and publication can be changed using the
* RequestSubscription, AuthorizePublication and RemoveContacts methods.
*
* Rational: link-local XMPP, presence is implicitly published to everyone in
* the local subnet, so the user cannot control their presence publication.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_LIST_PROPERTIES or
* %TP_CONNECTION_FEATURE_CONTACT_LIST.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_boolean ("can-change-contact-list",
"ContactList can change", "Whether the contact list can change",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CAN_CHANGE_CONTACT_LIST,
param_spec);
/**
* TpConnection:request-uses-message:
*
* If true, the Message parameter to RequestSubscription is likely to be
* significant, and user interfaces SHOULD prompt the user for a message to
* send with the request; a message such as "I would like to add you to my
* contact list", translated into the local user's language, might make a
* suitable default.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_LIST_PROPERTIES or
* %TP_CONNECTION_FEATURE_CONTACT_LIST.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_boolean ("request-uses-message",
"Request Uses Message", "Whether request uses message",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_REQUEST_USES_MESSAGE,
param_spec);
/**
* TpConnection:disjoint-groups:
*
* True if each contact can be in at most one group; false if each contact
* can be in many groups.
*
* This property cannot change after the connection has moved to the
* %TP_CONNECTION_STATUS_CONNECTED state. Until then, its value is undefined,
* and it may change at any time, without notification.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_GROUPS.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_boolean ("disjoint-groups",
"Disjoint Groups", "Whether groups are disjoint",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_DISJOINT_GROUPS,
param_spec);
/**
* TpConnection:group-storage:
*
* Indicates the extent to which contacts' groups can be set and stored.
*
* This property cannot change after the connection has moved to the
* %TP_CONNECTION_STATUS_CONNECTED state. Until then, its value is undefined,
* and it may change at any time, without notification.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_GROUPS.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_uint ("group-storage",
"Group Storage", "Group storage capabilities",
0, G_MAXUINT, TP_CONTACT_METADATA_STORAGE_TYPE_NONE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_GROUP_STORAGE,
param_spec);
/**
* TpConnection:contact-groups:
*
* The names of all groups that currently exist. This may be a larger set than
* the union of all #TpContact:contact-groups properties, if the connection
* allows groups to be empty.
*
* This property's value is not meaningful until the
* #TpConnection:contact-list-state property has become
* %TP_CONTACT_LIST_STATE_SUCCESS.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_GROUPS.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_boxed ("contact-groups",
"Contact Groups",
"All existing contact groups",
G_TYPE_STRV,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CONTACT_GROUPS,
param_spec);
/**
* TpConnection:can-report-abusive:
*
* If this property is %TRUE, contacts may be reported as abusive to the
* server administrators by setting report_abusive to %TRUE when calling
* tp_connection_block_contacts_async().
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_BLOCKING.
*
* Since: 0.17.0
*/
param_spec = g_param_spec_boolean ("can-report-abusive",
"Can report abusive",
"Can report abusive",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CAN_REPORT_ABUSIVE,
param_spec);
/**
* TpConnection:blocked-contacts:
*
* A #GPtrArray of blocked #TpContact. Changes are notified using the
* #TpConnection::blocked-contacts-changed signal.
*
* These TpContact objects have been prepared with the desired features.
* See tp_simple_client_factory_add_contact_features() to define which
* features needs to be prepared on them.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_BLOCKING.
*
* Since: 0.17.0
*/
param_spec = g_param_spec_boxed ("blocked-contacts",
"blocked contacts",
"Blocked contacts",
G_TYPE_PTR_ARRAY,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_BLOCKED_CONTACTS,
param_spec);
/**
* TpConnection::groups-created:
* @self: a #TpConnection
* @added: a #GStrv with the names of the new groups.
*
* Emitted when new, empty groups are created. This will often be followed by
* #TpContact::contact-groups-changed signals that add some members. When this
* signal is emitted, #TpConnection:contact-groups property is already
* updated.
*
* For this signal to be emited, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_GROUPS.
*
* Since: 0.15.5
*/
signals[SIGNAL_GROUPS_CREATED] = g_signal_new (
"groups-created",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_STRV);
/**
* TpConnection::groups-removed:
* @self: A #TpConnection
* @added: A #GStrv with the names of the groups.
*
* Emitted when one or more groups are removed. If they had members at the
* time that they were removed, then immediately after this signal is emitted,
* #TpContact::contact-groups-changed signals that their members were removed.
* When this signal is emitted, #TpConnection:contact-groups property is
* already updated.
*
* For this signal to be emited, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_GROUPS.
*
* Since: 0.15.5
*/
signals[SIGNAL_GROUPS_REMOVED] = g_signal_new (
"groups-removed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_STRV);
/**
* TpConnection::group-renamed:
* @self: a #TpConnection
* @old_name: the old name of the group.
* @new_name: the new name of the group.
*
* Emitted when a group is renamed, in protocols where this can be
* distinguished from group creation, removal and membership changes.
*
* Immediately after this signal is emitted, #TpConnection::groups-created
* signal the creation of a group with the new name, and
* #TpConnection::groups-removed signal the removal of a group with the old
* name.
* If the group was not empty, immediately after those signals are emitted,
* #TpContact::contact-groups-changed signal that the members of that group
* were removed from the old name and added to the new name.
*
* When this signal is emitted, #TpConnection:contact-groups property is
* already updated.
*
* For this signal to be emited, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_GROUPS.
*
* Since: 0.15.5
*/
signals[SIGNAL_GROUP_RENAMED] = g_signal_new (
"group-renamed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
/**
* TpConnection::contact-list-changed:
* @self: a #TpConnection
* @added: (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
* a #GPtrArray of #TpContact added to contacts list
* @removed: (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
* a #GPtrArray of #TpContact removed from contacts list
*
* Notify of changes in the list of contacts as returned by
* tp_connection_dup_contact_list(). It is guaranteed that all contacts have
* desired features prepared. See
* tp_simple_client_factory_add_contact_features() to define which features
* needs to be prepared.
*
* This signal is also emitted for the initial set of contacts once retrieved.
*
* For this signal to be emitted, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_LIST.
*
* Since: 0.15.5
*/
signals[SIGNAL_CONTACT_LIST_CHANGED] = g_signal_new (
"contact-list-changed",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_PTR_ARRAY, G_TYPE_PTR_ARRAY);
/**
* TpConnection::blocked-contacts-changed:
* @self: a #TpConnection
* @added: (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
* a #GPtrArray of #TpContact which have been blocked
* @removed: (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
* a #GPtrArray of #TpContact which are no longer blocked
*
* Notify of changes in #TpConnection:blocked-contacts.
* It is guaranteed that all contacts have desired features prepared. See
* tp_simple_client_factory_add_contact_features() to define which features
* needs to be prepared.
*
* This signal is also emitted for the initial set of blocked contacts once
* retrieved.
*
* For this signal to be emitted, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_BLOCKING.
*
* Since: 0.17.0
*/
signals[SIGNAL_BLOCKED_CONTACTS_CHANGED] = g_signal_new (
"blocked-contacts-changed",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_PTR_ARRAY, G_TYPE_PTR_ARRAY);
}
/**
* tp_connection_new:
* @dbus: a D-Bus daemon; may not be %NULL
* @bus_name: (allow-none): the well-known or unique name of the connection
* process; if well-known, this function will make a blocking call to the bus
* daemon to resolve the unique name. May be %NULL if @object_path is not, in
* which case a well-known name will be derived from @object_path.
* @object_path: (allow-none): the object path of the connection process.
* May be %NULL if @bus_name is a well-known name, in which case the object
* path will be derived from @bus_name.
* @error: used to indicate the error if %NULL is returned
*
*
*
* Returns: a new connection proxy, or %NULL if unique-name resolution
* fails or on invalid arguments
*
* Since: 0.7.1
* Deprecated: Use tp_simple_client_factory_ensure_connection() instead.
*/
TpConnection *
tp_connection_new (TpDBusDaemon *dbus,
const gchar *bus_name,
const gchar *object_path,
GError **error)
{
return _tp_connection_new_with_factory (NULL, dbus, bus_name, object_path,
error);
}
TpConnection *
_tp_connection_new_with_factory (TpSimpleClientFactory *factory,
TpDBusDaemon *dbus,
const gchar *bus_name,
const gchar *object_path,
GError **error)
{
gchar *dup_path = NULL;
gchar *dup_name = NULL;
gchar *dup_unique_name = NULL;
TpConnection *ret = NULL;
g_return_val_if_fail (TP_IS_DBUS_DAEMON (dbus), NULL);
g_return_val_if_fail (object_path != NULL ||
(bus_name != NULL && bus_name[0] != ':'), NULL);
if (object_path == NULL)
{
dup_path = g_strdelimit (g_strdup_printf ("/%s", bus_name), ".", '/');
object_path = dup_path;
}
else if (bus_name == NULL)
{
dup_name = g_strdelimit (g_strdup (object_path + 1), "/", '.');
bus_name = dup_name;
}
if (!_tp_connection_parse (object_path, '/', NULL, NULL))
{
g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_INVALID_OBJECT_PATH,
"Connection object path is not in the right format");
goto finally;
}
if (!tp_dbus_check_valid_bus_name (bus_name,
TP_DBUS_NAME_TYPE_NOT_BUS_DAEMON, error))
goto finally;
/* Resolve unique name if necessary */
if (bus_name[0] != ':')
{
if (!_tp_dbus_daemon_get_name_owner (dbus, 2000, bus_name,
&dup_unique_name, error))
goto finally;
bus_name = dup_unique_name;
if (!tp_dbus_check_valid_bus_name (bus_name,
TP_DBUS_NAME_TYPE_UNIQUE, error))
goto finally;
}
if (!tp_dbus_check_valid_object_path (object_path, error))
goto finally;
ret = TP_CONNECTION (g_object_new (TP_TYPE_CONNECTION,
"dbus-daemon", dbus,
"bus-name", bus_name,
"object-path", object_path,
"factory", factory,
NULL));
finally:
g_free (dup_path);
g_free (dup_name);
g_free (dup_unique_name);
return ret;
}
/**
* tp_connection_get_account:
* @self: a connection
*
* Return the the #TpAccount associated with this connection. Will return %NULL
* if @self was not acquired from a #TpAccount via tp_account_get_connection(),
* or if the account object got finalized in the meantime (#TpConnection does
* not keep a strong ref on its #TpAccount).
*
* Returns: (transfer none): the account associated with this connection, or
* %NULL.
*
* Since: 0.15.5
*/
TpAccount *
tp_connection_get_account (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return self->priv->account;
}
void
_tp_connection_set_account (TpConnection *self,
TpAccount *account)
{
if (self->priv->account == account)
return;
g_assert (self->priv->account == NULL);
g_assert (account != NULL);
self->priv->account = account;
g_object_add_weak_pointer ((GObject *) account,
(gpointer) &self->priv->account);
}
/**
* tp_connection_get_self_handle:
* @self: a connection
*
* Return the %TP_HANDLE_TYPE_CONTACT handle of the local user on this
* connection, or 0 if the self-handle is not known yet or the connection
* has become invalid (the TpProxy::invalidated signal).
*
* The returned handle is not necessarily valid forever (the
* notify::self-handle signal will be emitted if it changes, which can happen
* on protocols such as IRC). Construct a #TpContact object if you want to
* track the local user's identifier in the protocol, or other information
* like their presence status, over time.
*
* Returns: the value of the TpConnection:self-handle property
*
* Since: 0.7.26
* Deprecated: Use tp_connection_get_self_contact() instead.
*/
TpHandle
tp_connection_get_self_handle (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), 0);
if (self->priv->self_contact == NULL)
return 0;
return tp_contact_get_handle (self->priv->self_contact);
}
/**
* tp_connection_get_status:
* @self: a connection
* @reason: (out): a TpConnectionStatusReason, or %NULL
*
* If @reason is not %NULL it is set to the reason why "status" changed to its
* current value, or %TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED if unknown.
*
* Returns: This connection's status, or %TP_UNKNOWN_CONNECTION_STATUS if we
* don't know yet.
*
* Since: 0.7.14
*/
TpConnectionStatus
tp_connection_get_status (TpConnection *self,
TpConnectionStatusReason *reason)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), TP_UNKNOWN_CONNECTION_STATUS);
if (reason != NULL)
*reason = self->priv->status_reason;
return self->priv->status;
}
/**
* tp_connection_get_connection_manager_name:
* @self: a #TpConnection
*
*
*
* Returns: the same as the #TpConnection:connection-manager-name property
*
* Since: 0.13.16
* Deprecated: Use tp_connection_get_cm_name() instead.
*
*/
const gchar *
tp_connection_get_connection_manager_name (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return self->priv->cm_name;
}
/**
* tp_connection_get_cm_name:
* @self: a #TpConnection
*
*
*
* Returns: the same as the #TpConnection:cm-name property
*
* Since: 0.19.3
*
*/
const gchar *
tp_connection_get_cm_name (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return self->priv->cm_name;
}
/**
* tp_connection_get_protocol_name:
* @self: a #TpConnection
*
*
*
* Returns: the same as the #TpConnection:protocol-name property
*
* Since: 0.13.16
*
*/
const gchar *
tp_connection_get_protocol_name (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return self->priv->proto_name;
}
/**
* tp_connection_run_until_ready: (skip)
* @self: a connection
* @connect: if %TRUE, call Connect() if it appears to be necessary;
* if %FALSE, rely on Connect() to be called by another client
* @error: if not %NULL and %FALSE is returned, used to raise an error
* @loop: if not %NULL, a #GMainLoop is placed here while it is being run
* (so calling code can call g_main_loop_quit() to abort), and %NULL is
* placed here after the loop has been run
*
* If @self is connected and ready for use, return immediately. Otherwise,
* call Connect() (unless @connect is %FALSE) and re-enter the main loop
* until the connection becomes invalid, the connection connects successfully
* and is introspected, or the main loop stored via @loop is cancelled.
*
* Returns: %TRUE if the connection is now connected and ready for use,
* %FALSE if the connection has become invalid.
*
* Since: 0.7.1
* Deprecated: 0.11.0: Use tp_proxy_prepare_async() and re-enter the main
* loop yourself, or restructure your program in such a way as to avoid
* re-entering the main loop.
*/
typedef struct {
GMainLoop *loop;
TpProxyPendingCall *pc;
GError *connect_error /* gets initialized */;
} RunUntilReadyData;
static void
run_until_ready_ret (TpConnection *self,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
RunUntilReadyData *data = user_data;
if (error != NULL)
{
g_main_loop_quit (data->loop);
data->connect_error = g_error_copy (error);
}
}
static void
run_until_ready_destroy (gpointer p)
{
RunUntilReadyData *data = p;
data->pc = NULL;
}
gboolean
tp_connection_run_until_ready (TpConnection *self,
gboolean connect,
GError **error,
GMainLoop **loop)
{
TpProxy *as_proxy = (TpProxy *) self;
gulong invalidated_id, ready_id;
RunUntilReadyData data = { NULL, NULL, NULL };
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
if (as_proxy->invalidated)
goto raise_invalidated;
if (self->priv->ready)
return TRUE;
data.loop = g_main_loop_new (NULL, FALSE);
invalidated_id = g_signal_connect_swapped (self, "invalidated",
G_CALLBACK (g_main_loop_quit), data.loop);
ready_id = g_signal_connect_swapped (self, "notify::connection-ready",
G_CALLBACK (g_main_loop_quit), data.loop);
if (self->priv->status != TP_CONNECTION_STATUS_CONNECTED &&
connect)
{
data.pc = tp_cli_connection_call_connect (self, -1,
run_until_ready_ret, &data,
run_until_ready_destroy, NULL);
}
if (data.connect_error == NULL)
{
if (loop != NULL)
*loop = data.loop;
g_main_loop_run (data.loop);
if (loop != NULL)
*loop = NULL;
}
if (data.pc != NULL)
tp_proxy_pending_call_cancel (data.pc);
g_signal_handler_disconnect (self, invalidated_id);
g_signal_handler_disconnect (self, ready_id);
g_main_loop_unref (data.loop);
if (data.connect_error != NULL)
{
g_propagate_error (error, data.connect_error);
return FALSE;
}
if (as_proxy->invalidated != NULL)
goto raise_invalidated;
if (self->priv->ready)
return TRUE;
g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_CANCELLED,
"tp_connection_run_until_ready() cancelled");
return FALSE;
raise_invalidated:
if (error != NULL)
{
g_return_val_if_fail (*error == NULL, FALSE);
*error = g_error_copy (as_proxy->invalidated);
}
return FALSE;
}
/**
* TpConnectionNameListCb:
* @names: (array zero-terminated=1): %NULL-terminated array of @n
* connection bus names, or %NULL on error
* @n: number of names (not including the final %NULL), or 0 on error
* @cms: (array zero-terminated=1): %NULL-terminated array of @n
* connection manager names (e.g. "gabble") in the same order as @names, or
* %NULL on error
* @protocols: (array zero-terminated=1): %NULL-terminated array of
* @n protocol names as defined in the Telepathy spec (e.g. "jabber") in the
* same order as @names, or %NULL on error
* @error: %NULL on success, or an error that occurred
* @user_data: user-supplied data
* @weak_object: user-supplied weakly referenced object
*
* Signature of the callback supplied to tp_list_connection_names().
*
* Since: 0.7.1
*/
typedef struct {
TpConnectionNameListCb callback;
gpointer user_data;
GDestroyNotify destroy;
} _ListContext;
static gboolean
_tp_connection_parse (const gchar *path_or_bus_name,
char delimiter,
gchar **protocol,
gchar **cm_name)
{
const gchar *prefix;
const gchar *cm_name_start;
const gchar *protocol_start;
const gchar *account_start;
gchar *dup_cm_name = NULL;
gchar *dup_protocol = NULL;
g_return_val_if_fail (delimiter == '.' || delimiter == '/', FALSE);
/* If CM respects the spec, object path and bus name should be in the form:
* /org/freedesktop/Telepathy/Connection/cmname/proto/account
* org.freedesktop.Telepathy.Connection.cmname.proto.account
*/
if (delimiter == '.')
prefix = TP_CONN_BUS_NAME_BASE;
else
prefix = TP_CONN_OBJECT_PATH_BASE;
if (!g_str_has_prefix (path_or_bus_name, prefix))
goto OUT;
cm_name_start = path_or_bus_name + strlen (prefix);
protocol_start = strchr (cm_name_start, delimiter);
if (protocol_start == NULL)
goto OUT;
protocol_start++;
account_start = strchr (protocol_start, delimiter);
if (account_start == NULL)
goto OUT;
account_start++;
dup_cm_name = g_strndup (cm_name_start, protocol_start - cm_name_start - 1);
if (!tp_connection_manager_check_valid_name (dup_cm_name, NULL))
{
g_free (dup_cm_name);
dup_cm_name = NULL;
goto OUT;
}
dup_protocol = g_strndup (protocol_start, account_start - protocol_start - 1);
if (!tp_strdiff (dup_protocol, "local_2dxmpp"))
{
/* the CM's telepathy-glib is older than 0.7.x, work around it.
* FIXME: Remove this workaround in 0.9.x */
g_free (dup_protocol);
dup_protocol = g_strdup ("local-xmpp");
}
else
{
/* the real protocol name may have "-" in; bus names may not, but
* they may have "_", so the Telepathy spec specifies replacement.
* Here we need to undo that replacement */
g_strdelimit (dup_protocol, "_", '-');
}
if (!tp_connection_manager_check_valid_protocol_name (dup_protocol, NULL))
{
g_free (dup_protocol);
dup_protocol = NULL;
goto OUT;
}
OUT:
if (dup_protocol == NULL || dup_cm_name == NULL)
{
g_free (dup_protocol);
g_free (dup_cm_name);
return FALSE;
}
if (cm_name != NULL)
*cm_name = dup_cm_name;
else
g_free (dup_cm_name);
if (protocol != NULL)
*protocol = dup_protocol;
else
g_free (dup_protocol);
return TRUE;
}
static void
tp_list_connection_names_helper (TpDBusDaemon *bus_daemon,
const gchar * const *names,
const GError *error,
gpointer user_data,
GObject *user_object)
{
_ListContext *list_context = user_data;
const gchar * const *iter;
/* array of borrowed strings */
GPtrArray *bus_names;
/* array of dup'd strings */
GPtrArray *cms;
/* array of borrowed strings */
GPtrArray *protocols;
if (error != NULL)
{
list_context->callback (NULL, 0, NULL, NULL, error,
list_context->user_data, user_object);
return;
}
bus_names = g_ptr_array_new ();
cms = g_ptr_array_new ();
protocols = g_ptr_array_new ();
for (iter = names; iter != NULL && *iter != NULL; iter++)
{
gchar *proto, *cm_name;
if (_tp_connection_parse (*iter, '.', &proto, &cm_name))
{
/* the casts here are because g_ptr_array contains non-const pointers -
* but in this case I'll only be passing pdata to a callback with const
* arguments, so it's fine */
g_ptr_array_add (bus_names, (gpointer) *iter);
g_ptr_array_add (cms, cm_name);
g_ptr_array_add (protocols, proto);
continue;
}
}
g_ptr_array_add (bus_names, NULL);
g_ptr_array_add (cms, NULL);
g_ptr_array_add (protocols, NULL);
list_context->callback ((const gchar * const *) bus_names->pdata,
bus_names->len - 1, (const gchar * const *) cms->pdata,
(const gchar * const *) protocols->pdata,
NULL, list_context->user_data, user_object);
g_ptr_array_unref (bus_names);
g_strfreev ((char **) g_ptr_array_free (cms, FALSE));
g_strfreev ((char **) g_ptr_array_free (protocols, FALSE));
}
static void
list_context_free (gpointer p)
{
_ListContext *list_context = p;
if (list_context->destroy != NULL)
list_context->destroy (list_context->user_data);
g_slice_free (_ListContext, list_context);
}
/**
* tp_list_connection_names:
* @bus_daemon: proxy for the D-Bus daemon
* @callback: callback to be called when listing the connections succeeds or
* fails; not called if the D-Bus connection fails completely or if the
* @weak_object goes away
* @user_data: user-supplied data for the callback
* @destroy: callback to destroy the user-supplied data, called after
* @callback, but also if the D-Bus connection fails or if the @weak_object
* goes away
* @weak_object: (allow-none): if not %NULL, will be weakly referenced; the callback will
* not be called if the object has vanished
*
* List the bus names of all the connections that currently exist, together
* with the connection manager name and the protocol name for each connection.
* Call the callback when done.
*
* The bus names passed to the callback can be used to construct #TpConnection
* objects for any connections that are of interest.
*
* Since: 0.7.1
*/
void
tp_list_connection_names (TpDBusDaemon *bus_daemon,
TpConnectionNameListCb callback,
gpointer user_data,
GDestroyNotify destroy,
GObject *weak_object)
{
_ListContext *list_context = g_slice_new0 (_ListContext);
g_return_if_fail (TP_IS_DBUS_DAEMON (bus_daemon));
g_return_if_fail (callback != NULL);
list_context->callback = callback;
list_context->user_data = user_data;
tp_dbus_daemon_list_names (bus_daemon, 2000,
tp_list_connection_names_helper, list_context,
list_context_free, weak_object);
}
static gpointer
tp_connection_once (gpointer data G_GNUC_UNUSED)
{
GType type = TP_TYPE_CONNECTION;
tp_proxy_init_known_interfaces ();
tp_proxy_or_subclass_hook_on_interface_add (type,
tp_cli_connection_add_signals);
tp_proxy_subclass_add_error_mapping (type,
TP_ERROR_PREFIX, TP_ERROR, TP_TYPE_ERROR);
return NULL;
}
/**
* tp_connection_init_known_interfaces:
*
* Ensure that the known interfaces for TpConnection 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_CONNECTION.
*
* Since: 0.7.6
*/
void
tp_connection_init_known_interfaces (void)
{
static GOnce once = G_ONCE_INIT;
g_once (&once, tp_connection_once, NULL);
}
typedef struct {
TpConnectionWhenReadyCb callback;
gpointer user_data;
gulong invalidated_id;
gulong ready_id;
} CallWhenReadyContext;
static void
cwr_invalidated (TpConnection *self,
guint domain,
gint code,
gchar *message,
gpointer user_data)
{
CallWhenReadyContext *ctx = user_data;
GError e = { domain, code, message };
DEBUG ("enter");
g_assert (ctx->callback != NULL);
ctx->callback (self, &e, ctx->user_data);
g_signal_handler_disconnect (self, ctx->invalidated_id);
g_signal_handler_disconnect (self, ctx->ready_id);
ctx->callback = NULL; /* poison it to detect errors */
g_slice_free (CallWhenReadyContext, ctx);
}
static void
cwr_ready (TpConnection *self,
GParamSpec *unused G_GNUC_UNUSED,
gpointer user_data)
{
CallWhenReadyContext *ctx = user_data;
DEBUG ("enter");
g_assert (ctx->callback != NULL);
ctx->callback (self, NULL, ctx->user_data);
g_signal_handler_disconnect (self, ctx->invalidated_id);
g_signal_handler_disconnect (self, ctx->ready_id);
ctx->callback = NULL; /* poison it to detect errors */
g_slice_free (CallWhenReadyContext, ctx);
}
/**
* TpConnectionWhenReadyCb:
* @connection: the connection (which may be in the middle of being disposed,
* if error is non-%NULL, error->domain is TP_DBUS_ERRORS and error->code is
* TP_DBUS_ERROR_PROXY_UNREFERENCED)
* @error: %NULL if the connection is ready for use, or the error with which
* it was invalidated if it is now invalid
* @user_data: whatever was passed to tp_connection_call_when_ready()
*
* Signature of a callback passed to tp_connection_call_when_ready(), which
* will be called exactly once, when the connection becomes ready or
* invalid (whichever happens first)
*
* Deprecated: 0.17.6
*/
/**
* tp_connection_call_when_ready: (skip)
* @self: a connection
* @callback: called when the connection becomes ready or invalidated,
* whichever happens first
* @user_data: arbitrary user-supplied data passed to the callback
*
* If @self is ready for use or has been invalidated, call @callback
* immediately, then return. Otherwise, arrange
* for @callback to be called when @self either becomes ready for use
* or becomes invalid.
*
* Note that if the connection is not in state CONNECTED, the callback will
* not be called until the connection either goes to state CONNECTED
* or is invalidated (e.g. by going to state DISCONNECTED or by becoming
* unreferenced). In particular, this method does not call Connect().
* Call tp_cli_connection_call_connect() too, if you want to do that.
*
* Since: 0.7.7
* Deprecated: 0.17.6: Use tp_proxy_prepare_async()
*/
void
tp_connection_call_when_ready (TpConnection *self,
TpConnectionWhenReadyCb callback,
gpointer user_data)
{
TpProxy *as_proxy = (TpProxy *) self;
g_return_if_fail (TP_IS_CONNECTION (self));
g_return_if_fail (callback != NULL);
if (self->priv->ready || as_proxy->invalidated != NULL)
{
DEBUG ("already ready or invalidated");
callback (self, as_proxy->invalidated, user_data);
}
else
{
CallWhenReadyContext *ctx = g_slice_new (CallWhenReadyContext);
DEBUG ("arranging callback later");
ctx->callback = callback;
ctx->user_data = user_data;
ctx->invalidated_id = g_signal_connect (self, "invalidated",
G_CALLBACK (cwr_invalidated), ctx);
ctx->ready_id = g_signal_connect (self, "notify::connection-ready",
G_CALLBACK (cwr_ready), ctx);
}
}
static guint
get_presence_type_availability (TpConnectionPresenceType type)
{
switch (type)
{
case TP_CONNECTION_PRESENCE_TYPE_UNSET:
return 0;
case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
return 1;
case TP_CONNECTION_PRESENCE_TYPE_ERROR:
return 2;
case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
return 3;
case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
return 4;
case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
return 5;
case TP_CONNECTION_PRESENCE_TYPE_AWAY:
return 6;
case TP_CONNECTION_PRESENCE_TYPE_BUSY:
return 7;
case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
return 8;
}
/* This is an unexpected presence type, treat it like UNKNOWN */
return 1;
}
/**
* tp_connection_presence_type_cmp_availability:
* @p1: a #TpConnectionPresenceType
* @p2: a #TpConnectionPresenceType
*
* Compares @p1 and @p2 like strcmp(). @p1 > @p2 means @p1 is more available
* than @p2.
*
* The order used is: available > busy > away > xa > hidden > offline > error >
* unknown > unset
*
* Returns: -1, 0 or 1, if @p1 is <, == or > than @p2.
*
* Since: 0.7.16
*/
gint
tp_connection_presence_type_cmp_availability (TpConnectionPresenceType p1,
TpConnectionPresenceType p2)
{
guint availability1;
guint availability2;
availability1 = get_presence_type_availability (p1);
availability2 = get_presence_type_availability (p2);
if (availability1 < availability2)
return -1;
if (availability1 > availability2)
return +1;
return 0;
}
/**
* tp_connection_parse_object_path:
* @self: a connection
* @protocol: (out) (transfer full): If not NULL, used to return the protocol
* of the connection
* @cm_name: (out) (transfer full): If not NULL, used to return the connection
* manager name of the connection
*
* If the object path of @connection is in the correct form, set
* @protocol and @cm_name, return TRUE. Otherwise leave them unchanged and
* return FALSE.
*
* Returns: TRUE if the object path was correctly parsed, FALSE otherwise.
*
* Since: 0.7.27
* Deprecated: Use tp_connection_get_protocol_name() and
* tp_connection_get_connection_manager_name() instead.
*/
gboolean
tp_connection_parse_object_path (TpConnection *self,
gchar **protocol,
gchar **cm_name)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
if (protocol != NULL)
*protocol = g_strdup (self->priv->proto_name);
if (cm_name != NULL)
*cm_name = g_strdup (self->priv->cm_name);
return TRUE;
}
/* Can return a contact that's not meant to be visible to library users
* because it lacks an identifier */
TpContact *
_tp_connection_lookup_contact (TpConnection *self,
TpHandle handle)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return g_hash_table_lookup (self->priv->contacts, GUINT_TO_POINTER (handle));
}
/* this could be done with proper weak references, but we know that every
* connection will weakly reference all its contacts, so we can just do this
* explicitly in tp_contact_dispose */
void
_tp_connection_remove_contact (TpConnection *self,
TpHandle handle,
TpContact *contact)
{
TpContact *mine;
g_return_if_fail (TP_IS_CONNECTION (self));
g_return_if_fail (TP_IS_CONTACT (contact));
mine = g_hash_table_lookup (self->priv->contacts, GUINT_TO_POINTER (handle));
g_return_if_fail (mine == contact);
g_hash_table_remove (self->priv->contacts, GUINT_TO_POINTER (handle));
}
void
_tp_connection_add_contact (TpConnection *self,
TpHandle handle,
TpContact *contact)
{
g_return_if_fail (TP_IS_CONNECTION (self));
g_return_if_fail (TP_IS_CONTACT (contact));
g_return_if_fail (g_hash_table_lookup (self->priv->contacts,
GUINT_TO_POINTER (handle)) == NULL);
g_hash_table_insert (self->priv->contacts, GUINT_TO_POINTER (handle),
contact);
/* Set TP_CONTACT_FEATURE_CONTACT_BLOCKING if possible */
if (tp_proxy_is_prepared (self, TP_CONNECTION_FEATURE_CONTACT_BLOCKING))
{
_tp_connection_set_contact_blocked (self, contact);
}
}
/**
* tp_connection_is_ready: (skip)
* @self: a connection
*
* Returns the same thing as the #TpConnection:connection-ready property.
*
* Returns: %TRUE if introspection has completed
* Since: 0.7.17
* Deprecated: 0.17.6: use tp_proxy_is_prepared() with
* %TP_CONNECTION_FEATURE_CONNECTED
*/
gboolean
tp_connection_is_ready (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
return self->priv->ready;
}
/**
* tp_connection_get_capabilities:
* @self: a connection
*
*
*
* Returns: (transfer none): the same #TpCapabilities as the
* #TpConnection:capabilities property
* Since: 0.11.3
*/
TpCapabilities *
tp_connection_get_capabilities (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
return self->priv->capabilities;
}
/**
* tp_connection_get_detailed_error:
* @self: a connection
* @details: (out) (allow-none) (element-type utf8 GObject.Value) (transfer none):
* optionally used to return a map from string to #GValue, which must not be
* modified or destroyed by the caller
*
* If the connection has disconnected, return the D-Bus error name with which
* it disconnected (in particular, this is %TP_ERROR_STR_CANCELLED if it was
* disconnected by a user request).
*
* Otherwise, return %NULL, without altering @details.
*
* Returns: (transfer none) (allow-none): a D-Bus error name, or %NULL.
*
* Since: 0.11.4
*/
const gchar *
tp_connection_get_detailed_error (TpConnection *self,
const GHashTable **details)
{
TpProxy *proxy = (TpProxy *) self;
if (proxy->invalidated == NULL)
return NULL;
if (self->priv->connection_error != NULL)
{
g_assert (self->priv->connection_error_details != NULL);
if (details != NULL)
*details = self->priv->connection_error_details;
return self->priv->connection_error;
}
else
{
/* no detailed error, but we *have* been invalidated - guess one based
* on the invalidation reason */
if (details != NULL)
{
if (self->priv->connection_error_details == NULL)
{
self->priv->connection_error_details = tp_asv_new (
"debug-message", G_TYPE_STRING, proxy->invalidated->message,
NULL);
}
*details = self->priv->connection_error_details;
}
if (proxy->invalidated->domain == TP_ERROR)
{
return tp_error_get_dbus_name (proxy->invalidated->code);
}
else if (proxy->invalidated->domain == TP_DBUS_ERRORS)
{
switch (proxy->invalidated->code)
{
case TP_DBUS_ERROR_NAME_OWNER_LOST:
/* the CM probably crashed */
return DBUS_ERROR_NO_REPLY;
break;
case TP_DBUS_ERROR_OBJECT_REMOVED:
case TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR:
case TP_DBUS_ERROR_INCONSISTENT:
/* ... and all other cases up to and including
* TP_DBUS_ERROR_INCONSISTENT don't make sense in this context, so
* just use the generic one for them too */
default:
return TP_ERROR_STR_DISCONNECTED;
}
}
else
{
/* no idea what that means */
return TP_ERROR_STR_DISCONNECTED;
}
}
}
/**
* tp_connection_dup_detailed_error_vardict:
* @self: a connection
* @details: (out) (allow-none) (transfer full):
* optionally used to return a %G_VARIANT_TYPE_VARDICT with details
* of the error
*
* If the connection has disconnected, return the D-Bus error name with which
* it disconnected (in particular, this is %TP_ERROR_STR_CANCELLED if it was
* disconnected by a user request).
*
* Otherwise, return %NULL, without altering @details.
*
* Returns: (transfer full) (allow-none): a D-Bus error name, or %NULL.
*
* Since: 0.19.0
*/
gchar *
tp_connection_dup_detailed_error_vardict (TpConnection *self,
GVariant **details)
{
const GHashTable *asv;
const gchar *error = tp_connection_get_detailed_error (self, &asv);
if (error == NULL)
return NULL;
if (details != NULL)
*details = _tp_asv_to_vardict (asv);
return g_strdup (error);
}
/**
* tp_connection_add_client_interest:
* @self: a connection
* @interested_in: a string identifying an interface or part of an interface
* to which this connection will subscribe
*
* Subscribe to any opt-in change notifications for @interested_in.
*
* For contact information, use #TpContact instead, which will call this
* automatically.
*
* Since: 0.11.3
*/
void
tp_connection_add_client_interest (TpConnection *self,
const gchar *interested_in)
{
tp_connection_add_client_interest_by_id (self,
g_quark_from_string (interested_in));
}
/**
* tp_connection_add_client_interest_by_id: (skip)
* @self: a connection
* @interested_in: a quark identifying an interface or part of an interface
* to which this connection will subscribe
*
* Subscribe to any opt-in change notifications for @interested_in.
*
* Equivalent to, but a little more efficient than, calling
* tp_connection_add_client_interest() for the string value of @interested_in.
*
* Since: 0.11.3
*/
void
tp_connection_add_client_interest_by_id (TpConnection *self,
GQuark interested_in)
{
TpProxy *proxy = (TpProxy *) self;
const gchar *interest = g_quark_to_string (interested_in);
const gchar *strv[2] = { interest, NULL };
g_return_if_fail (TP_IS_CONNECTION (self));
g_return_if_fail (interest != NULL);
if (proxy->invalidated != NULL ||
tp_intset_is_member (self->priv->interests, interested_in))
return;
tp_intset_add (self->priv->interests, interested_in);
/* no-reply flag set, and we ignore any reply */
tp_cli_connection_call_add_client_interest (self, -1,
strv, NULL, NULL, NULL, NULL);
}
/**
* tp_connection_has_immortal_handles:
* @self: a connection
*
* Return %TRUE if this connection is known to not destroy handles
* (#TpHandle) until it disconnects.
*
* On such connections, if you know that a handle maps to a particular
* identifier now, then you can rely on that handle mapping to that
* identifier for the whole lifetime of the connection.
*
* Returns: %TRUE if handles last as long as the connection itself
*/
gboolean
tp_connection_has_immortal_handles (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
return self->priv->has_immortal_handles;
}
/**
* tp_connection_get_self_contact:
* @self: a connection
*
* Return a #TpContact representing the local user on this connection.
*
* The returned object is not necessarily valid after the main loop is
* re-entered; ref it with g_object_ref() if you want to keep it.
*
* Returns: (transfer none): the value of the TpConnection:self-contact
* property, which may be %NULL
*
* Since: 0.13.9
*/
TpContact *
tp_connection_get_self_contact (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return self->priv->self_contact;
}
/**
* tp_connection_bind_connection_status_to_property:
* @self: a #TpConnection
* @target: the target #GObject
* @target_property: the property on @target to bind (must be %G_TYPE_BOOLEAN)
* @invert: %TRUE if you wish to invert the value of @target_property
* (i.e. %FALSE if connected)
*
* Binds the :status of @self to the boolean property of another
* object using a #GBinding such that the @target_property will be set to
* %TRUE when @self is connected (and @invert is %FALSE).
*
* @target_property will be synchronised immediately (%G_BINDING_SYNC_CREATE).
* @invert can be interpreted as analogous to %G_BINDING_INVERT_BOOLEAN.
*
* For instance, this function can be used to bind the GtkWidget:sensitive
* property to only make a widget sensitive when the account is connected.
*
* See g_object_bind_property() for more information.
*
* Returns: (transfer none): the #GBinding instance representing the binding
* between the @self and the @target. The binding is released whenever the
* #GBinding reference count reaches zero.
* Since: 0.13.16
*/
GBinding *
tp_connection_bind_connection_status_to_property (TpConnection *self,
gpointer target,
const char *target_property,
gboolean invert)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return g_object_bind_property_full (self, "status",
target, target_property,
G_BINDING_SYNC_CREATE,
_tp_bind_connection_status_to_boolean,
NULL, GUINT_TO_POINTER (invert), NULL);
}
/**
* tp_connection_get_balance:
* @self: a #TpConnection
* @balance: (out): a pointer to store the account balance (or %NULL)
* @scale: (out): a pointer to store the balance scale (or %NULL)
* @currency: (out) (transfer none): a pointer to store the balance
* currency (or %NULL)
*
* If @self has a valid account balance, returns %TRUE and sets the variables
* pointed to by @balance, @scale and @currency to the appropriate fields
* of the Balance.AccountBalance property.
*
* The monetary value of the balance is expressed as a fixed-point number,
* @balance, with a decimal scale defined by @scale; for instance a @balance
* of 1234 with @scale of 2 represents a value of "12.34" in the currency
* represented by @currency.
*
* Requires %TP_CONNECTION_FEATURE_BALANCE to be prepared.
*
* Returns: %TRUE if the balance is valid (and the values set), %FALSE if the
* balance is invalid.
* Since: 0.15.1
*/
gboolean
tp_connection_get_balance (TpConnection *self,
gint *balance,
guint *scale,
const gchar **currency)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
if (self->priv->balance_currency == NULL)
return FALSE;
if (self->priv->balance == 0 &&
self->priv->balance_scale == G_MAXUINT32 &&
tp_str_empty (self->priv->balance_currency))
return FALSE;
if (balance != NULL)
*balance = self->priv->balance;
if (scale != NULL)
*scale = self->priv->balance_scale;
if (currency != NULL)
*currency = self->priv->balance_currency;
return TRUE;
}
/**
* tp_connection_get_balance_uri:
* @self: a #TpConnection
*
* The value of Balance.ManageCreditURI.
*
* Requires %TP_CONNECTION_FEATURE_BALANCE to be prepared.
*
* Returns: (transfer none): the #TpConnection:balance-uri property.
* Since: 0.15.1
*/
const gchar *
tp_connection_get_balance_uri (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
return self->priv->balance_uri;
}
static void
_tp_connection_void_cb (TpConnection *proxy,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data);
if (error != NULL)
g_simple_async_result_set_from_error (result, error);
g_simple_async_result_complete_in_idle (result);
g_object_unref (G_OBJECT (result));
}
/**
* tp_connection_disconnect_async:
* @self: a #TpConnection
* @callback: a callback to call when the request is satisfied
* @user_data: data to pass to @callback
*
* Disconnect the connection.
*
* This method is intended for use by AccountManager implementations,
* such as Mission Control. To disconnect a connection managed by an
* AccountManager, either use tp_account_request_presence_async()
* or tp_account_set_enabled_async(), depending whether the intention is
* to put the account offline temporarily, or disable it longer-term.
*
* Since: 0.17.5
*/
void
tp_connection_disconnect_async (TpConnection *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
g_return_if_fail (TP_IS_CONNECTION (self));
result = g_simple_async_result_new (G_OBJECT (self), callback,
user_data, tp_connection_disconnect_async);
tp_cli_connection_call_disconnect (self, -1, _tp_connection_void_cb, result,
NULL, NULL);
}
/**
* tp_connection_disconnect_finish:
* @self: a #TpConnection
* @result: a #GAsyncResult
* @error: a #GError to fill
*
* Interpret the result of tp_connection_disconnect_async().
*
* Returns: %TRUE if the call was successful, otherwise %FALSE
*
* Since: 0.17.5
*/
gboolean
tp_connection_disconnect_finish (TpConnection *self,
GAsyncResult *result,
GError **error)
{
_tp_implement_finish_void (self, tp_connection_disconnect_async);
}