/* GDBus - GLib D-Bus Library * * Copyright (C) 2008-2010 Red Hat, Inc. * * 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, see . * * Author: David Zeuthen */ #include "config.h" #include "gcancellable.h" #include "gdbusobjectmanager.h" #include "gdbusobjectmanagerclient.h" #include "gdbusobject.h" #include "gdbusprivate.h" #include "gioenumtypes.h" #include "gioerror.h" #include "ginitable.h" #include "gasyncresult.h" #include "gasyncinitable.h" #include "gdbusconnection.h" #include "gdbusutils.h" #include "gdbusobject.h" #include "gdbusobjectproxy.h" #include "gdbusproxy.h" #include "gdbusinterface.h" #include "glibintl.h" #include "gmarshal-internal.h" /** * SECTION:gdbusobjectmanagerclient * @short_description: Client-side object manager * @include: gio/gio.h * * #GDBusObjectManagerClient is used to create, monitor and delete object * proxies for remote objects exported by a #GDBusObjectManagerServer (or any * code implementing the * [org.freedesktop.DBus.ObjectManager](http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager) * interface). * * Once an instance of this type has been created, you can connect to * the #GDBusObjectManager::object-added and * #GDBusObjectManager::object-removed signals and inspect the * #GDBusObjectProxy objects returned by * g_dbus_object_manager_get_objects(). * * If the name for a #GDBusObjectManagerClient is not owned by anyone at * object construction time, the default behavior is to request the * message bus to launch an owner for the name. This behavior can be * disabled using the %G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START * flag. It's also worth noting that this only works if the name of * interest is activatable in the first place. E.g. in some cases it * is not possible to launch an owner for the requested name. In this * case, #GDBusObjectManagerClient object construction still succeeds but * there will be no object proxies * (e.g. g_dbus_object_manager_get_objects() returns the empty list) and * the #GDBusObjectManagerClient:name-owner property is %NULL. * * The owner of the requested name can come and go (for example * consider a system service being restarted) – #GDBusObjectManagerClient * handles this case too; simply connect to the #GObject::notify * signal to watch for changes on the #GDBusObjectManagerClient:name-owner * property. When the name owner vanishes, the behavior is that * #GDBusObjectManagerClient:name-owner is set to %NULL (this includes * emission of the #GObject::notify signal) and then * #GDBusObjectManager::object-removed signals are synthesized * for all currently existing object proxies. Since * #GDBusObjectManagerClient:name-owner is %NULL when this happens, you can * use this information to disambiguate a synthesized signal from a * genuine signal caused by object removal on the remote * #GDBusObjectManager. Similarly, when a new name owner appears, * #GDBusObjectManager::object-added signals are synthesized * while #GDBusObjectManagerClient:name-owner is still %NULL. Only when all * object proxies have been added, the #GDBusObjectManagerClient:name-owner * is set to the new name owner (this includes emission of the * #GObject::notify signal). Furthermore, you are guaranteed that * #GDBusObjectManagerClient:name-owner will alternate between a name owner * (e.g. `:1.42`) and %NULL even in the case where * the name of interest is atomically replaced * * Ultimately, #GDBusObjectManagerClient is used to obtain #GDBusProxy * instances. All signals (including the * org.freedesktop.DBus.Properties::PropertiesChanged signal) * delivered to #GDBusProxy instances are guaranteed to originate * from the name owner. This guarantee along with the behavior * described above, means that certain race conditions including the * "half the proxy is from the old owner and the other half is from * the new owner" problem cannot happen. * * To avoid having the application connect to signals on the returned * #GDBusObjectProxy and #GDBusProxy objects, the * #GDBusObject::interface-added, * #GDBusObject::interface-removed, * #GDBusProxy::g-properties-changed and * #GDBusProxy::g-signal signals * are also emitted on the #GDBusObjectManagerClient instance managing these * objects. The signals emitted are * #GDBusObjectManager::interface-added, * #GDBusObjectManager::interface-removed, * #GDBusObjectManagerClient::interface-proxy-properties-changed and * #GDBusObjectManagerClient::interface-proxy-signal. * * Note that all callbacks and signals are emitted in the * [thread-default main context][g-main-context-push-thread-default] * that the #GDBusObjectManagerClient object was constructed * in. Additionally, the #GDBusObjectProxy and #GDBusProxy objects * originating from the #GDBusObjectManagerClient object will be created in * the same context and, consequently, will deliver signals in the * same main loop. */ struct _GDBusObjectManagerClientPrivate { GMutex lock; GBusType bus_type; GDBusConnection *connection; gchar *object_path; gchar *name; gchar *name_owner; GDBusObjectManagerClientFlags flags; GDBusProxy *control_proxy; GHashTable *map_object_path_to_object_proxy; guint signal_subscription_id; gchar *match_rule; GDBusProxyTypeFunc get_proxy_type_func; gpointer get_proxy_type_user_data; GDestroyNotify get_proxy_type_destroy_notify; gulong name_owner_signal_id; gulong signal_signal_id; GCancellable *cancel; }; enum { PROP_0, PROP_BUS_TYPE, PROP_CONNECTION, PROP_FLAGS, PROP_OBJECT_PATH, PROP_NAME, PROP_NAME_OWNER, PROP_GET_PROXY_TYPE_FUNC, PROP_GET_PROXY_TYPE_USER_DATA, PROP_GET_PROXY_TYPE_DESTROY_NOTIFY }; enum { INTERFACE_PROXY_SIGNAL_SIGNAL, INTERFACE_PROXY_PROPERTIES_CHANGED_SIGNAL, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static void initable_iface_init (GInitableIface *initable_iface); static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface); static void dbus_object_manager_interface_init (GDBusObjectManagerIface *iface); G_DEFINE_TYPE_WITH_CODE (GDBusObjectManagerClient, g_dbus_object_manager_client, G_TYPE_OBJECT, G_ADD_PRIVATE (GDBusObjectManagerClient) G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init) G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init) G_IMPLEMENT_INTERFACE (G_TYPE_DBUS_OBJECT_MANAGER, dbus_object_manager_interface_init)) static void maybe_unsubscribe_signals (GDBusObjectManagerClient *manager); static void on_control_proxy_g_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, gpointer user_data); static void process_get_all_result (GDBusObjectManagerClient *manager, GVariant *value, const gchar *name_owner); static void g_dbus_object_manager_client_dispose (GObject *object) { GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (object); if (manager->priv->cancel != NULL) { g_cancellable_cancel (manager->priv->cancel); g_clear_object (&manager->priv->cancel); } G_OBJECT_CLASS (g_dbus_object_manager_client_parent_class)->dispose (object); } static void g_dbus_object_manager_client_finalize (GObject *object) { GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (object); maybe_unsubscribe_signals (manager); g_hash_table_unref (manager->priv->map_object_path_to_object_proxy); if (manager->priv->control_proxy != NULL && manager->priv->signal_signal_id != 0) g_signal_handler_disconnect (manager->priv->control_proxy, manager->priv->signal_signal_id); manager->priv->signal_signal_id = 0; if (manager->priv->control_proxy != NULL && manager->priv->name_owner_signal_id != 0) g_signal_handler_disconnect (manager->priv->control_proxy, manager->priv->name_owner_signal_id); manager->priv->name_owner_signal_id = 0; g_clear_object (&manager->priv->control_proxy); if (manager->priv->connection != NULL) g_object_unref (manager->priv->connection); g_free (manager->priv->object_path); g_free (manager->priv->name); g_free (manager->priv->name_owner); if (manager->priv->get_proxy_type_destroy_notify != NULL) manager->priv->get_proxy_type_destroy_notify (manager->priv->get_proxy_type_user_data); g_mutex_clear (&manager->priv->lock); if (G_OBJECT_CLASS (g_dbus_object_manager_client_parent_class)->finalize != NULL) G_OBJECT_CLASS (g_dbus_object_manager_client_parent_class)->finalize (object); } static void g_dbus_object_manager_client_get_property (GObject *_object, guint prop_id, GValue *value, GParamSpec *pspec) { GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (_object); switch (prop_id) { case PROP_CONNECTION: g_value_set_object (value, g_dbus_object_manager_client_get_connection (manager)); break; case PROP_OBJECT_PATH: g_value_set_string (value, g_dbus_object_manager_get_object_path (G_DBUS_OBJECT_MANAGER (manager))); break; case PROP_NAME: g_value_set_string (value, g_dbus_object_manager_client_get_name (manager)); break; case PROP_FLAGS: g_value_set_flags (value, g_dbus_object_manager_client_get_flags (manager)); break; case PROP_NAME_OWNER: g_value_take_string (value, g_dbus_object_manager_client_get_name_owner (manager)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (manager, prop_id, pspec); break; } } static void g_dbus_object_manager_client_set_property (GObject *_object, guint prop_id, const GValue *value, GParamSpec *pspec) { GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (_object); const gchar *name; switch (prop_id) { case PROP_BUS_TYPE: manager->priv->bus_type = g_value_get_enum (value); break; case PROP_CONNECTION: if (g_value_get_object (value) != NULL) { g_assert (manager->priv->connection == NULL); g_assert (G_IS_DBUS_CONNECTION (g_value_get_object (value))); manager->priv->connection = g_value_dup_object (value); } break; case PROP_OBJECT_PATH: g_assert (manager->priv->object_path == NULL); g_assert (g_variant_is_object_path (g_value_get_string (value))); manager->priv->object_path = g_value_dup_string (value); break; case PROP_NAME: g_assert (manager->priv->name == NULL); name = g_value_get_string (value); g_assert (name == NULL || g_dbus_is_name (name)); manager->priv->name = g_strdup (name); break; case PROP_FLAGS: manager->priv->flags = g_value_get_flags (value); break; case PROP_GET_PROXY_TYPE_FUNC: manager->priv->get_proxy_type_func = g_value_get_pointer (value); break; case PROP_GET_PROXY_TYPE_USER_DATA: manager->priv->get_proxy_type_user_data = g_value_get_pointer (value); break; case PROP_GET_PROXY_TYPE_DESTROY_NOTIFY: manager->priv->get_proxy_type_destroy_notify = g_value_get_pointer (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (manager, prop_id, pspec); break; } } static void g_dbus_object_manager_client_class_init (GDBusObjectManagerClientClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->dispose = g_dbus_object_manager_client_dispose; gobject_class->finalize = g_dbus_object_manager_client_finalize; gobject_class->set_property = g_dbus_object_manager_client_set_property; gobject_class->get_property = g_dbus_object_manager_client_get_property; /** * GDBusObjectManagerClient:connection: * * The #GDBusConnection to use. * * Since: 2.30 */ g_object_class_install_property (gobject_class, PROP_CONNECTION, g_param_spec_object ("connection", "Connection", "The connection to use", G_TYPE_DBUS_CONNECTION, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GDBusObjectManagerClient:bus-type: * * If this property is not %G_BUS_TYPE_NONE, then * #GDBusObjectManagerClient:connection must be %NULL and will be set to the * #GDBusConnection obtained by calling g_bus_get() with the value * of this property. * * Since: 2.30 */ g_object_class_install_property (gobject_class, PROP_BUS_TYPE, g_param_spec_enum ("bus-type", "Bus Type", "The bus to connect to, if any", G_TYPE_BUS_TYPE, G_BUS_TYPE_NONE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); /** * GDBusObjectManagerClient:flags: * * Flags from the #GDBusObjectManagerClientFlags enumeration. * * Since: 2.30 */ g_object_class_install_property (gobject_class, PROP_FLAGS, g_param_spec_flags ("flags", "Flags", "Flags for the proxy manager", G_TYPE_DBUS_OBJECT_MANAGER_CLIENT_FLAGS, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); /** * GDBusObjectManagerClient:object-path: * * The object path the manager is for. * * Since: 2.30 */ g_object_class_install_property (gobject_class, PROP_OBJECT_PATH, g_param_spec_string ("object-path", "Object Path", "The object path of the control object", NULL, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GDBusObjectManagerClient:name: * * The well-known name or unique name that the manager is for. * * Since: 2.30 */ g_object_class_install_property (gobject_class, PROP_NAME, g_param_spec_string ("name", "Name", "Name that the manager is for", NULL, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GDBusObjectManagerClient:name-owner: * * The unique name that owns #GDBusObjectManagerClient:name or %NULL if * no-one is currently owning the name. Connect to the * #GObject::notify signal to track changes to this property. * * Since: 2.30 */ g_object_class_install_property (gobject_class, PROP_NAME_OWNER, g_param_spec_string ("name-owner", "Name Owner", "The owner of the name we are watching", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GDBusObjectManagerClient:get-proxy-type-func: * * The #GDBusProxyTypeFunc to use when determining what #GType to * use for interface proxies or %NULL. * * Since: 2.30 */ g_object_class_install_property (gobject_class, PROP_GET_PROXY_TYPE_FUNC, g_param_spec_pointer ("get-proxy-type-func", "GDBusProxyTypeFunc Function Pointer", "The GDBusProxyTypeFunc pointer to use", G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GDBusObjectManagerClient:get-proxy-type-user-data: * * The #gpointer user_data to pass to #GDBusObjectManagerClient:get-proxy-type-func. * * Since: 2.30 */ g_object_class_install_property (gobject_class, PROP_GET_PROXY_TYPE_USER_DATA, g_param_spec_pointer ("get-proxy-type-user-data", "GDBusProxyTypeFunc User Data", "The GDBusProxyTypeFunc user_data", G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GDBusObjectManagerClient:get-proxy-type-destroy-notify: * * A #GDestroyNotify for the #gpointer user_data in #GDBusObjectManagerClient:get-proxy-type-user-data. * * Since: 2.30 */ g_object_class_install_property (gobject_class, PROP_GET_PROXY_TYPE_DESTROY_NOTIFY, g_param_spec_pointer ("get-proxy-type-destroy-notify", "GDBusProxyTypeFunc user data free function", "The GDBusProxyTypeFunc user data free function", G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GDBusObjectManagerClient::interface-proxy-signal: * @manager: The #GDBusObjectManagerClient emitting the signal. * @object_proxy: The #GDBusObjectProxy on which an interface is emitting a D-Bus signal. * @interface_proxy: The #GDBusProxy that is emitting a D-Bus signal. * @sender_name: The sender of the signal or NULL if the connection is not a bus connection. * @signal_name: The signal name. * @parameters: A #GVariant tuple with parameters for the signal. * * Emitted when a D-Bus signal is received on @interface_proxy. * * This signal exists purely as a convenience to avoid having to * connect signals to all interface proxies managed by @manager. * * This signal is emitted in the * [thread-default main context][g-main-context-push-thread-default] * that @manager was constructed in. * * Since: 2.30 */ signals[INTERFACE_PROXY_SIGNAL_SIGNAL] = g_signal_new (I_("interface-proxy-signal"), G_TYPE_DBUS_OBJECT_MANAGER_CLIENT, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GDBusObjectManagerClientClass, interface_proxy_signal), NULL, NULL, _g_cclosure_marshal_VOID__OBJECT_OBJECT_STRING_STRING_VARIANT, G_TYPE_NONE, 5, G_TYPE_DBUS_OBJECT_PROXY, G_TYPE_DBUS_PROXY, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_VARIANT); g_signal_set_va_marshaller (signals[INTERFACE_PROXY_SIGNAL_SIGNAL], G_TYPE_FROM_CLASS (klass), _g_cclosure_marshal_VOID__OBJECT_OBJECT_STRING_STRING_VARIANTv); /** * GDBusObjectManagerClient::interface-proxy-properties-changed: * @manager: The #GDBusObjectManagerClient emitting the signal. * @object_proxy: The #GDBusObjectProxy on which an interface has properties that are changing. * @interface_proxy: The #GDBusProxy that has properties that are changing. * @changed_properties: A #GVariant containing the properties that changed (type: `a{sv}`). * @invalidated_properties: (array zero-terminated=1) (element-type utf8): A %NULL terminated * array of properties that were invalidated. * * Emitted when one or more D-Bus properties on proxy changes. The * local cache has already been updated when this signal fires. Note * that both @changed_properties and @invalidated_properties are * guaranteed to never be %NULL (either may be empty though). * * This signal exists purely as a convenience to avoid having to * connect signals to all interface proxies managed by @manager. * * This signal is emitted in the * [thread-default main context][g-main-context-push-thread-default] * that @manager was constructed in. * * Since: 2.30 */ signals[INTERFACE_PROXY_PROPERTIES_CHANGED_SIGNAL] = g_signal_new (I_("interface-proxy-properties-changed"), G_TYPE_DBUS_OBJECT_MANAGER_CLIENT, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GDBusObjectManagerClientClass, interface_proxy_properties_changed), NULL, NULL, _g_cclosure_marshal_VOID__OBJECT_OBJECT_VARIANT_BOXED, G_TYPE_NONE, 4, G_TYPE_DBUS_OBJECT_PROXY, G_TYPE_DBUS_PROXY, G_TYPE_VARIANT, G_TYPE_STRV); g_signal_set_va_marshaller (signals[INTERFACE_PROXY_PROPERTIES_CHANGED_SIGNAL], G_TYPE_FROM_CLASS (klass), _g_cclosure_marshal_VOID__OBJECT_OBJECT_VARIANT_BOXEDv); } static void g_dbus_object_manager_client_init (GDBusObjectManagerClient *manager) { manager->priv = g_dbus_object_manager_client_get_instance_private (manager); g_mutex_init (&manager->priv->lock); manager->priv->map_object_path_to_object_proxy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref); manager->priv->cancel = g_cancellable_new (); } /* ---------------------------------------------------------------------------------------------------- */ /** * g_dbus_object_manager_client_new_sync: * @connection: A #GDBusConnection. * @flags: Zero or more flags from the #GDBusObjectManagerClientFlags enumeration. * @name: (nullable): The owner of the control object (unique or well-known name), or %NULL when not using a message bus connection. * @object_path: The object path of the control object. * @get_proxy_type_func: (nullable): A #GDBusProxyTypeFunc function or %NULL to always construct #GDBusProxy proxies. * @get_proxy_type_user_data: User data to pass to @get_proxy_type_func. * @get_proxy_type_destroy_notify: (nullable): Free function for @get_proxy_type_user_data or %NULL. * @cancellable: (nullable): A #GCancellable or %NULL * @error: Return location for error or %NULL. * * Creates a new #GDBusObjectManagerClient object. * * This is a synchronous failable constructor - the calling thread is * blocked until a reply is received. See g_dbus_object_manager_client_new() * for the asynchronous version. * * Returns: (transfer full) (type GDBusObjectManagerClient): A * #GDBusObjectManagerClient object or %NULL if @error is set. Free * with g_object_unref(). * * Since: 2.30 */ GDBusObjectManager * g_dbus_object_manager_client_new_sync (GDBusConnection *connection, GDBusObjectManagerClientFlags flags, const gchar *name, const gchar *object_path, GDBusProxyTypeFunc get_proxy_type_func, gpointer get_proxy_type_user_data, GDestroyNotify get_proxy_type_destroy_notify, GCancellable *cancellable, GError **error) { GInitable *initable; g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); g_return_val_if_fail ((name == NULL && g_dbus_connection_get_unique_name (connection) == NULL) || g_dbus_is_name (name), NULL); g_return_val_if_fail (g_variant_is_object_path (object_path), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); initable = g_initable_new (G_TYPE_DBUS_OBJECT_MANAGER_CLIENT, cancellable, error, "connection", connection, "flags", flags, "name", name, "object-path", object_path, "get-proxy-type-func", get_proxy_type_func, "get-proxy-type-user-data", get_proxy_type_user_data, "get-proxy-type-destroy-notify", get_proxy_type_destroy_notify, NULL); if (initable != NULL) return G_DBUS_OBJECT_MANAGER (initable); else return NULL; } /** * g_dbus_object_manager_client_new: * @connection: A #GDBusConnection. * @flags: Zero or more flags from the #GDBusObjectManagerClientFlags enumeration. * @name: The owner of the control object (unique or well-known name). * @object_path: The object path of the control object. * @get_proxy_type_func: (nullable): A #GDBusProxyTypeFunc function or %NULL to always construct #GDBusProxy proxies. * @get_proxy_type_user_data: User data to pass to @get_proxy_type_func. * @get_proxy_type_destroy_notify: (nullable): Free function for @get_proxy_type_user_data or %NULL. * @cancellable: (nullable): A #GCancellable or %NULL * @callback: A #GAsyncReadyCallback to call when the request is satisfied. * @user_data: The data to pass to @callback. * * Asynchronously creates a new #GDBusObjectManagerClient object. * * This is an asynchronous failable constructor. When the result is * ready, @callback will be invoked in the * [thread-default main context][g-main-context-push-thread-default] * of the thread you are calling this method from. You can * then call g_dbus_object_manager_client_new_finish() to get the result. See * g_dbus_object_manager_client_new_sync() for the synchronous version. * * Since: 2.30 */ void g_dbus_object_manager_client_new (GDBusConnection *connection, GDBusObjectManagerClientFlags flags, const gchar *name, const gchar *object_path, GDBusProxyTypeFunc get_proxy_type_func, gpointer get_proxy_type_user_data, GDestroyNotify get_proxy_type_destroy_notify, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); g_return_if_fail ((name == NULL && g_dbus_connection_get_unique_name (connection) == NULL) || g_dbus_is_name (name)); g_return_if_fail (g_variant_is_object_path (object_path)); g_async_initable_new_async (G_TYPE_DBUS_OBJECT_MANAGER_CLIENT, G_PRIORITY_DEFAULT, cancellable, callback, user_data, "connection", connection, "flags", flags, "name", name, "object-path", object_path, "get-proxy-type-func", get_proxy_type_func, "get-proxy-type-user-data", get_proxy_type_user_data, "get-proxy-type-destroy-notify", get_proxy_type_destroy_notify, NULL); } /** * g_dbus_object_manager_client_new_finish: * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_object_manager_client_new(). * @error: Return location for error or %NULL. * * Finishes an operation started with g_dbus_object_manager_client_new(). * * Returns: (transfer full) (type GDBusObjectManagerClient): A * #GDBusObjectManagerClient object or %NULL if @error is set. Free * with g_object_unref(). * * Since: 2.30 */ GDBusObjectManager * g_dbus_object_manager_client_new_finish (GAsyncResult *res, GError **error) { GObject *object; GObject *source_object; source_object = g_async_result_get_source_object (res); g_assert (source_object != NULL); object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error); g_object_unref (source_object); if (object != NULL) return G_DBUS_OBJECT_MANAGER (object); else return NULL; } /* ---------------------------------------------------------------------------------------------------- */ /** * g_dbus_object_manager_client_new_for_bus_sync: * @bus_type: A #GBusType. * @flags: Zero or more flags from the #GDBusObjectManagerClientFlags enumeration. * @name: The owner of the control object (unique or well-known name). * @object_path: The object path of the control object. * @get_proxy_type_func: (nullable): A #GDBusProxyTypeFunc function or %NULL to always construct #GDBusProxy proxies. * @get_proxy_type_user_data: User data to pass to @get_proxy_type_func. * @get_proxy_type_destroy_notify: (nullable): Free function for @get_proxy_type_user_data or %NULL. * @cancellable: (nullable): A #GCancellable or %NULL * @error: Return location for error or %NULL. * * Like g_dbus_object_manager_client_new_sync() but takes a #GBusType instead * of a #GDBusConnection. * * This is a synchronous failable constructor - the calling thread is * blocked until a reply is received. See g_dbus_object_manager_client_new_for_bus() * for the asynchronous version. * * Returns: (transfer full) (type GDBusObjectManagerClient): A * #GDBusObjectManagerClient object or %NULL if @error is set. Free * with g_object_unref(). * * Since: 2.30 */ GDBusObjectManager * g_dbus_object_manager_client_new_for_bus_sync (GBusType bus_type, GDBusObjectManagerClientFlags flags, const gchar *name, const gchar *object_path, GDBusProxyTypeFunc get_proxy_type_func, gpointer get_proxy_type_user_data, GDestroyNotify get_proxy_type_destroy_notify, GCancellable *cancellable, GError **error) { GInitable *initable; g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, NULL); g_return_val_if_fail (g_dbus_is_name (name), NULL); g_return_val_if_fail (g_variant_is_object_path (object_path), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); initable = g_initable_new (G_TYPE_DBUS_OBJECT_MANAGER_CLIENT, cancellable, error, "bus-type", bus_type, "flags", flags, "name", name, "object-path", object_path, "get-proxy-type-func", get_proxy_type_func, "get-proxy-type-user-data", get_proxy_type_user_data, "get-proxy-type-destroy-notify", get_proxy_type_destroy_notify, NULL); if (initable != NULL) return G_DBUS_OBJECT_MANAGER (initable); else return NULL; } /** * g_dbus_object_manager_client_new_for_bus: * @bus_type: A #GBusType. * @flags: Zero or more flags from the #GDBusObjectManagerClientFlags enumeration. * @name: The owner of the control object (unique or well-known name). * @object_path: The object path of the control object. * @get_proxy_type_func: (nullable): A #GDBusProxyTypeFunc function or %NULL to always construct #GDBusProxy proxies. * @get_proxy_type_user_data: User data to pass to @get_proxy_type_func. * @get_proxy_type_destroy_notify: (nullable): Free function for @get_proxy_type_user_data or %NULL. * @cancellable: (nullable): A #GCancellable or %NULL * @callback: A #GAsyncReadyCallback to call when the request is satisfied. * @user_data: The data to pass to @callback. * * Like g_dbus_object_manager_client_new() but takes a #GBusType instead of a * #GDBusConnection. * * This is an asynchronous failable constructor. When the result is * ready, @callback will be invoked in the * [thread-default main loop][g-main-context-push-thread-default] * of the thread you are calling this method from. You can * then call g_dbus_object_manager_client_new_for_bus_finish() to get the result. See * g_dbus_object_manager_client_new_for_bus_sync() for the synchronous version. * * Since: 2.30 */ void g_dbus_object_manager_client_new_for_bus (GBusType bus_type, GDBusObjectManagerClientFlags flags, const gchar *name, const gchar *object_path, GDBusProxyTypeFunc get_proxy_type_func, gpointer get_proxy_type_user_data, GDestroyNotify get_proxy_type_destroy_notify, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail (bus_type != G_BUS_TYPE_NONE); g_return_if_fail (g_dbus_is_name (name)); g_return_if_fail (g_variant_is_object_path (object_path)); g_async_initable_new_async (G_TYPE_DBUS_OBJECT_MANAGER_CLIENT, G_PRIORITY_DEFAULT, cancellable, callback, user_data, "bus-type", bus_type, "flags", flags, "name", name, "object-path", object_path, "get-proxy-type-func", get_proxy_type_func, "get-proxy-type-user-data", get_proxy_type_user_data, "get-proxy-type-destroy-notify", get_proxy_type_destroy_notify, NULL); } /** * g_dbus_object_manager_client_new_for_bus_finish: * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_object_manager_client_new_for_bus(). * @error: Return location for error or %NULL. * * Finishes an operation started with g_dbus_object_manager_client_new_for_bus(). * * Returns: (transfer full) (type GDBusObjectManagerClient): A * #GDBusObjectManagerClient object or %NULL if @error is set. Free * with g_object_unref(). * * Since: 2.30 */ GDBusObjectManager * g_dbus_object_manager_client_new_for_bus_finish (GAsyncResult *res, GError **error) { GObject *object; GObject *source_object; source_object = g_async_result_get_source_object (res); g_assert (source_object != NULL); object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error); g_object_unref (source_object); if (object != NULL) return G_DBUS_OBJECT_MANAGER (object); else return NULL; } /* ---------------------------------------------------------------------------------------------------- */ /** * g_dbus_object_manager_client_get_connection: * @manager: A #GDBusObjectManagerClient * * Gets the #GDBusConnection used by @manager. * * Returns: (transfer none): A #GDBusConnection object. Do not free, * the object belongs to @manager. * * Since: 2.30 */ GDBusConnection * g_dbus_object_manager_client_get_connection (GDBusObjectManagerClient *manager) { GDBusConnection *ret; g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), NULL); g_mutex_lock (&manager->priv->lock); ret = manager->priv->connection; g_mutex_unlock (&manager->priv->lock); return ret; } /** * g_dbus_object_manager_client_get_name: * @manager: A #GDBusObjectManagerClient * * Gets the name that @manager is for, or %NULL if not a message bus * connection. * * Returns: A unique or well-known name. Do not free, the string * belongs to @manager. * * Since: 2.30 */ const gchar * g_dbus_object_manager_client_get_name (GDBusObjectManagerClient *manager) { const gchar *ret; g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), NULL); g_mutex_lock (&manager->priv->lock); ret = manager->priv->name; g_mutex_unlock (&manager->priv->lock); return ret; } /** * g_dbus_object_manager_client_get_flags: * @manager: A #GDBusObjectManagerClient * * Gets the flags that @manager was constructed with. * * Returns: Zero of more flags from the #GDBusObjectManagerClientFlags * enumeration. * * Since: 2.30 */ GDBusObjectManagerClientFlags g_dbus_object_manager_client_get_flags (GDBusObjectManagerClient *manager) { GDBusObjectManagerClientFlags ret; g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE); g_mutex_lock (&manager->priv->lock); ret = manager->priv->flags; g_mutex_unlock (&manager->priv->lock); return ret; } /** * g_dbus_object_manager_client_get_name_owner: * @manager: A #GDBusObjectManagerClient. * * The unique name that owns the name that @manager is for or %NULL if * no-one currently owns that name. You can connect to the * #GObject::notify signal to track changes to the * #GDBusObjectManagerClient:name-owner property. * * Returns: (nullable): The name owner or %NULL if no name owner * exists. Free with g_free(). * * Since: 2.30 */ gchar * g_dbus_object_manager_client_get_name_owner (GDBusObjectManagerClient *manager) { gchar *ret; g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), NULL); g_mutex_lock (&manager->priv->lock); ret = g_strdup (manager->priv->name_owner); g_mutex_unlock (&manager->priv->lock); return ret; } /* ---------------------------------------------------------------------------------------------------- */ /* signal handler for all objects we manage - we dispatch signals * from here to the objects */ static void signal_cb (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (user_data); GDBusObjectProxy *object_proxy; GDBusInterface *interface; g_mutex_lock (&manager->priv->lock); object_proxy = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path); if (object_proxy == NULL) { g_mutex_unlock (&manager->priv->lock); goto out; } g_object_ref (object_proxy); g_mutex_unlock (&manager->priv->lock); //g_debug ("yay, signal_cb %s %s: %s\n", signal_name, object_path, g_variant_print (parameters, TRUE)); g_object_ref (manager); if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0) { if (g_strcmp0 (signal_name, "PropertiesChanged") == 0) { const gchar *interface_name; GVariant *changed_properties; const gchar **invalidated_properties; g_variant_get (parameters, "(&s@a{sv}^a&s)", &interface_name, &changed_properties, &invalidated_properties); interface = g_dbus_object_get_interface (G_DBUS_OBJECT (object_proxy), interface_name); if (interface != NULL) { GVariantIter property_iter; const gchar *property_name; GVariant *property_value; guint n; /* update caches... */ g_variant_iter_init (&property_iter, changed_properties); while (g_variant_iter_next (&property_iter, "{&sv}", &property_name, &property_value)) { g_dbus_proxy_set_cached_property (G_DBUS_PROXY (interface), property_name, property_value); g_variant_unref (property_value); } for (n = 0; invalidated_properties[n] != NULL; n++) { g_dbus_proxy_set_cached_property (G_DBUS_PROXY (interface), invalidated_properties[n], NULL); } /* ... and then synthesize the signal */ g_signal_emit_by_name (interface, "g-properties-changed", changed_properties, invalidated_properties); g_signal_emit (manager, signals[INTERFACE_PROXY_PROPERTIES_CHANGED_SIGNAL], 0, object_proxy, interface, changed_properties, invalidated_properties); g_object_unref (interface); } g_variant_unref (changed_properties); g_free (invalidated_properties); } } else { /* regular signal - just dispatch it */ interface = g_dbus_object_get_interface (G_DBUS_OBJECT (object_proxy), interface_name); if (interface != NULL) { g_signal_emit_by_name (interface, "g-signal", sender_name, signal_name, parameters); g_signal_emit (manager, signals[INTERFACE_PROXY_SIGNAL_SIGNAL], 0, object_proxy, interface, sender_name, signal_name, parameters); g_object_unref (interface); } } g_object_unref (manager); out: g_clear_object (&object_proxy); } static void subscribe_signals (GDBusObjectManagerClient *manager, const gchar *name_owner) { GError *error = NULL; GVariant *ret; g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager)); g_return_if_fail (manager->priv->signal_subscription_id == 0); g_return_if_fail (name_owner == NULL || g_dbus_is_unique_name (name_owner)); if (name_owner != NULL) { /* Only add path_namespace if it's non-'/'. This removes a no-op key from * the match rule, and also works around a D-Bus bug where * path_namespace='/' matches nothing in D-Bus versions < 1.6.18. * * See: https://bugs.freedesktop.org/show_bug.cgi?id=70799 */ if (g_str_equal (manager->priv->object_path, "/")) { manager->priv->match_rule = g_strdup_printf ("type='signal',sender='%s'", name_owner); } else { manager->priv->match_rule = g_strdup_printf ("type='signal',sender='%s',path_namespace='%s'", name_owner, manager->priv->object_path); } /* The bus daemon may not implement path_namespace so gracefully * handle this by using a fallback triggered if @error is set. */ ret = g_dbus_connection_call_sync (manager->priv->connection, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "AddMatch", g_variant_new ("(s)", manager->priv->match_rule), NULL, /* reply_type */ G_DBUS_CALL_FLAGS_NONE, -1, /* default timeout */ NULL, /* TODO: Cancellable */ &error); /* yay, bus daemon supports path_namespace */ if (ret != NULL) g_variant_unref (ret); } if (error == NULL) { /* still need to ask GDBusConnection for the callbacks */ manager->priv->signal_subscription_id = g_dbus_connection_signal_subscribe (manager->priv->connection, name_owner, NULL, /* interface */ NULL, /* member */ NULL, /* path - TODO: really want wildcard support here */ NULL, /* arg0 */ G_DBUS_SIGNAL_FLAGS_NONE | G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, signal_cb, manager, NULL); /* user_data_free_func */ } else { /* TODO: we could report this to the user g_warning ("Message bus daemon does not support path_namespace: %s (%s %d)", error->message, g_quark_to_string (error->domain), error->code); */ g_error_free (error); /* no need to call RemoveMatch when done since it didn't work */ g_free (manager->priv->match_rule); manager->priv->match_rule = NULL; /* Fallback is to subscribe to *all* signals from the name owner which * is rather wasteful. It's probably not a big practical problem because * users typically want all objects that the name owner supplies. */ manager->priv->signal_subscription_id = g_dbus_connection_signal_subscribe (manager->priv->connection, name_owner, NULL, /* interface */ NULL, /* member */ NULL, /* path - TODO: really want wildcard support here */ NULL, /* arg0 */ G_DBUS_SIGNAL_FLAGS_NONE, signal_cb, manager, NULL); /* user_data_free_func */ } } static void maybe_unsubscribe_signals (GDBusObjectManagerClient *manager) { g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager)); if (manager->priv->signal_subscription_id > 0) { g_dbus_connection_signal_unsubscribe (manager->priv->connection, manager->priv->signal_subscription_id); manager->priv->signal_subscription_id = 0; } if (manager->priv->match_rule != NULL) { /* Since the AddMatch call succeeded this is guaranteed to not * fail - therefore, don't bother checking the return value */ g_dbus_connection_call (manager->priv->connection, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "RemoveMatch", g_variant_new ("(s)", manager->priv->match_rule), NULL, /* reply_type */ G_DBUS_CALL_FLAGS_NONE, -1, /* default timeout */ NULL, /* GCancellable */ NULL, /* GAsyncReadyCallback */ NULL); /* user data */ g_free (manager->priv->match_rule); manager->priv->match_rule = NULL; } } /* ---------------------------------------------------------------------------------------------------- */ static GWeakRef * weak_ref_new (GObject *object) { GWeakRef *weak_ref = g_new0 (GWeakRef, 1); g_weak_ref_init (weak_ref, object); return g_steal_pointer (&weak_ref); } static void weak_ref_free (GWeakRef *weak_ref) { g_weak_ref_clear (weak_ref); g_free (weak_ref); } static void on_get_managed_objects_finish (GObject *source, GAsyncResult *result, gpointer user_data) { GDBusProxy *proxy = G_DBUS_PROXY (source); GWeakRef *manager_weak = user_data; GDBusObjectManagerClient *manager; GError *error = NULL; GVariant *value = NULL; gchar *new_name_owner = NULL; value = g_dbus_proxy_call_finish (proxy, result, &error); manager = G_DBUS_OBJECT_MANAGER_CLIENT (g_weak_ref_get (manager_weak)); /* Manager got disposed, nothing to do */ if (manager == NULL) { goto out; } new_name_owner = g_dbus_proxy_get_name_owner (manager->priv->control_proxy); if (value == NULL) { maybe_unsubscribe_signals (manager); if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("Error calling GetManagedObjects() when name owner %s for name %s came back: %s", new_name_owner, manager->priv->name, error->message); } } else { process_get_all_result (manager, value, new_name_owner); } /* do the :name-owner notify *AFTER* emitting ::object-proxy-added signals - this * way the user knows that the signals were emitted because the name owner came back */ g_mutex_lock (&manager->priv->lock); manager->priv->name_owner = g_steal_pointer (&new_name_owner); g_mutex_unlock (&manager->priv->lock); g_object_notify (G_OBJECT (manager), "name-owner"); g_object_unref (manager); out: g_clear_error (&error); g_clear_pointer (&value, g_variant_unref); weak_ref_free (manager_weak); } static void on_notify_g_name_owner (GObject *object, GParamSpec *pspec, gpointer user_data) { GWeakRef *manager_weak = user_data; GDBusObjectManagerClient *manager = NULL; gchar *old_name_owner; gchar *new_name_owner; manager = G_DBUS_OBJECT_MANAGER_CLIENT (g_weak_ref_get (manager_weak)); if (manager == NULL) return; g_mutex_lock (&manager->priv->lock); old_name_owner = manager->priv->name_owner; new_name_owner = g_dbus_proxy_get_name_owner (manager->priv->control_proxy); manager->priv->name_owner = NULL; if (g_strcmp0 (old_name_owner, new_name_owner) != 0) { GList *l; GList *proxies; /* remote manager changed; nuke all local proxies */ proxies = g_hash_table_get_values (manager->priv->map_object_path_to_object_proxy); g_list_foreach (proxies, (GFunc) g_object_ref, NULL); g_hash_table_remove_all (manager->priv->map_object_path_to_object_proxy); g_mutex_unlock (&manager->priv->lock); /* do the :name-owner notify with a NULL name - this way the user knows * the ::object-proxy-removed following is because the name owner went * away */ g_object_notify (G_OBJECT (manager), "name-owner"); for (l = proxies; l != NULL; l = l->next) { GDBusObjectProxy *object_proxy = G_DBUS_OBJECT_PROXY (l->data); g_signal_emit_by_name (manager, "object-removed", object_proxy); } g_list_free_full (proxies, g_object_unref); /* nuke local filter */ maybe_unsubscribe_signals (manager); } else { g_mutex_unlock (&manager->priv->lock); } if (new_name_owner != NULL) { //g_debug ("repopulating for %s", new_name_owner); subscribe_signals (manager, new_name_owner); g_dbus_proxy_call (manager->priv->control_proxy, "GetManagedObjects", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NONE, -1, manager->priv->cancel, on_get_managed_objects_finish, weak_ref_new (G_OBJECT (manager))); } g_free (new_name_owner); g_free (old_name_owner); g_object_unref (manager); } static gboolean initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (initable); gboolean ret; GVariant *value; GDBusProxyFlags proxy_flags; ret = FALSE; if (manager->priv->bus_type != G_BUS_TYPE_NONE) { g_assert (manager->priv->connection == NULL); manager->priv->connection = g_bus_get_sync (manager->priv->bus_type, cancellable, error); if (manager->priv->connection == NULL) goto out; } proxy_flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES; if (manager->priv->flags & G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START) proxy_flags |= G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START; manager->priv->control_proxy = g_dbus_proxy_new_sync (manager->priv->connection, proxy_flags, NULL, /* GDBusInterfaceInfo* */ manager->priv->name, manager->priv->object_path, "org.freedesktop.DBus.ObjectManager", cancellable, error); if (manager->priv->control_proxy == NULL) goto out; /* Use weak refs here. The @control_proxy will emit its signals in the current * #GMainContext (since we constructed it just above). However, the user may * drop the last external reference to this #GDBusObjectManagerClient in * another thread between a signal being emitted and scheduled in an idle * callback in this #GMainContext, and that idle callback being invoked. We * can’t use a strong reference here, as there’s no * g_dbus_object_manager_client_disconnect() (or similar) method to tell us * when the last external reference to this object has been dropped, so we * can’t break a strong reference count cycle. So use weak refs. */ manager->priv->name_owner_signal_id = g_signal_connect_data (G_OBJECT (manager->priv->control_proxy), "notify::g-name-owner", G_CALLBACK (on_notify_g_name_owner), weak_ref_new (G_OBJECT (manager)), (GClosureNotify) weak_ref_free, 0 /* flags */); manager->priv->signal_signal_id = g_signal_connect_data (manager->priv->control_proxy, "g-signal", G_CALLBACK (on_control_proxy_g_signal), weak_ref_new (G_OBJECT (manager)), (GClosureNotify) weak_ref_free, 0 /* flags */); manager->priv->name_owner = g_dbus_proxy_get_name_owner (manager->priv->control_proxy); if (manager->priv->name_owner == NULL && manager->priv->name != NULL) { /* it's perfectly fine if there's no name owner.. we're just going to * wait until one is ready */ } else { /* yay, we can get the objects */ subscribe_signals (manager, manager->priv->name_owner); value = g_dbus_proxy_call_sync (manager->priv->control_proxy, "GetManagedObjects", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error); if (value == NULL) { maybe_unsubscribe_signals (manager); g_warn_if_fail (manager->priv->signal_signal_id != 0); g_signal_handler_disconnect (manager->priv->control_proxy, manager->priv->signal_signal_id); manager->priv->signal_signal_id = 0; g_warn_if_fail (manager->priv->name_owner_signal_id != 0); g_signal_handler_disconnect (manager->priv->control_proxy, manager->priv->name_owner_signal_id); manager->priv->name_owner_signal_id = 0; g_object_unref (manager->priv->control_proxy); manager->priv->control_proxy = NULL; goto out; } process_get_all_result (manager, value, manager->priv->name_owner); g_variant_unref (value); } ret = TRUE; out: return ret; } static void initable_iface_init (GInitableIface *initable_iface) { initable_iface->init = initable_init; } static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface) { /* for now, just use default: run GInitable code in thread */ } /* ---------------------------------------------------------------------------------------------------- */ static void add_interfaces (GDBusObjectManagerClient *manager, const gchar *object_path, GVariant *ifaces_and_properties, const gchar *name_owner) { GDBusObjectProxy *op; gboolean added; GVariantIter iter; const gchar *interface_name; GVariant *properties; GList *interface_added_signals, *l; GDBusProxy *interface_proxy; g_return_if_fail (name_owner == NULL || g_dbus_is_unique_name (name_owner)); g_mutex_lock (&manager->priv->lock); interface_added_signals = NULL; added = FALSE; op = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path); if (op == NULL) { GType object_proxy_type; if (manager->priv->get_proxy_type_func != NULL) { object_proxy_type = manager->priv->get_proxy_type_func (manager, object_path, NULL, manager->priv->get_proxy_type_user_data); g_warn_if_fail (g_type_is_a (object_proxy_type, G_TYPE_DBUS_OBJECT_PROXY)); } else { object_proxy_type = G_TYPE_DBUS_OBJECT_PROXY; } op = g_object_new (object_proxy_type, "g-connection", manager->priv->connection, "g-object-path", object_path, NULL); added = TRUE; } g_object_ref (op); g_variant_iter_init (&iter, ifaces_and_properties); while (g_variant_iter_next (&iter, "{&s@a{sv}}", &interface_name, &properties)) { GError *error; GType interface_proxy_type; if (manager->priv->get_proxy_type_func != NULL) { interface_proxy_type = manager->priv->get_proxy_type_func (manager, object_path, interface_name, manager->priv->get_proxy_type_user_data); g_warn_if_fail (g_type_is_a (interface_proxy_type, G_TYPE_DBUS_PROXY)); } else { interface_proxy_type = G_TYPE_DBUS_PROXY; } /* this is fine - there is no blocking IO because we pass DO_NOT_LOAD_PROPERTIES and * DO_NOT_CONNECT_SIGNALS and use a unique name */ error = NULL; interface_proxy = g_initable_new (interface_proxy_type, NULL, /* GCancellable */ &error, "g-connection", manager->priv->connection, "g-flags", G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, "g-name", name_owner, "g-object-path", object_path, "g-interface-name", interface_name, NULL); if (interface_proxy == NULL) { g_warning ("%s: Error constructing proxy for path %s and interface %s: %s", G_STRLOC, object_path, interface_name, error->message); g_error_free (error); } else { GVariantIter property_iter; const gchar *property_name; GVariant *property_value; /* associate the interface proxy with the object */ g_dbus_interface_set_object (G_DBUS_INTERFACE (interface_proxy), G_DBUS_OBJECT (op)); g_variant_iter_init (&property_iter, properties); while (g_variant_iter_next (&property_iter, "{&sv}", &property_name, &property_value)) { g_dbus_proxy_set_cached_property (interface_proxy, property_name, property_value); g_variant_unref (property_value); } _g_dbus_object_proxy_add_interface (op, interface_proxy); if (!added) interface_added_signals = g_list_append (interface_added_signals, g_object_ref (interface_proxy)); g_object_unref (interface_proxy); } g_variant_unref (properties); } if (added) { g_hash_table_insert (manager->priv->map_object_path_to_object_proxy, g_strdup (object_path), op); } g_mutex_unlock (&manager->priv->lock); /* now that we don't hold the lock any more, emit signals */ g_object_ref (manager); for (l = interface_added_signals; l != NULL; l = l->next) { interface_proxy = G_DBUS_PROXY (l->data); g_signal_emit_by_name (manager, "interface-added", op, interface_proxy); g_object_unref (interface_proxy); } g_list_free (interface_added_signals); if (added) g_signal_emit_by_name (manager, "object-added", op); g_object_unref (manager); g_object_unref (op); } static void remove_interfaces (GDBusObjectManagerClient *manager, const gchar *object_path, const gchar *const *interface_names) { GDBusObjectProxy *op; GList *interfaces; guint n; guint num_interfaces; guint num_interfaces_to_remove; g_mutex_lock (&manager->priv->lock); op = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path); if (op == NULL) { g_warning ("%s: Processing InterfaceRemoved signal for path %s but no object proxy exists", G_STRLOC, object_path); g_mutex_unlock (&manager->priv->lock); goto out; } interfaces = g_dbus_object_get_interfaces (G_DBUS_OBJECT (op)); num_interfaces = g_list_length (interfaces); g_list_free_full (interfaces, g_object_unref); num_interfaces_to_remove = g_strv_length ((gchar **) interface_names); /* see if we are going to completety remove the object */ g_object_ref (manager); if (num_interfaces_to_remove == num_interfaces) { g_object_ref (op); g_warn_if_fail (g_hash_table_remove (manager->priv->map_object_path_to_object_proxy, object_path)); g_mutex_unlock (&manager->priv->lock); g_signal_emit_by_name (manager, "object-removed", op); g_object_unref (op); } else { g_object_ref (op); g_mutex_unlock (&manager->priv->lock); for (n = 0; interface_names != NULL && interface_names[n] != NULL; n++) { GDBusInterface *interface; interface = g_dbus_object_get_interface (G_DBUS_OBJECT (op), interface_names[n]); _g_dbus_object_proxy_remove_interface (op, interface_names[n]); if (interface != NULL) { g_signal_emit_by_name (manager, "interface-removed", op, interface); g_object_unref (interface); } } g_object_unref (op); } g_object_unref (manager); out: ; } static void process_get_all_result (GDBusObjectManagerClient *manager, GVariant *value, const gchar *name_owner) { GVariant *arg0; const gchar *object_path; GVariant *ifaces_and_properties; GVariantIter iter; g_return_if_fail (name_owner == NULL || g_dbus_is_unique_name (name_owner)); arg0 = g_variant_get_child_value (value, 0); g_variant_iter_init (&iter, arg0); while (g_variant_iter_next (&iter, "{&o@a{sa{sv}}}", &object_path, &ifaces_and_properties)) { add_interfaces (manager, object_path, ifaces_and_properties, name_owner); g_variant_unref (ifaces_and_properties); } g_variant_unref (arg0); } static void on_control_proxy_g_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { GWeakRef *manager_weak = user_data; GDBusObjectManagerClient *manager = NULL; const gchar *object_path; manager = G_DBUS_OBJECT_MANAGER_CLIENT (g_weak_ref_get (manager_weak)); if (manager == NULL) return; //g_debug ("yay, g_signal %s: %s\n", signal_name, g_variant_print (parameters, TRUE)); if (g_strcmp0 (signal_name, "InterfacesAdded") == 0) { GVariant *ifaces_and_properties; g_variant_get (parameters, "(&o@a{sa{sv}})", &object_path, &ifaces_and_properties); add_interfaces (manager, object_path, ifaces_and_properties, manager->priv->name_owner); g_variant_unref (ifaces_and_properties); } else if (g_strcmp0 (signal_name, "InterfacesRemoved") == 0) { const gchar **ifaces; g_variant_get (parameters, "(&o^a&s)", &object_path, &ifaces); remove_interfaces (manager, object_path, ifaces); g_free (ifaces); } g_object_unref (manager); } /* ---------------------------------------------------------------------------------------------------- */ static const gchar * g_dbus_object_manager_client_get_object_path (GDBusObjectManager *_manager) { GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (_manager); return manager->priv->object_path; } static GDBusObject * g_dbus_object_manager_client_get_object (GDBusObjectManager *_manager, const gchar *object_path) { GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (_manager); GDBusObject *ret; g_mutex_lock (&manager->priv->lock); ret = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path); if (ret != NULL) g_object_ref (ret); g_mutex_unlock (&manager->priv->lock); return ret; } static GDBusInterface * g_dbus_object_manager_client_get_interface (GDBusObjectManager *_manager, const gchar *object_path, const gchar *interface_name) { GDBusInterface *ret; GDBusObject *object; ret = NULL; object = g_dbus_object_manager_get_object (_manager, object_path); if (object == NULL) goto out; ret = g_dbus_object_get_interface (object, interface_name); g_object_unref (object); out: return ret; } static GList * g_dbus_object_manager_client_get_objects (GDBusObjectManager *_manager) { GDBusObjectManagerClient *manager = G_DBUS_OBJECT_MANAGER_CLIENT (_manager); GList *ret; g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_CLIENT (manager), NULL); g_mutex_lock (&manager->priv->lock); ret = g_hash_table_get_values (manager->priv->map_object_path_to_object_proxy); g_list_foreach (ret, (GFunc) g_object_ref, NULL); g_mutex_unlock (&manager->priv->lock); return ret; } static void dbus_object_manager_interface_init (GDBusObjectManagerIface *iface) { iface->get_object_path = g_dbus_object_manager_client_get_object_path; iface->get_objects = g_dbus_object_manager_client_get_objects; iface->get_object = g_dbus_object_manager_client_get_object; iface->get_interface = g_dbus_object_manager_client_get_interface; } /* ---------------------------------------------------------------------------------------------------- */