/* * presence-mixin.c - Source for TpPresenceMixin * Copyright (C) 2005-2008 Collabora Ltd. * Copyright (C) 2005-2007 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 */ /** * SECTION:presence-mixin * @title: TpPresenceMixin * @short_description: a mixin implementation of the Presence connection * interface * @see_also: #TpSvcConnectionInterfacePresence * * This mixin can be added to a #TpBaseConnection subclass to implement the * SimplePresence and/or Presence interfaces. Implementing both interfaces * (as described below) is recommended. In particular, you must implement the * old-style Presence interface if compatibility with telepathy-glib * versions older than 0.11.13 is required. * * To use the presence mixin, include a #TpPresenceMixinClass somewhere in your * class structure and a #TpPresenceMixin somewhere in your instance structure, * and call tp_presence_mixin_class_init() from your class_init function, * tp_presence_mixin_init() from your init function or constructor, and * tp_presence_mixin_finalize() from your dispose or finalize function. * *
* Implementing SimplePresence * * Since 0.7.13 this mixin supports the entire SimplePresence interface. * You can implement #TpSvcConnectionInterfaceSimplePresence as follows: * * * use the #TpContactsMixin and * TpDBusPropertiesMixin; * * * pass tp_presence_mixin_simple_presence_iface_init() as an * argument to G_IMPLEMENT_INTERFACE(), like so: * * |[ * G_DEFINE_TYPE_WITH_CODE (MyConnection, my_connection, * TP_TYPE_BASE_CONNECTION, * // ... * G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE, * tp_presence_mixin_simple_presence_iface_init); * // ... * ) * ]| * * * * call tp_presence_mixin_simple_presence_init_dbus_properties() in the * #GTypeInfo class_init function; * * * * * call tp_presence_mixin_simple_presence_register_with_contacts_mixin() * in the #GObjectClass constructed function. * * * * *
*
* Implementing old-style Presence * * This mixin also supports a large subset of the deprecated Presence * interface. It does not support protocols where it is possible to set * multiple statuses on yourself at once (all presence statuses will have the * exclusive flag set), or last-activity-time information. * * * To use the presence mixin as the implementation of * #TpSvcConnectionInterfacePresence, use tp_presence_mixin_iface_init() as * the function you pass to G_IMPLEMENT_INTERFACE(), as in the following * example. The presence mixin implements all of the D-Bus methods in the * Presence interface. * * |[ * G_DEFINE_TYPE_WITH_CODE (MyConnection, my_connection, * TP_TYPE_BASE_CONNECTION, * // ... * G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE, * tp_presence_mixin_iface_init); * // ... * ) * ]| * * In telepathy-glib versions older than 0.11.13, every connection * that used the #TpPresenceMixin was required to implement * #TpSvcConnectionInterfacePresence; failing to do so would lead to an * assertion failure. Since 0.11.13, this is no longer required. * *
* * Since: 0.5.13 */ /** * TpPresenceStatusOptionalArgumentSpec: * @name: Name of the argument as passed over D-Bus * @dtype: D-Bus type signature of the argument * * Structure specifying a supported optional argument for a presence status. * * In addition to the fields documented here, there are two gpointer fields * which must currently be %NULL. A meaning may be defined for these in a * future version of telepathy-glib. */ /** * TpPresenceStatusSpec: * @name: String identifier of the presence status * @presence_type: A type value, as specified by #TpConnectionPresenceType * @self: Indicates if this status may be set on yourself * @optional_arguments: An array of #TpPresenceStatusOptionalArgumentSpec * structures representing the optional arguments for this status, terminated * by a NULL name. If there are no optional arguments for a status, this can * be NULL. * * Structure specifying a supported presence status. * * In addition to the fields documented here, there are two gpointer fields * which must currently be %NULL. A meaning may be defined for these in a * future version of telepathy-glib. */ /** * TpPresenceStatus: * @index: Index of the presence status in the provided supported presence * statuses array * @optional_arguments: A GHashTable mapping of string identifiers to GValues * of the optional status arguments, if any. If there are no optional * arguments, this pointer may be NULL. * * Structure representing a presence status. * * In addition to the fields documented here, there are two gpointer fields * which must currently be %NULL. A meaning may be defined for these in a * future version of telepathy-glib. */ /** * TpPresenceMixinStatusAvailableFunc: * @obj: An instance of a #TpBaseConnection subclass implementing the presence * interface with this mixin * @which: An index into the array of #TpPresenceStatusSpec provided to * tp_presence_mixin_class_init() * * Signature of a callback to be used to determine if a given presence * status can be set on the connection. Most users of this mixin do not need to * supply an implementation of this callback: the value of * #TpPresenceStatusSpec.self is enough to determine whether this is a * user-settable presence, so %NULL should be passed to * tp_presence_mixin_class_init() for this callback. * * One place where this callback may be needed is on XMPP: not all server * implementation support the user becoming invisible. So an XMPP * implementation would implement this function, so that—once connected—the * hidden status is only available if the server supports it. Before the * connection is connected, this callback should return %TRUE for every status * that might possibly be supported: this allows the user to at least try to * sign in as invisible. * * Returns: %TRUE if the status can be set on this connection; %FALSE if not. */ /** * TpPresenceMixinGetContactStatusesFunc: * @obj: An object with this mixin. * @contacts: An array of #TpHandle for the contacts to get presence status for * @error: Used to return a Telepathy D-Bus error if %NULL is returned * * Signature of the callback used to get the stored presence status of * contacts. The returned hash table should have contact handles mapped to * their respective presence statuses in #TpPresenceStatus structs. * * The returned hash table will be freed with g_hash_table_unref. The * callback is responsible for ensuring that this does any cleanup that * may be necessary. * * Returns: (transfer full): The contact presence on success, %NULL with * error set on error */ /** * TpPresenceMixinSetOwnStatusFunc: * @obj: An object with this mixin. * @status: The status to set, or NULL for whatever the protocol defines as a * "default" status * @error: Used to return a Telepathy D-Bus error if %FALSE is returned * * Signature of the callback used to commit changes to the user's own presence * status in SetStatuses. It is also used in ClearStatus and RemoveStatus to * reset the user's own status back to the "default" one with a %NULL status * argument. * * The optional_arguments hash table in @status, if not NULL, will have been * filtered so it only contains recognised parameters, so the callback * need not (and cannot) check for unrecognised parameters. However, the * types of the parameters are not currently checked, so the callback is * responsible for doing so. * * The callback is responsible for emitting PresenceUpdate, if appropriate, * by calling tp_presence_mixin_emit_presence_update(). * * Returns: %TRUE if the operation was successful, %FALSE if not. */ /** * TpPresenceMixinGetMaximumStatusMessageLengthFunc: * @obj: An object with this mixin. * * Signature of a callback used to determine the maximum length of status * messages. If this callback is provided and returns non-zero, the * #TpPresenceMixinSetOwnStatusFunc implementation is responsible for * truncating the message to fit this limit, if necessary. * * Returns: the maximum number of UTF-8 characters which may appear in a status * message, or 0 if there is no limit. * Since: 0.14.5 */ /** * TpPresenceMixinClass: * @status_available: The status-available function that was passed to * tp_presence_mixin_class_init() * @get_contact_statuses: The get-contact-statuses function that was passed to * tp_presence_mixin_class_init() * @set_own_status: The set-own-status function that was passed to * tp_presence_mixin_class_init() * @statuses: The presence statuses array that was passed to * tp_presence_mixin_class_init() * @get_maximum_status_message_length: The callback used to discover the * the limit for status messages length, if any. Since: 0.14.5 * * Structure to be included in the class structure of objects that * use this mixin. Initialize it with tp_presence_mixin_class_init(). * * If the protocol imposes a limit on the length of status messages, one should * implement @get_maximum_status_message_length. If this callback is not * implemented, it is assumed that there is no limit. The callback function * should be set after calling tp_presence_mixin_class_init(), like so: * * |[ * TpPresenceMixinClass *mixin_class; * * tp_presence_mixin_class_init ((GObjectClass *) klass, * G_STRUCT_OFFSET (SomeObjectClass, presence_mixin)); * mixin_class = TP_PRESENCE_MIXIN_CLASS (klass); * mixin_class->get_maximum_status_message_length = * some_object_get_maximum_status_message_length; * ]| * * All other fields should be considered read-only. */ /** * TpPresenceMixin: * * Structure to be included in the instance structure of objects that * use this mixin. Initialize it with tp_presence_mixin_init(). * * There are no public fields. */ #include "config.h" #include #include #include #include #include #include #include #include #include #define DEBUG_FLAG TP_DEBUG_PRESENCE #include "debug-internal.h" static GHashTable *construct_simple_presence_hash ( const TpPresenceStatusSpec *supported_statuses, GHashTable *contact_statuses); /* * deep_copy_hashtable * * Make a deep copy of a GHashTable. */ static GHashTable * deep_copy_hashtable (GHashTable *hash_table) { GValue value = {0, }; if (!hash_table) return NULL; g_value_init (&value, TP_HASH_TYPE_STRING_VARIANT_MAP); g_value_take_boxed (&value, hash_table); return g_value_dup_boxed (&value); } /** * tp_presence_status_new: (skip) * @which: Index of the presence status in the provided supported presence * statuses array * @optional_arguments: Optional arguments for the presence statuses. Can be * NULL if there are no optional arguments. The presence status object makes a * copy of the hashtable, so you should free the original. * * Construct a presence status structure. You should free the returned * structure with #tp_presence_status_free. * * Returns: A pointer to the newly allocated presence status structure. */ TpPresenceStatus * tp_presence_status_new (guint which, GHashTable *optional_arguments) { TpPresenceStatus *status = g_slice_new (TpPresenceStatus); status->index = which; status->optional_arguments = deep_copy_hashtable (optional_arguments); return status; } /** * tp_presence_status_free: (skip) * @status: A pointer to the presence status structure to free. * * Deallocate all resources associated with a presence status structure. */ void tp_presence_status_free (TpPresenceStatus *status) { if (!status) return; if (status->optional_arguments) g_hash_table_unref (status->optional_arguments); g_slice_free (TpPresenceStatus, status); } /** * tp_presence_mixin_class_get_offset_quark: (skip) * * * * Returns: the quark used for storing mixin offset on a GObjectClass */ GQuark tp_presence_mixin_class_get_offset_quark () { static GQuark offset_quark = 0; if (!offset_quark) offset_quark = g_quark_from_static_string ("TpPresenceMixinClassOffsetQuark"); return offset_quark; } /** * tp_presence_mixin_get_offset_quark: (skip) * * * * Returns: the quark used for storing mixin offset on a GObject */ GQuark tp_presence_mixin_get_offset_quark () { static GQuark offset_quark = 0; if (!offset_quark) offset_quark = g_quark_from_static_string ("TpPresenceMixinOffsetQuark"); return offset_quark; } /** * tp_presence_mixin_class_init: (skip) * @obj_cls: The class of the implementation that uses this mixin * @offset: The byte offset of the TpPresenceMixinClass within the class * structure * @status_available: A callback to be used to determine if a given presence * status can be set on a particular connection. Should usually be %NULL, to * consider all statuses with #TpPresenceStatusSpec.self set to %TRUE to be * settable. * @get_contact_statuses: A callback to be used get the current presence status * for contacts. This is used in implementations of various D-Bus methods and * hence must be provided. * @set_own_status: A callback to be used to commit changes to the user's own * presence status to the server. This is used in implementations of various * D-Bus methods and hence must be provided. * @statuses: An array of #TpPresenceStatusSpec structures representing all * presence statuses supported by the protocol, terminated by a NULL name. * * Initialize the presence mixin. Should be called from the implementation's * class_init function like so: * * * tp_presence_mixin_class_init ((GObjectClass *) klass, * G_STRUCT_OFFSET (SomeObjectClass, * presence_mixin)); * */ void tp_presence_mixin_class_init (GObjectClass *obj_cls, glong offset, TpPresenceMixinStatusAvailableFunc status_available, TpPresenceMixinGetContactStatusesFunc get_contact_statuses, TpPresenceMixinSetOwnStatusFunc set_own_status, const TpPresenceStatusSpec *statuses) { TpPresenceMixinClass *mixin_cls; guint i; DEBUG ("called."); g_assert (get_contact_statuses != NULL); g_assert (set_own_status != NULL); g_assert (statuses != NULL); g_assert (G_IS_OBJECT_CLASS (obj_cls)); g_type_set_qdata (G_OBJECT_CLASS_TYPE (obj_cls), TP_PRESENCE_MIXIN_CLASS_OFFSET_QUARK, GINT_TO_POINTER (offset)); mixin_cls = TP_PRESENCE_MIXIN_CLASS (obj_cls); mixin_cls->status_available = status_available; mixin_cls->get_contact_statuses = get_contact_statuses; mixin_cls->set_own_status = set_own_status; mixin_cls->statuses = statuses; mixin_cls->get_maximum_status_message_length = NULL; for (i = 0; statuses[i].name != NULL; i++) { if (statuses[i].self) { switch (statuses[i].presence_type) { case TP_CONNECTION_PRESENCE_TYPE_OFFLINE: case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN: case TP_CONNECTION_PRESENCE_TYPE_ERROR: WARNING ("Status \"%s\" of type %u should not be available " "to set on yourself", statuses[i].name, statuses[i].presence_type); break; default: break; } } } } /** * tp_presence_mixin_init: (skip) * @obj: An instance of the implementation that uses this mixin * @offset: The byte offset of the TpPresenceMixin within the object structure * * Initialize the presence mixin. Should be called from the implementation's * instance init function like so: * * * tp_presence_mixin_init ((GObject *) self, * G_STRUCT_OFFSET (SomeObject, presence_mixin)); * */ void tp_presence_mixin_init (GObject *obj, glong offset) { DEBUG ("called."); g_assert (G_IS_OBJECT (obj)); g_type_set_qdata (G_OBJECT_TYPE (obj), TP_PRESENCE_MIXIN_OFFSET_QUARK, GINT_TO_POINTER (offset)); } /** * tp_presence_mixin_finalize: (skip) * @obj: An object with this mixin. * * Free resources held by the presence mixin. */ void tp_presence_mixin_finalize (GObject *obj) { DEBUG ("%p", obj); /* free any data held directly by the object here */ } static void construct_presence_hash_foreach ( GHashTable *presence_hash, const TpPresenceStatusSpec *supported_statuses, TpHandle handle, TpPresenceStatus *status) { GHashTable *parameters; GHashTable *contact_status; GValueArray *vals; contact_status = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_hash_table_unref); parameters = deep_copy_hashtable (status->optional_arguments); if (!parameters) parameters = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); g_hash_table_insert (contact_status, (gpointer) supported_statuses[status->index].name, parameters); vals = g_value_array_new (2); /* last-activity sucks and will probably be removed soon */ g_value_array_append (vals, NULL); g_value_init (g_value_array_get_nth (vals, 0), G_TYPE_UINT); g_value_set_uint (g_value_array_get_nth (vals, 0), 0); g_value_array_append (vals, NULL); g_value_init (g_value_array_get_nth (vals, 1), TP_HASH_TYPE_MULTIPLE_STATUS_MAP); g_value_take_boxed (g_value_array_get_nth (vals, 1), contact_status); g_hash_table_insert (presence_hash, GUINT_TO_POINTER (handle), vals); } static GHashTable * construct_presence_hash (const TpPresenceStatusSpec *supported_statuses, GHashTable *contact_statuses) { GHashTable *presence_hash = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_value_array_free); GHashTableIter iter; gpointer key, value; DEBUG ("called."); g_hash_table_iter_init (&iter, contact_statuses); while (g_hash_table_iter_next (&iter, &key, &value)) construct_presence_hash_foreach (presence_hash, supported_statuses, GPOINTER_TO_UINT (key), value); return presence_hash; } /** * tp_presence_mixin_emit_presence_update: (skip) * @obj: A connection object with this mixin * @contact_presences: A mapping of contact handles to #TpPresenceStatus * structures with the presence data to emit * * Emit the PresenceUpdate signal for multiple contacts. For emitting * PresenceUpdate for a single contact, there is a convenience wrapper called * #tp_presence_mixin_emit_one_presence_update. */ void tp_presence_mixin_emit_presence_update (GObject *obj, GHashTable *contact_statuses) { TpPresenceMixinClass *mixin_cls = TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); GHashTable *presence_hash; DEBUG ("called."); if (g_type_interface_peek (G_OBJECT_GET_CLASS (obj), TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE) != NULL) { presence_hash = construct_presence_hash (mixin_cls->statuses, contact_statuses); tp_svc_connection_interface_presence_emit_presence_update (obj, presence_hash); g_hash_table_unref (presence_hash); } if (g_type_interface_peek (G_OBJECT_GET_CLASS (obj), TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE) != NULL) { presence_hash = construct_simple_presence_hash (mixin_cls->statuses, contact_statuses); tp_svc_connection_interface_simple_presence_emit_presences_changed (obj, presence_hash); g_hash_table_unref (presence_hash); } } /** * tp_presence_mixin_emit_one_presence_update: (skip) * @obj: A connection object with this mixin * @handle: The handle of the contact to emit the signal for * @status: The new status to emit * * Emit the PresenceUpdate signal for a single contact. This method is just a * convenience wrapper around #tp_presence_mixin_emit_presence_update. */ void tp_presence_mixin_emit_one_presence_update (GObject *obj, TpHandle handle, const TpPresenceStatus *status) { GHashTable *contact_statuses; DEBUG ("called."); contact_statuses = g_hash_table_new (NULL, NULL); g_hash_table_insert (contact_statuses, GUINT_TO_POINTER (handle), (gpointer) status); tp_presence_mixin_emit_presence_update (obj, contact_statuses); g_hash_table_unref (contact_statuses); } /* * tp_presence_mixin_add_status: * * Implements D-Bus method AddStatus * on interface org.freedesktop.Telepathy.Connection.Interface.Presence */ static void tp_presence_mixin_add_status (TpSvcConnectionInterfacePresence *iface, const gchar *status, GHashTable *parms, DBusGMethodInvocation *context) { TpBaseConnection *conn = TP_BASE_CONNECTION (iface); GError error = { TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "Only one status is possible at a time with this protocol!" }; DEBUG ("called."); TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context); dbus_g_method_return_error (context, &error); } /* * tp_presence_mixin_clear_status: * * Implements D-Bus method ClearStatus * on interface org.freedesktop.Telepathy.Connection.Interface.Presence */ static void tp_presence_mixin_clear_status (TpSvcConnectionInterfacePresence *iface, DBusGMethodInvocation *context) { GObject *obj = (GObject *) iface; TpBaseConnection *conn = TP_BASE_CONNECTION (iface); TpPresenceMixinClass *mixin_cls = TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); GError *error = NULL; DEBUG ("called."); TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context); if (!mixin_cls->set_own_status (obj, NULL, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); return; } tp_svc_connection_interface_presence_return_from_clear_status (context); } /* * tp_presence_mixin_get_presence: * * Implements D-Bus method GetPresence * on interface org.freedesktop.Telepathy.Connection.Interface.Presence */ static void tp_presence_mixin_get_presence (TpSvcConnectionInterfacePresence *iface, const GArray *contacts, DBusGMethodInvocation *context) { GObject *obj = (GObject *) iface; TpBaseConnection *conn = TP_BASE_CONNECTION (obj); TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); TpPresenceMixinClass *mixin_cls = TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); GHashTable *contact_statuses; GHashTable *presence_hash; GError *error = NULL; DEBUG ("called."); TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context); if (contacts->len == 0) { presence_hash = g_hash_table_new (g_direct_hash, g_direct_equal); tp_svc_connection_interface_presence_return_from_get_presence (context, presence_hash); g_hash_table_unref (presence_hash); return; } if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); return; } contact_statuses = mixin_cls->get_contact_statuses (obj, contacts, &error); if (!contact_statuses) { dbus_g_method_return_error (context, error); g_error_free (error); return; } presence_hash = construct_presence_hash (mixin_cls->statuses, contact_statuses); tp_svc_connection_interface_presence_return_from_get_presence (context, presence_hash); g_hash_table_unref (presence_hash); g_hash_table_unref (contact_statuses); } static GHashTable * get_statuses_arguments (const TpPresenceStatusOptionalArgumentSpec *specs) { GHashTable *arguments = g_hash_table_new (g_str_hash, g_str_equal); int i; for (i=0; specs != NULL && specs[i].name != NULL; i++) g_hash_table_insert (arguments, (gchar *) specs[i].name, (gchar *) specs[i].dtype); return arguments; } static gboolean check_status_available (GObject *object, TpPresenceMixinClass *mixin_cls, guint i, GError **error, gboolean for_self) { if (for_self) { if (!mixin_cls->statuses[i].self) { g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "cannot set status '%s' on yourself", mixin_cls->statuses[i].name); return FALSE; } /* never allow OFFLINE, UNKNOWN or ERROR - if the CM says they're * OK to set on yourself, then it's wrong */ switch (mixin_cls->statuses[i].presence_type) { case TP_CONNECTION_PRESENCE_TYPE_OFFLINE: case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN: case TP_CONNECTION_PRESENCE_TYPE_ERROR: g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "cannot set offline/unknown/error status '%s' on yourself", mixin_cls->statuses[i].name); return FALSE; default: break; } } if (mixin_cls->status_available && !mixin_cls->status_available (object, i)) { DEBUG ("requested status %s is not available", mixin_cls->statuses[i].name); g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, "requested status '%s' is not available on this connection", mixin_cls->statuses[i].name); return FALSE; } return TRUE; } /* * tp_presence_mixin_get_statuses: * * Implements D-Bus method GetStatuses * on interface org.freedesktop.Telepathy.Connection.Interface.Presence */ static void tp_presence_mixin_get_statuses (TpSvcConnectionInterfacePresence *iface, DBusGMethodInvocation *context) { TpBaseConnection *conn = TP_BASE_CONNECTION (iface); GObject *obj = (GObject *) conn; TpPresenceMixinClass *mixin_cls = TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); GHashTable *ret; GValueArray *status; int i; DEBUG ("called."); TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context); ret = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_value_array_free); for (i=0; mixin_cls->statuses[i].name != NULL; i++) { /* the spec says we include statuses here even if they're not available * to set on yourself */ if (!check_status_available (obj, mixin_cls, i, NULL, FALSE)) continue; status = g_value_array_new (5); g_value_array_append (status, NULL); g_value_init (g_value_array_get_nth (status, 0), G_TYPE_UINT); g_value_set_uint (g_value_array_get_nth (status, 0), mixin_cls->statuses[i].presence_type); g_value_array_append (status, NULL); g_value_init (g_value_array_get_nth (status, 1), G_TYPE_BOOLEAN); g_value_set_boolean (g_value_array_get_nth (status, 1), mixin_cls->statuses[i].self); /* everything is exclusive */ g_value_array_append (status, NULL); g_value_init (g_value_array_get_nth (status, 2), G_TYPE_BOOLEAN); g_value_set_boolean (g_value_array_get_nth (status, 2), TRUE); g_value_array_append (status, NULL); g_value_init (g_value_array_get_nth (status, 3), DBUS_TYPE_G_STRING_STRING_HASHTABLE); g_value_take_boxed (g_value_array_get_nth (status, 3), get_statuses_arguments (mixin_cls->statuses[i].optional_arguments)); g_hash_table_insert (ret, (gchar *) mixin_cls->statuses[i].name, status); } tp_svc_connection_interface_presence_return_from_get_statuses (context, ret); g_hash_table_unref (ret); } /* * tp_presence_mixin_set_last_activity_time: * * Implements D-Bus method SetLastActivityTime * on interface org.freedesktop.Telepathy.Connection.Interface.Presence */ static void tp_presence_mixin_set_last_activity_time (TpSvcConnectionInterfacePresence *iface, guint timestamp, DBusGMethodInvocation *context) { TpBaseConnection *conn = TP_BASE_CONNECTION (iface); TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context); tp_svc_connection_interface_presence_return_from_set_last_activity_time ( context); } /* * tp_presence_mixin_remove_status: * * Implements D-Bus method RemoveStatus * on interface org.freedesktop.Telepathy.Connection.Interface.Presence */ static void tp_presence_mixin_remove_status (TpSvcConnectionInterfacePresence *iface, const gchar *status, DBusGMethodInvocation *context) { GObject *obj = (GObject *) iface; TpBaseConnection *conn = TP_BASE_CONNECTION (iface); TpPresenceMixinClass *mixin_cls = TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); GArray *self_contacts; GError *error = NULL; GHashTable *self_contact_statuses; TpPresenceStatus *self_status; TpHandle self_handle; DEBUG ("called."); TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context); self_contacts = g_array_sized_new (TRUE, TRUE, sizeof (TpHandle), 1); self_handle = tp_base_connection_get_self_handle (conn); g_array_append_val (self_contacts, self_handle); self_contact_statuses = mixin_cls->get_contact_statuses (obj, self_contacts, &error); if (!self_contact_statuses) { dbus_g_method_return_error (context, error); g_error_free (error); g_array_unref (self_contacts); return; } self_status = (TpPresenceStatus *) g_hash_table_lookup (self_contact_statuses, GUINT_TO_POINTER (tp_base_connection_get_self_handle (conn))); if (!self_status) { DEBUG ("Got no self status, assuming we already have default status"); g_array_unref (self_contacts); g_hash_table_unref (self_contact_statuses); tp_svc_connection_interface_presence_return_from_remove_status (context); return; } if (!tp_strdiff (status, mixin_cls->statuses[self_status->index].name)) { if (mixin_cls->set_own_status (obj, NULL, &error)) { tp_svc_connection_interface_presence_return_from_remove_status (context); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } else { GError nonexistent = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Attempting to remove non-existent presence." }; dbus_g_method_return_error (context, &nonexistent); } g_array_unref (self_contacts); g_hash_table_unref (self_contact_statuses); } /* * tp_presence_mixin_request_presence: * * Implements D-Bus method RequestPresence * on interface org.freedesktop.Telepathy.Connection.Interface.Presence */ static void tp_presence_mixin_request_presence (TpSvcConnectionInterfacePresence *iface, const GArray *contacts, DBusGMethodInvocation *context) { GObject *obj = (GObject *) iface; TpPresenceMixinClass *mixin_cls = TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); TpBaseConnection *conn = TP_BASE_CONNECTION (iface); TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); GHashTable *contact_statuses; GError *error = NULL; DEBUG ("called."); TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context); if (contacts->len == 0) { tp_svc_connection_interface_presence_return_from_request_presence (context); return; } if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); return; } contact_statuses = mixin_cls->get_contact_statuses (obj, contacts, &error); if (!contact_statuses) { dbus_g_method_return_error (context, error); g_error_free (error); return; } tp_presence_mixin_emit_presence_update (obj, contact_statuses); tp_svc_connection_interface_presence_return_from_request_presence (context); g_hash_table_unref (contact_statuses); } static int check_for_status (GObject *object, const gchar *status, GError **error) { TpPresenceMixinClass *mixin_cls = TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (object)); int i; for (i = 0; mixin_cls->statuses[i].name != NULL; i++) { if (!tp_strdiff (mixin_cls->statuses[i].name, status)) break; } if (mixin_cls->statuses[i].name != NULL) { DEBUG ("Found status \"%s\", checking if it's available...", (const gchar *) status); if (!check_status_available (object, mixin_cls, i, error, TRUE)) return -1; } else { DEBUG ("got unknown status identifier %s", status); g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "unknown status identifier: %s", status); return -1; } return i; } static gboolean set_status ( GObject *obj, const gchar *status_name, GHashTable *provided_arguments, GError **error) { TpPresenceMixinClass *mixin_cls = TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); TpPresenceStatus status_to_set = { 0, }; int status; GHashTable *optional_arguments = NULL; gboolean ret = TRUE; DEBUG ("called."); /* This function will actually only be invoked once for one SetStatus request, * since we check that the hash table has size 1 in * tp_presence_mixin_set_status(). Therefore there are no problems with * sharing the foreach data like this. */ status = check_for_status (obj, status_name, error); if (status == -1) return FALSE; DEBUG ("The status is available."); if (provided_arguments != NULL) { int j; const TpPresenceStatusOptionalArgumentSpec *specs = mixin_cls->statuses[status].optional_arguments; for (j=0; specs != NULL && specs[j].name != NULL; j++) { GValue *provided_value = g_hash_table_lookup (provided_arguments, specs[j].name); GValue *new_value; if (!provided_value) continue; new_value = tp_g_value_slice_dup (provided_value); if (!optional_arguments) optional_arguments = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free); if (DEBUGGING) { gchar *value_contents = g_strdup_value_contents (new_value); DEBUG ("Got optional argument (\"%s\", %s)", specs[j].name, value_contents); g_free (value_contents); } g_hash_table_insert (optional_arguments, (gpointer) specs[j].name, new_value); } } status_to_set.index = status; status_to_set.optional_arguments = optional_arguments; DEBUG ("About to try setting status \"%s\"", mixin_cls->statuses[status].name); ret = mixin_cls->set_own_status (obj, &status_to_set, error); if (optional_arguments) g_hash_table_unref (optional_arguments); return ret; } /* * tp_presence_mixin_set_status: * * Implements D-Bus method SetStatus * on interface org.freedesktop.Telepathy.Connection.Interface.Presence */ static void tp_presence_mixin_set_status (TpSvcConnectionInterfacePresence *iface, GHashTable *statuses, DBusGMethodInvocation *context) { GObject *obj = (GObject *) iface; TpBaseConnection *conn = TP_BASE_CONNECTION (iface); GHashTableIter iter; gpointer key, value; GError *error = NULL; DEBUG ("called."); TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context); g_hash_table_iter_init (&iter, statuses); if (!g_hash_table_iter_next (&iter, &key, &value) || g_hash_table_iter_next (&iter, NULL, NULL)) { GError invalid = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Only one status may be set at a time in this protocol" }; DEBUG ("got more than one status"); dbus_g_method_return_error (context, &invalid); return; } if (set_status (obj, key, value, &error)) { tp_svc_connection_interface_presence_return_from_set_status (context); } else { DEBUG ("failed: %s", error->message); dbus_g_method_return_error (context, error); g_error_free (error); } } /** * tp_presence_mixin_iface_init: (skip) * @g_iface: A pointer to the #TpSvcConnectionInterfacePresenceClass in an * object class * @iface_data: Ignored * * Fill in the vtable entries needed to implement the presence interface using * this mixin. This function should usually be called via G_IMPLEMENT_INTERFACE. */ void tp_presence_mixin_iface_init (gpointer g_iface, gpointer iface_data) { TpSvcConnectionInterfacePresenceClass *klass = g_iface; #define IMPLEMENT(x) tp_svc_connection_interface_presence_implement_##x (klass,\ tp_presence_mixin_##x) IMPLEMENT(add_status); IMPLEMENT(clear_status); IMPLEMENT(get_presence); IMPLEMENT(get_statuses); IMPLEMENT(remove_status); IMPLEMENT(request_presence); IMPLEMENT(set_last_activity_time); IMPLEMENT(set_status); #undef IMPLEMENT } enum { MIXIN_DP_SIMPLE_STATUSES, MIXIN_DP_SIMPLE_MAX_STATUS_MESSAGE_LENGTH, NUM_MIXIN_SIMPLE_DBUS_PROPERTIES }; static TpDBusPropertiesMixinPropImpl known_simple_presence_props[] = { { "Statuses", NULL, NULL }, { "MaximumStatusMessageLength", NULL, NULL }, { NULL } }; static void tp_presence_mixin_get_simple_presence_dbus_property (GObject *object, GQuark interface, GQuark name, GValue *value, gpointer unused G_GNUC_UNUSED) { TpPresenceMixinClass *mixin_cls = TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (object)); static GQuark q[NUM_MIXIN_SIMPLE_DBUS_PROPERTIES] = { 0, }; DEBUG ("called."); if (G_UNLIKELY (q[0] == 0)) { q[MIXIN_DP_SIMPLE_STATUSES] = g_quark_from_static_string ("Statuses"); q[MIXIN_DP_SIMPLE_MAX_STATUS_MESSAGE_LENGTH] = g_quark_from_static_string ("MaximumStatusMessageLength"); } g_return_if_fail (object != NULL); if (name == q[MIXIN_DP_SIMPLE_STATUSES]) { GHashTable *ret; GValueArray *status; int i; g_return_if_fail (G_VALUE_HOLDS_BOXED (value)); ret = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_value_array_free); for (i=0; mixin_cls->statuses[i].name != NULL; i++) { const TpPresenceStatusOptionalArgumentSpec *specs; int j; gboolean message = FALSE; /* we include statuses here even if they're not available * to set on yourself */ if (!check_status_available (object, mixin_cls, i, NULL, FALSE)) continue; specs = mixin_cls->statuses[i].optional_arguments; for (j = 0; specs != NULL && specs[j].name != NULL; j++) { if (!tp_strdiff (specs[j].name, "message")) { message = TRUE; break; } } status = g_value_array_new (3); g_value_array_append (status, NULL); g_value_init (g_value_array_get_nth (status, 0), G_TYPE_UINT); g_value_set_uint (g_value_array_get_nth (status, 0), mixin_cls->statuses[i].presence_type); g_value_array_append (status, NULL); g_value_init (g_value_array_get_nth (status, 1), G_TYPE_BOOLEAN); g_value_set_boolean (g_value_array_get_nth (status, 1), mixin_cls->statuses[i].self); g_value_array_append (status, NULL); g_value_init (g_value_array_get_nth (status, 2), G_TYPE_BOOLEAN); g_value_set_boolean (g_value_array_get_nth (status, 2), message); g_hash_table_insert (ret, (gchar *) mixin_cls->statuses[i].name, status); } g_value_take_boxed (value, ret); } else if (name == q[MIXIN_DP_SIMPLE_MAX_STATUS_MESSAGE_LENGTH]) { guint max_status_message_length = 0; g_assert (G_VALUE_HOLDS (value, G_TYPE_UINT)); if (mixin_cls->get_maximum_status_message_length != NULL) max_status_message_length = mixin_cls->get_maximum_status_message_length (object); g_value_set_uint (value, max_status_message_length); } else { g_return_if_reached (); } } /** * tp_presence_mixin_simple_presence_init_dbus_properties: (skip) * @cls: The class of an object with this mixin * * Set up #TpDBusPropertiesMixinClass to use this mixin's implementation of * the SimplePresence interface's properties. * * This automatically sets up a list of the supported properties for the * SimplePresence interface. * * Since: 0.7.13 */ void tp_presence_mixin_simple_presence_init_dbus_properties (GObjectClass *cls) { tp_dbus_properties_mixin_implement_interface (cls, TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE, tp_presence_mixin_get_simple_presence_dbus_property, NULL, known_simple_presence_props); } /* * tp_presence_mixin_simple_presence_set_presence: * * Implements D-Bus method SetPresence * on interface org.freedesktop.Telepathy.Connection.Interface.SimplePresence */ static void tp_presence_mixin_simple_presence_set_presence ( TpSvcConnectionInterfaceSimplePresence *iface, const gchar *status, const gchar *message, DBusGMethodInvocation *context) { GObject *obj = (GObject *) iface; TpPresenceMixinClass *mixin_cls = TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); TpPresenceStatus status_to_set = { 0, }; int s; GError *error = NULL; GHashTable *optional_arguments = NULL; DEBUG ("called."); s = check_for_status (obj, status, &error); if (s == -1) goto out; status_to_set.index = s; if (*message != '\0') { optional_arguments = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free); g_hash_table_insert (optional_arguments, "message", tp_g_value_slice_new_string (message)); status_to_set.optional_arguments = optional_arguments; } mixin_cls->set_own_status (obj, &status_to_set, &error); out: if (error == NULL) { tp_svc_connection_interface_simple_presence_return_from_set_presence ( context); } else { dbus_g_method_return_error (context, error); g_error_free (error); } if (optional_arguments != NULL) g_hash_table_unref (optional_arguments); } static GValueArray * construct_simple_presence_value_array (TpPresenceStatus *status, const TpPresenceStatusSpec *supported_statuses) { TpConnectionPresenceType status_type; const gchar *status_name; const gchar *message = NULL; GValueArray *presence; status_name = supported_statuses[status->index].name; status_type = supported_statuses[status->index].presence_type; if (status->optional_arguments != NULL) { GValue *val; val = g_hash_table_lookup (status->optional_arguments, "message"); if (val != NULL) message = g_value_get_string (val); } if (message == NULL) message = ""; presence = g_value_array_new (3); g_value_array_append (presence, NULL); g_value_init (g_value_array_get_nth (presence, 0), G_TYPE_UINT); g_value_set_uint (g_value_array_get_nth (presence, 0), status_type); g_value_array_append (presence, NULL); g_value_init (g_value_array_get_nth (presence, 1), G_TYPE_STRING); g_value_set_string (g_value_array_get_nth (presence, 1), status_name); g_value_array_append (presence, NULL); g_value_init (g_value_array_get_nth (presence, 2), G_TYPE_STRING); g_value_set_string (g_value_array_get_nth (presence, 2), message); return presence; } static void construct_simple_presence_hash_foreach ( GHashTable *presence_hash, const TpPresenceStatusSpec *supported_statuses, TpHandle handle, TpPresenceStatus *status) { GValueArray *presence; presence = construct_simple_presence_value_array (status, supported_statuses); g_hash_table_insert (presence_hash, GUINT_TO_POINTER (handle), presence); } static GHashTable * construct_simple_presence_hash (const TpPresenceStatusSpec *supported_statuses, GHashTable *contact_statuses) { GHashTable *presence_hash = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_value_array_free); GHashTableIter iter; gpointer key, value; DEBUG ("called."); g_hash_table_iter_init (&iter, contact_statuses); while (g_hash_table_iter_next (&iter, &key, &value)) construct_simple_presence_hash_foreach (presence_hash, supported_statuses, GPOINTER_TO_UINT (key), value); return presence_hash; } /* * tp_presence_mixin_get_simple_presence: * * Implements D-Bus method GetPresence * on interface org.freedesktop.Telepathy.Connection.Interface.SimplePresence */ static void tp_presence_mixin_simple_presence_get_presences ( TpSvcConnectionInterfaceSimplePresence *iface, const GArray *contacts, DBusGMethodInvocation *context) { GObject *obj = (GObject *) iface; TpBaseConnection *conn = TP_BASE_CONNECTION (obj); TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); TpPresenceMixinClass *mixin_cls = TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); GHashTable *contact_statuses; GHashTable *presence_hash; GError *error = NULL; DEBUG ("called."); TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context); if (contacts->len == 0) { presence_hash = g_hash_table_new (g_direct_hash, g_direct_equal); tp_svc_connection_interface_simple_presence_return_from_get_presences ( context, presence_hash); g_hash_table_unref (presence_hash); return; } if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); return; } contact_statuses = mixin_cls->get_contact_statuses (obj, contacts, &error); if (!contact_statuses) { dbus_g_method_return_error (context, error); g_error_free (error); return; } presence_hash = construct_simple_presence_hash (mixin_cls->statuses, contact_statuses); tp_svc_connection_interface_simple_presence_return_from_get_presences ( context, presence_hash); g_hash_table_unref (presence_hash); g_hash_table_unref (contact_statuses); } /** * tp_presence_mixin_simple_presence_iface_init: (skip) * @g_iface: A pointer to the #TpSvcConnectionInterfaceSimplePresenceClass in * an object class * @iface_data: Ignored * * Fill in the vtable entries needed to implement the simple presence interface * using this mixin. This function should usually be called via * G_IMPLEMENT_INTERFACE. * * Since: 0.7.13 */ void tp_presence_mixin_simple_presence_iface_init (gpointer g_iface, gpointer iface_data) { TpSvcConnectionInterfaceSimplePresenceClass *klass = g_iface; #define IMPLEMENT(x) tp_svc_connection_interface_simple_presence_implement_##x\ (klass, tp_presence_mixin_simple_presence_##x) IMPLEMENT(set_presence); IMPLEMENT(get_presences); #undef IMPLEMENT } static void tp_presence_mixin_simple_presence_fill_contact_attributes (GObject *obj, const GArray *contacts, GHashTable *attributes_hash) { TpPresenceMixinClass *mixin_cls = TP_PRESENCE_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); GHashTable *contact_statuses; GError *error = NULL; contact_statuses = mixin_cls->get_contact_statuses (obj, contacts, &error); if (contact_statuses == NULL) { DEBUG ("get_contact_statuses failed: %s", error->message); g_error_free (error); } else { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, contact_statuses); while (g_hash_table_iter_next (&iter, &key, &value)) { TpHandle handle = GPOINTER_TO_UINT (key); TpPresenceStatus *status = value; GValueArray *presence = construct_simple_presence_value_array ( status, mixin_cls->statuses); tp_contacts_mixin_set_contact_attribute (attributes_hash, handle, TP_TOKEN_CONNECTION_INTERFACE_SIMPLE_PRESENCE_PRESENCE, tp_g_value_slice_new_take_boxed (G_TYPE_VALUE_ARRAY, presence)); } g_hash_table_unref (contact_statuses); } } /** * tp_presence_mixin_simple_presence_register_with_contacts_mixin: (skip) * @obj: An instance that of the implementation that uses both the Contacts * mixin and this mixin * * Register the SimplePresence interface with the Contacts interface to make it * inspectable. The Contacts mixin should be initialized before this function * is called */ void tp_presence_mixin_simple_presence_register_with_contacts_mixin (GObject *obj) { tp_contacts_mixin_add_contact_attributes_iface (obj, TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE, tp_presence_mixin_simple_presence_fill_contact_attributes); }