/* * proxy.c - Base class for Telepathy client proxies * * Copyright (C) 2007-2008 Collabora Ltd. * Copyright (C) 2007-2008 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 "telepathy-glib/proxy-subclass.h" #include "telepathy-glib/proxy-internal.h" #include #include #include #include #include "dbus-internal.h" #define DEBUG_FLAG TP_DEBUG_PROXY #include "debug-internal.h" #include "_gen/signals-marshal.h" #include "_gen/tp-cli-generic-body.h" #if 0 #define MORE_DEBUG DEBUG #else #define MORE_DEBUG(...) G_STMT_START {} G_STMT_END #endif /** * TP_DBUS_ERRORS: * * #GError domain representing D-Bus errors not directly related to * Telepathy, for use by #TpProxy. The @code in a #GError with this * domain must be a member of #TpDBusError. * * This macro expands to a function call returning a #GQuark. * * Since: 0.7.1 */ GQuark tp_dbus_errors_quark (void) { static GQuark q = 0; if (q == 0) q = g_quark_from_static_string ("tp_dbus_errors_quark"); return q; } /** * TpDBusError: * @TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR: Raised if the error raised by * a remote D-Bus object is not recognised * @TP_DBUS_ERROR_PROXY_UNREFERENCED: Emitted in #TpProxy:invalidated * when the #TpProxy has lost its last reference * @TP_DBUS_ERROR_NO_INTERFACE: Raised by #TpProxy methods if the remote * object does not appear to have the required interface * @TP_DBUS_ERROR_NAME_OWNER_LOST: Emitted in #TpProxy:invalidated if the * remote process loses ownership of its bus name, and raised by * any #TpProxy methods that have not had a reply at that time or are called * after the proxy becomes invalid in this way (usually meaning it crashed) * @TP_DBUS_ERROR_INVALID_BUS_NAME: Raised if a D-Bus bus name given is not * valid, or is of an unacceptable type (e.g. well-known vs. unique) * @TP_DBUS_ERROR_INVALID_INTERFACE_NAME: Raised if a D-Bus interface or * error name given is not valid * @TP_DBUS_ERROR_INVALID_OBJECT_PATH: Raised if a D-Bus object path * given is not valid * @TP_DBUS_ERROR_INVALID_MEMBER_NAME: Raised if a D-Bus method or signal * name given is not valid * @TP_DBUS_ERROR_OBJECT_REMOVED: A generic error which can be used with * #TpProxy:invalidated to indicate an application-specific indication * that the remote object no longer exists, if no more specific error * is available. * @TP_DBUS_ERROR_CANCELLED: Raised from calls that re-enter the main * loop (*_run_*) if they are cancelled * @TP_DBUS_ERROR_INCONSISTENT: Raised if information received from a remote * object is inconsistent or otherwise obviously wrong (added in 0.7.17) * @NUM_TP_DBUS_ERRORS: 1 more than the highest valid #TpDBusError at the * time of compilation * * #GError codes for use with the %TP_DBUS_ERRORS domain. * * Since: 0.7.1 */ /** * SECTION:proxy * @title: TpProxy * @short_description: base class for Telepathy client proxy objects * @see_also: #TpChannel, #TpConnection, #TpConnectionManager * * #TpProxy is a base class for Telepathy client-side proxies, which represent * an object accessed via D-Bus and provide access to its methods and signals. * * Since: 0.7.1 */ /** * SECTION:proxy-dbus-core * @title: TpProxy D-Bus core methods * @short_description: The D-Bus Introspectable, Peer and Properties interfaces * @see_also: #TpProxy * * All D-Bus objects support the Peer interface, and many support the * Introspectable and Properties interfaces. * * Since: 0.7.2 */ /** * SECTION:proxy-tp-properties * @title: TpProxy Telepathy Properties * @short_description: The Telepathy Properties interface * @see_also: #TpProxy * * As well as #TpProxy, proxy.h includes auto-generated client wrappers for the * Telepathy Properties interface, which can be implemented by any type of * object. * * The Telepathy Properties interface should not be confused with the D-Bus * core Properties interface. * * Since: 0.7.1 */ /** * SECTION:proxy-subclass * @title: TpProxy subclasses and mixins * @short_description: Providing extra functionality for a #TpProxy or * subclass, or subclassing it * @see_also: #TpProxy * * The implementations of #TpProxy subclasses and "mixin" functions need * access to the underlying dbus-glib objects used to implement the * #TpProxy API. * * Mixin functions to implement particular D-Bus interfaces should usually * be auto-generated, by copying tools/glib-client-gen.py from telepathy-glib. * * Since: 0.7.1 */ /** * TpProxy: * @parent: parent object * * Structure representing a Telepathy client-side proxy. * * Since: 0.7.1 */ /** * TpProxyInterfaceAddedCb: * @self: the proxy * @quark: a quark whose string value is the interface being added * @proxy: the #DBusGProxy for the added interface * @unused: unused * * The signature of a #TpProxy::interface-added signal callback. * * Since: 0.7.1 */ /** * TpProxyClass: * @parent_class: The parent class structure * @interface: If set non-zero by a subclass, #TpProxy will * automatically add this interface in its constructor * @must_have_unique_name: If set %TRUE by a subclass, the #TpProxy * constructor will fail if a well-known bus name is given * @_reserved_flags: Reserved for future expansion * @_reserved: Reserved for future expansion * @priv: Opaque pointer for private data * * The class of a #TpProxy. * * Since: 0.7.1 */ typedef struct _TpProxyErrorMappingLink TpProxyErrorMappingLink; struct _TpProxyErrorMappingLink { const gchar *prefix; GQuark domain; GEnumClass *code_enum_class; TpProxyErrorMappingLink *next; }; typedef struct _TpProxyInterfaceAddLink TpProxyInterfaceAddLink; struct _TpProxyInterfaceAddLink { TpProxyInterfaceAddedCb callback; TpProxyInterfaceAddLink *next; }; /** * TpProxyInvokeFunc: * @self: the #TpProxy on which the D-Bus method was invoked * @error: %NULL if the method call succeeded, or a non-%NULL error if the * method call failed * @args: array of "out" arguments (return values) for the D-Bus method, * or %NULL if an error occurred or if there were no "out" arguments * @callback: the callback that should be invoked, as passed to * tp_proxy_pending_call_v0_new() * @user_data: user-supplied data to pass to the callback, as passed to * tp_proxy_pending_call_v0_new() * @weak_object: user-supplied object to pass to the callback, as passed to * tp_proxy_pending_call_v0_new() * * Signature of a callback invoked by the #TpProxy machinery after a D-Bus * method call has succeeded or failed. It is responsible for calling the * user-supplied callback. * * Because parts of dbus-glib aren't reentrant, this callback may be called * from an idle handler shortly after the method call reply is received, * rather than from the callback for the reply. * * At most one of @args and @error can be non-%NULL (implementations may * assert this). @args and @error may both be %NULL if a method with no * "out" arguments (i.e. a method that returns nothing) was called * successfully. * * The #TpProxyInvokeFunc must call callback with @user_data, @weak_object, * and appropriate arguments derived from @error and @args. It is responsible * for freeing @error and @args, if their ownership has not been transferred. * * Since: 0.7.1 */ struct _TpProxyPrivate { /* GQuark for interface => either a ref'd DBusGProxy *, * or the TpProxy itself used as a dummy value to indicate that * the DBusGProxy has not been needed yet */ GData *interfaces; unsigned dispose_has_run:1; }; G_DEFINE_TYPE (TpProxy, tp_proxy, G_TYPE_OBJECT); enum { PROP_DBUS_DAEMON = 1, PROP_DBUS_CONNECTION, PROP_BUS_NAME, PROP_OBJECT_PATH, PROP_INTERFACES, N_PROPS }; enum { SIGNAL_INTERFACE_ADDED, SIGNAL_INVALIDATED, N_SIGNALS }; static guint signals[N_SIGNALS] = {0}; static void tp_proxy_iface_destroyed_cb (DBusGProxy *dgproxy, TpProxy *self); /** * tp_proxy_borrow_interface_by_id: * @self: the TpProxy * @iface: quark representing the interface required * @error: used to raise an error in the #TP_DBUS_ERRORS domain if @iface * is invalid, @self has been invalidated or @self does not implement * @iface * * * * Returns: a borrowed reference to a #DBusGProxy * for which the bus name and object path are the same as for @self, but the * interface is as given (or %NULL if an @error is raised). * The reference is only valid as long as @self is. * * Since: 0.7.1 */ DBusGProxy * tp_proxy_borrow_interface_by_id (TpProxy *self, GQuark iface, GError **error) { gpointer dgproxy; if (self->invalidated != NULL) { g_set_error (error, self->invalidated->domain, self->invalidated->code, "%s", self->invalidated->message); return NULL; } if (!tp_dbus_check_valid_interface_name (g_quark_to_string (iface), error)) return NULL; dgproxy = g_datalist_id_get_data (&self->priv->interfaces, iface); if (dgproxy == self) { /* dummy value - we've never actually needed the interface, so we * didn't create it, to avoid binding to all the signals */ dgproxy = dbus_g_proxy_new_for_name (self->dbus_connection, self->bus_name, self->object_path, g_quark_to_string (iface)); DEBUG ("%p: %s DBusGProxy is %p", self, g_quark_to_string (iface), dgproxy); g_signal_connect (dgproxy, "destroy", G_CALLBACK (tp_proxy_iface_destroyed_cb), self); g_datalist_id_set_data_full (&self->priv->interfaces, iface, dgproxy, g_object_unref); g_signal_emit (self, signals[SIGNAL_INTERFACE_ADDED], 0, (guint) iface, dgproxy); } if (dgproxy != NULL) { return dgproxy; } g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_NO_INTERFACE, "Object %s does not have interface %s", self->object_path, g_quark_to_string (iface)); return NULL; } /** * tp_proxy_has_interface_by_id: * @self: the #TpProxy (or subclass) * @iface: quark representing the interface required * * * * Returns: %TRUE if this proxy implements the given interface. * * Since: 0.7.1 */ gboolean tp_proxy_has_interface_by_id (gpointer self, GQuark iface) { TpProxy *proxy = TP_PROXY (self); return (g_datalist_id_get_data (&proxy->priv->interfaces, iface) != NULL); } /** * tp_proxy_has_interface: * @self: the #TpProxy (or subclass) * @iface: the interface required, as a string * * A macro wrapping tp_proxy_has_interface_by_id(). Returns %TRUE if this * proxy implements the given interface. * * Since: 0.7.1 */ static void tp_proxy_lose_interface (GQuark unused, gpointer dgproxy_or_self, gpointer self) { if (dgproxy_or_self != self) g_signal_handlers_disconnect_by_func (dgproxy_or_self, G_CALLBACK (tp_proxy_iface_destroyed_cb), self); } static void tp_proxy_lose_interfaces (TpProxy *self) { g_datalist_foreach (&self->priv->interfaces, tp_proxy_lose_interface, self); g_datalist_clear (&self->priv->interfaces); } /* This signature is chosen to match GSourceFunc */ static gboolean tp_proxy_emit_invalidated (gpointer p) { TpProxy *self = p; g_signal_emit (self, signals[SIGNAL_INVALIDATED], 0, self->invalidated->domain, self->invalidated->code, self->invalidated->message); /* Don't clear the datalist until after we've emitted the signal, so * the pending call and signal connection friend classes can still get * to the proxies */ tp_proxy_lose_interfaces (self); if (self->dbus_connection != NULL) { dbus_g_connection_unref (self->dbus_connection); self->dbus_connection = NULL; } return FALSE; } /** * tp_proxy_invalidate: * @self: a proxy * @error: an error causing the invalidation * * Mark @self as having been invalidated - no further calls will work, and * if not already invalidated, the #TpProxy:invalidated signal will be emitted * with the given error. * * Since: 0.7.1 */ void tp_proxy_invalidate (TpProxy *self, const GError *error) { g_return_if_fail (self != NULL); g_return_if_fail (error != NULL); if (self->invalidated == NULL) { DEBUG ("%p: %s", self, error->message); self->invalidated = g_error_copy (error); tp_proxy_emit_invalidated (self); } } static void tp_proxy_iface_destroyed_cb (DBusGProxy *dgproxy, TpProxy *self) { /* We can't call any API on the proxy now. Because the proxies are all * for the same bus name, we can assume that all of them are equally * useless now */ tp_proxy_lose_interfaces (self); /* We need to be able to delay emitting the invalidated signal, so that * any queued-up method calls and signal handlers will run first, and so * it doesn't try to reenter libdbus. */ if (self->invalidated == NULL) { DEBUG ("%p", self); self->invalidated = g_error_new_literal (TP_DBUS_ERRORS, TP_DBUS_ERROR_NAME_OWNER_LOST, "Name owner lost (service crashed?)"); g_idle_add_full (G_PRIORITY_HIGH, tp_proxy_emit_invalidated, g_object_ref (self), g_object_unref); } } /** * tp_proxy_add_interface_by_id: * @self: the TpProxy, which must not have become #TpProxy::invalidated. * @iface: quark representing the interface to be added * * Declare that this proxy supports a given interface. * * To use methods and signals of that interface, either call * tp_proxy_borrow_interface_by_id() to get the #DBusGProxy, or use the * tp_cli_* wrapper functions (strongly recommended). * * If the interface is the proxy's "main interface", or has already been * added, then do nothing. * * Returns: either %NULL or a borrowed #DBusGProxy corresponding to @iface, * depending on implementation details. To reliably borrow the #DBusGProxy, use * tp_proxy_borrow_interface_by_id(). (This method should probably have * returned void; sorry.) * * Since: 0.7.1 */ DBusGProxy * tp_proxy_add_interface_by_id (TpProxy *self, GQuark iface) { DBusGProxy *iface_proxy = g_datalist_id_get_data (&self->priv->interfaces, iface); g_return_val_if_fail (tp_dbus_check_valid_interface_name (g_quark_to_string (iface), NULL), NULL); g_return_val_if_fail (tp_proxy_get_invalidated (self) == NULL, NULL); if (iface_proxy == NULL) { /* we don't want to actually create it just yet - dbus-glib will * helpfully wake us up on every signal, if we do. So we set a * dummy value (self), and replace it with the real value in * tp_proxy_borrow_interface_by_id */ g_datalist_id_set_data_full (&self->priv->interfaces, iface, self, NULL); } return iface_proxy; } static GQuark error_mapping_quark (void) { static GQuark q = 0; if (G_UNLIKELY (q == 0)) { q = g_quark_from_static_string ("TpProxyErrorMappingCb_0.7.1"); } return q; } /** * tp_proxy_dbus_error_to_gerror: * @self: a #TpProxy or subclass * @dbus_error: a D-Bus error name, for instance from the callback for * tp_cli_connection_connect_to_connection_error() * @debug_message: a debug message that accompanied the error name, or %NULL * @error: used to return the corresponding #GError * * Convert a D-Bus error name into a GError as if it was returned by a method * on this proxy. This method is useful when D-Bus error names are emitted in * signals, such as Connection.ConnectionError and * Group.MembersChangedDetailed. * * Since: 0.7.24 */ void tp_proxy_dbus_error_to_gerror (gpointer self, const char *dbus_error, const char *debug_message, GError **error) { GType proxy_type = TP_TYPE_PROXY; GType type; g_return_if_fail (TP_IS_PROXY (self)); if (error == NULL) return; g_return_if_fail (*error == NULL); if (!tp_dbus_check_valid_interface_name (dbus_error, error)) { return; } if (debug_message == NULL) debug_message = ""; for (type = G_TYPE_FROM_INSTANCE (self); type != proxy_type; type = g_type_parent (type)) { TpProxyErrorMappingLink *iter; for (iter = g_type_get_qdata (type, error_mapping_quark ()); iter != NULL; iter = iter->next) { size_t prefix_len = strlen (iter->prefix); if (!strncmp (dbus_error, iter->prefix, prefix_len) && dbus_error[prefix_len] == '.') { GEnumValue *code = g_enum_get_value_by_nick (iter->code_enum_class, dbus_error + prefix_len + 1); if (code != NULL) { g_set_error (error, iter->domain, code->value, "%s", debug_message); return; } } } } /* we don't have an error mapping - so let's just paste the * error name and message into TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR */ g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR, "%s: %s", dbus_error, debug_message); } GError * _tp_proxy_take_and_remap_error (TpProxy *self, GError *error) { if (error == NULL || error->domain != DBUS_GERROR || error->code != DBUS_GERROR_REMOTE_EXCEPTION) { return error; } else { GError *replacement = NULL; const gchar *dbus = dbus_g_error_get_name (error); tp_proxy_dbus_error_to_gerror (self, dbus, error->message, &replacement); g_error_free (error); return replacement; } } static void dup_quark_into_ptr_array (GQuark q, gpointer unused, gpointer user_data) { GPtrArray *strings = user_data; g_ptr_array_add (strings, g_strdup (g_quark_to_string (q))); } static void tp_proxy_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { TpProxy *self = TP_PROXY (object); switch (property_id) { case PROP_DBUS_DAEMON: if (TP_IS_DBUS_DAEMON (self)) { g_value_set_object (value, self); } else { g_value_set_object (value, self->dbus_daemon); } break; case PROP_DBUS_CONNECTION: g_value_set_boxed (value, self->dbus_connection); break; case PROP_BUS_NAME: g_value_set_string (value, self->bus_name); break; case PROP_OBJECT_PATH: g_value_set_string (value, self->object_path); break; case PROP_INTERFACES: { GPtrArray *strings = g_ptr_array_new (); g_datalist_foreach (&self->priv->interfaces, dup_quark_into_ptr_array, strings); g_ptr_array_add (strings, NULL); g_value_take_boxed (value, g_ptr_array_free (strings, FALSE)); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void tp_proxy_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { TpProxy *self = TP_PROXY (object); switch (property_id) { case PROP_DBUS_DAEMON: if (TP_IS_DBUS_DAEMON (self)) { g_assert (g_value_get_object (value) == NULL); } else { TpProxy *daemon_as_proxy = TP_PROXY (g_value_get_object (value)); g_assert (self->dbus_daemon == NULL); if (daemon_as_proxy != NULL) self->dbus_daemon = TP_DBUS_DAEMON (g_object_ref (daemon_as_proxy)); if (daemon_as_proxy != NULL) { g_assert (self->dbus_connection == NULL || self->dbus_connection == daemon_as_proxy->dbus_connection); if (self->dbus_connection == NULL) self->dbus_connection = dbus_g_connection_ref (daemon_as_proxy->dbus_connection); } } break; case PROP_DBUS_CONNECTION: { DBusGConnection *conn = g_value_get_boxed (value); /* if we're given a NULL dbus-connection, but we've got a * DBusGConnection from the dbus-daemon, we want to keep it */ if (conn == NULL) return; if (self->dbus_connection == NULL) self->dbus_connection = g_value_dup_boxed (value); g_assert (self->dbus_connection == g_value_get_boxed (value)); } break; case PROP_BUS_NAME: g_assert (self->bus_name == NULL); self->bus_name = g_value_dup_string (value); break; case PROP_OBJECT_PATH: g_assert (self->object_path == NULL); self->object_path = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void tp_proxy_init (TpProxy *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_PROXY, TpProxyPrivate); } static GQuark interface_added_cb_quark (void) { static GQuark q = 0; if (G_UNLIKELY (q == 0)) { q = g_quark_from_static_string ("TpProxyInterfaceAddedCb_0.7.1"); } return q; } static GObject * tp_proxy_constructor (GType type, guint n_params, GObjectConstructParam *params) { GObjectClass *object_class = (GObjectClass *) tp_proxy_parent_class; TpProxy *self = TP_PROXY (object_class->constructor (type, n_params, params)); TpProxyClass *klass = TP_PROXY_GET_CLASS (self); TpProxyInterfaceAddLink *iter; GType ancestor_type = type; GType proxy_parent_type = G_TYPE_FROM_CLASS (tp_proxy_parent_class); _tp_register_dbus_glib_marshallers (); for (ancestor_type = type; ancestor_type != proxy_parent_type && ancestor_type != 0; ancestor_type = g_type_parent (ancestor_type)) { for (iter = g_type_get_qdata (ancestor_type, interface_added_cb_quark ()); iter != NULL; iter = iter->next) g_signal_connect (self, "interface-added", G_CALLBACK (iter->callback), NULL); } g_return_val_if_fail (self->dbus_connection != NULL, NULL); g_return_val_if_fail (self->object_path != NULL, NULL); g_return_val_if_fail (self->bus_name != NULL, NULL); g_return_val_if_fail (tp_dbus_check_valid_object_path (self->object_path, NULL), NULL); g_return_val_if_fail (tp_dbus_check_valid_bus_name (self->bus_name, TP_DBUS_NAME_TYPE_ANY, NULL), NULL); tp_proxy_add_interface_by_id (self, TP_IFACE_QUARK_DBUS_INTROSPECTABLE); tp_proxy_add_interface_by_id (self, TP_IFACE_QUARK_DBUS_PEER); tp_proxy_add_interface_by_id (self, TP_IFACE_QUARK_DBUS_PROPERTIES); if (klass->interface != 0) { tp_proxy_add_interface_by_id (self, klass->interface); } /* Some interfaces are stateful, so we only allow binding to a unique * name, like in dbus_g_proxy_new_for_name_owner() */ if (klass->must_have_unique_name) { g_return_val_if_fail (self->bus_name[0] == ':', NULL); } return (GObject *) self; } static void tp_proxy_dispose (GObject *object) { TpProxy *self = TP_PROXY (object); GError e = { TP_DBUS_ERRORS, TP_DBUS_ERROR_PROXY_UNREFERENCED, "Proxy unreferenced" }; if (self->priv->dispose_has_run) return; self->priv->dispose_has_run = TRUE; DEBUG ("%p", self); tp_proxy_invalidate (self, &e); if (self->dbus_daemon != NULL) { g_object_unref (self->dbus_daemon); self->dbus_daemon = NULL; } G_OBJECT_CLASS (tp_proxy_parent_class)->dispose (object); } static void tp_proxy_finalize (GObject *object) { TpProxy *self = TP_PROXY (object); DEBUG ("%p", self); g_assert (self->invalidated != NULL); g_error_free (self->invalidated); g_free (self->bus_name); g_free (self->object_path); G_OBJECT_CLASS (tp_proxy_parent_class)->finalize (object); } /** * tp_proxy_or_subclass_hook_on_interface_add: * @proxy_or_subclass: The #GType of #TpProxy or a subclass * @callback: A signal handler for #TpProxy::interface-added * * Arrange for @callback to be connected to #TpProxy::interface-added * during the #TpProxy constructor. This is done sufficiently early that * it will see the signal for the default interface (@interface member of * #TpProxyClass), if any, being added. The intended use is for the callback * to call dbus_g_proxy_add_signal() on the new #DBusGProxy. * * Since 0.7.6, to ensure correct overriding of interfaces that might be * added to telepathy-glib, before calling this function you should * call tp_proxy_init_known_interfaces, tp_connection_init_known_interfaces, * tp_channel_init_known_interfaces etc. as appropriate for the subclass. * * Since: 0.7.1 */ void tp_proxy_or_subclass_hook_on_interface_add (GType proxy_or_subclass, TpProxyInterfaceAddedCb callback) { GQuark q = interface_added_cb_quark (); TpProxyInterfaceAddLink *old_link = g_type_get_qdata (proxy_or_subclass, q); TpProxyInterfaceAddLink *new_link; g_return_if_fail (g_type_is_a (proxy_or_subclass, TP_TYPE_PROXY)); g_return_if_fail (callback != NULL); /* never freed, suppressed in telepathy-glib.supp */ new_link = g_slice_new0 (TpProxyInterfaceAddLink); new_link->callback = callback; new_link->next = old_link; /* may be NULL */ g_type_set_qdata (proxy_or_subclass, q, new_link); } /** * tp_proxy_subclass_add_error_mapping: * @proxy_subclass: The #GType of a subclass of #TpProxy (which must not be * #TpProxy itself) * @static_prefix: A prefix for D-Bus error names, not including the trailing * dot (which must remain valid forever, and should usually be in static * storage) * @domain: A quark representing the corresponding #GError domain * @code_enum_type: The type of a subclass of #GEnumClass * * Register a mapping from D-Bus errors received from the given proxy * subclass to #GError instances. * * When a D-Bus error is received, the #TpProxy code checks for error * mappings registered for the class of the proxy receiving the error, * then for all of its parent classes. * * If there is an error mapping for which the D-Bus error name * starts with the mapping's @static_prefix, the proxy will check the * corresponding @code_enum_type for a value whose @value_nick is * the rest of the D-Bus error name (with the leading dot removed). If there * isn't such a value, it will continue to try other error mappings. * * If a suitable error mapping and code are found, the #GError that is raised * will have its error domain set to the @domain from the error mapping, * and its error code taken from the enum represented by the @code_enum_type. * * If no suitable error mapping or code is found, the #GError will have * error domain %TP_DBUS_ERRORS and error code * %TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR. * * Since: 0.7.1 */ void tp_proxy_subclass_add_error_mapping (GType proxy_subclass, const gchar *static_prefix, GQuark domain, GType code_enum_type) { GQuark q = error_mapping_quark (); TpProxyErrorMappingLink *old_link = g_type_get_qdata (proxy_subclass, q); TpProxyErrorMappingLink *new_link; GType tp_type_proxy = TP_TYPE_PROXY; g_return_if_fail (proxy_subclass != tp_type_proxy); g_return_if_fail (g_type_is_a (proxy_subclass, tp_type_proxy)); g_return_if_fail (static_prefix != NULL); g_return_if_fail (domain != 0); g_return_if_fail (code_enum_type != G_TYPE_INVALID); new_link = g_slice_new0 (TpProxyErrorMappingLink); new_link->prefix = static_prefix; new_link->domain = domain; /* We never unref the enum type - intentional one-per-process leak. * See "tp_proxy_subclass_add_error_mapping refs the enum" in our valgrind * suppressions file */ new_link->code_enum_class = g_type_class_ref (code_enum_type); new_link->next = old_link; /* may be NULL */ g_type_set_qdata (proxy_subclass, q, new_link); } static void tp_proxy_class_init (TpProxyClass *klass) { GParamSpec *param_spec; GObjectClass *object_class = G_OBJECT_CLASS (klass); tp_proxy_init_known_interfaces (); g_type_class_add_private (klass, sizeof (TpProxyPrivate)); object_class->constructor = tp_proxy_constructor; object_class->get_property = tp_proxy_get_property; object_class->set_property = tp_proxy_set_property; object_class->dispose = tp_proxy_dispose; object_class->finalize = tp_proxy_finalize; /** * TpProxy:dbus-daemon: * * The D-Bus daemon for this object (this object itself, if it is a * TpDBusDaemon). Read-only except during construction. */ param_spec = g_param_spec_object ("dbus-daemon", "D-Bus daemon", "The D-Bus daemon used by this object, or this object itself if it's " "a TpDBusDaemon", TP_TYPE_DBUS_DAEMON, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK); g_object_class_install_property (object_class, PROP_DBUS_DAEMON, param_spec); /** * TpProxy:dbus-connection: * * The D-Bus connection for this object. Read-only except during * construction. */ param_spec = g_param_spec_boxed ("dbus-connection", "D-Bus connection", "The D-Bus connection used by this object", DBUS_TYPE_G_CONNECTION, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK); g_object_class_install_property (object_class, PROP_DBUS_CONNECTION, param_spec); /** * TpProxy:bus-name: * * The D-Bus bus name for this object. Read-only except during construction. */ param_spec = g_param_spec_string ("bus-name", "D-Bus bus name", "The D-Bus bus name for this object", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK); g_object_class_install_property (object_class, PROP_BUS_NAME, param_spec); /** * TpProxy:object-path: * * The D-Bus object path for this object. Read-only except during * construction. */ param_spec = g_param_spec_string ("object-path", "D-Bus object path", "The D-Bus object path for this object", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK); g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec); /** * TpProxy:interfaces: * * Known D-Bus interface names for this object. */ param_spec = g_param_spec_boxed ("interfaces", "D-Bus interfaces", "Known D-Bus interface names for this object", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); g_object_class_install_property (object_class, PROP_INTERFACES, param_spec); /** * TpProxy::interface-added: * @self: the proxy object * @id: the GQuark representing the interface * @proxy: the dbus-glib proxy representing the interface * * Emitted when this proxy has gained an interface. It is not guaranteed * to be emitted immediately, but will be emitted before the interface is * first used (at the latest: before it's returned from * tp_proxy_borrow_interface_by_id(), any signal is connected, or any * method is called). * * The intended use is to call dbus_g_proxy_add_signals(). This signal * should only be used by TpProy implementations */ signals[SIGNAL_INTERFACE_ADDED] = g_signal_new ("interface-added", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, _tp_marshal_VOID__UINT_OBJECT, G_TYPE_NONE, 2, G_TYPE_UINT, DBUS_TYPE_G_PROXY); /** * TpProxy::invalidated: * @self: the proxy object * @domain: domain of a GError indicating why this proxy was invalidated * @code: error code of a GError indicating why this proxy was invalidated * @message: a message associated with the error * * Emitted when this proxy has been become invalid for * whatever reason. Any more specific signal should be emitted first. */ signals[SIGNAL_INVALIDATED] = g_signal_new ("invalidated", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, _tp_marshal_VOID__UINT_INT_STRING, G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_INT, G_TYPE_STRING); } /** * tp_proxy_get_dbus_daemon: * @self: a #TpProxy or subclass * * * * Returns: a borrowed reference to the #TpDBusDaemon for this object, if any; * always %NULL if this object is itself a #TpDBusDaemon. The caller must * reference the returned object with g_object_ref() if it will be kept. * * Since: 0.7.17 */ TpDBusDaemon * tp_proxy_get_dbus_daemon (gpointer self) { TpProxy *proxy = TP_PROXY (self); return proxy->dbus_daemon; } /** * tp_proxy_get_dbus_connection: * @self: a #TpProxy or subclass * * * * Returns: a borrowed reference to the D-Bus connection used by this object. * The caller must reference the returned pointer with * dbus_g_connection_ref() if it will be kept. * * Since: 0.7.17 */ DBusGConnection * tp_proxy_get_dbus_connection (gpointer self) { TpProxy *proxy = TP_PROXY (self); return proxy->dbus_connection; } /** * tp_proxy_get_bus_name: * @self: a #TpProxy or subclass * * * * Returns: the bus name of the application exporting the object. The caller * must copy the string with g_strdup() if it will be kept. * * Since: 0.7.17 */ const gchar * tp_proxy_get_bus_name (gpointer self) { TpProxy *proxy = TP_PROXY (self); return proxy->bus_name; } /** * tp_proxy_get_object_path: * @self: a #TpProxy or subclass * * * * Returns: the object path of the remote object. The caller must copy the * string with g_strdup() if it will be kept. * * Since: 0.7.17 */ const gchar * tp_proxy_get_object_path (gpointer self) { TpProxy *proxy = TP_PROXY (self); return proxy->object_path; } /** * tp_proxy_get_invalidated: * @self: a #TpProxy or subclass * * * * Returns: the reason this proxy was invalidated, or %NULL if has not been * invalidated. The caller must copy the error, for instance with * g_error_copy(), if it will be kept. * * Since: 0.7.17 */ const GError * tp_proxy_get_invalidated (gpointer self) { TpProxy *proxy = TP_PROXY (self); return proxy->invalidated; } /** * tp_proxy_dbus_g_proxy_claim_for_signal_adding: * @proxy: a #DBusGProxy * * Attempt to "claim" a #DBusGProxy for addition of signal signatures. * If this function has not been called on @proxy before, %TRUE is * returned, and the caller may safely call dbus_g_proxy_add_signal() * on @proxy. If this function has already been caled, %FALSE is * returned, and the caller may not safely call dbus_g_proxy_add_signal(). * * This is intended for use by auto-generated signal-adding functions, * to allow interfaces provided as local extensions to override those in * telepathy-glib without causing assertion failures. * * Returns: %TRUE if it is safe to call dbus_g_proxy_add_signal() * Since: 0.7.6 */ gboolean tp_proxy_dbus_g_proxy_claim_for_signal_adding (DBusGProxy *proxy) { static GQuark q = 0; g_return_val_if_fail (proxy != NULL, FALSE); if (G_UNLIKELY (q == 0)) { q = g_quark_from_static_string ( "tp_proxy_dbus_g_proxy_claim_for_signal_adding@0.7.6"); } if (g_object_get_qdata ((GObject *) proxy, q) != NULL) { /* Someone else has already added signal signatures for this interface. * We can't do it again or it'll cause an assertion */ return FALSE; } /* the proxy is just used as qdata here because it's a convenient * non-NULL pointer */ g_object_set_qdata ((GObject *) proxy, q, proxy); return TRUE; } static gpointer tp_proxy_once (gpointer data G_GNUC_UNUSED) { GType type = TP_TYPE_PROXY; tp_proxy_or_subclass_hook_on_interface_add (type, tp_cli_generic_add_signals); return NULL; } /** * tp_proxy_init_known_interfaces: * * Ensure that the known interfaces for TpProxy 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(). * * Functions like tp_connection_init_known_interfaces and * tp_channel_init_known_interfaces do this automatically. * * Since: 0.7.6 */ void tp_proxy_init_known_interfaces (void) { static GOnce once = G_ONCE_INIT; g_once (&once, tp_proxy_once, NULL); }