glib/gio/gdbusobjectmanagerclient.c
Simon McVittie 7c609f8142 gdbus: Use symbolic constants for interfaces from dbus-specification
Most D-Bus interfaces are domain-specific, but these interfaces from the
D-Bus Specification are intended to be commonly used in any context for
which they are found to be appropriate.

Most of these use `gdbusprivate.h`. One exception is that
`gio/tests/gdbus-example-*` redefine the constants locally: due to these
files' dual role as part of the unit tests and as sample code, it seems
desirable to ensure that they can still be compiled outside GLib.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-05-16 22:52:23 +01:00

1859 lines
72 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* GDBus - GLib D-Bus Library
*
* Copyright (C) 2008-2010 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* 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 <http://www.gnu.org/licenses/>.
*
* Author: David Zeuthen <davidz@redhat.com>
*/
#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"
/**
* GDBusObjectManagerClient:
*
* `GDBusObjectManagerClient` is used to create, monitor and delete object
* proxies for remote objects exported by a [class@Gio.DBusObjectManagerServer]
* (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 [signal@Gio.DBusObjectManager::object-added] and
* [signal@Gio.DBusObjectManager::object-removed signals] and inspect the
* [class@Gio.DBusObjectProxy] objects returned by
* [method@Gio.DBusObjectManager.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. Its 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. [method@Gio.DBusObjectManager.get_objects] returns the empty list) and
* the [property@Gio.DBusObjectManagerClient: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 [signal@GObject.Object::notify]
* signal to watch for changes on the
* [property@Gio.DBusObjectManagerClient:name-owner] property. When the name
* owner vanishes, the behavior is that
* [property@Gio.DBusObjectManagerClient:name-owner] is set to `NULL` (this
* includes emission of the [signal@GObject.Object::notify] signal) and then
* [signal@Gio.DBusObjectManager::object-removed] signals are synthesized
* for all currently existing object proxies. Since
* [property@Gio.DBusObjectManagerClient: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
* [iface@Gio.DBusObjectManager]. Similarly, when a new name owner appears,
* [signal@Gio.DBusObjectManager::object-added] signals are synthesized
* while [property@Gio.DBusObjectManagerClient:name-owner] is still `NULL`. Only
* when all object proxies have been added, the
* [property@Gio.DBusObjectManagerClient:name-owner] is set to the new name
* owner (this includes emission of the [signal@GObject.Object::notify] signal).
* Furthermore, you are guaranteed that
* [property@Gio.DBusObjectManagerClient: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
* [class@Gio.DBusProxy] instances. All signals (including the
* `org.freedesktop.DBus.Properties::PropertiesChanged` signal)
* delivered to [class@Gio.DBusProxy] 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
* [class@Gio.DBusObjectProxy] and [class@Gio.DBusProxy] objects, the
* [signal@Gio.DBusObject::interface-added],
* [signal@Gio.DBusObject::interface-removed],
* [signal@Gio.DBusProxy::g-properties-changed] and
* [signal@Gio.DBusProxy::g-signal] signals
* are also emitted on the `GDBusObjectManagerClient` instance managing these
* objects. The signals emitted are
* [signal@Gio.DBusObjectManager::interface-added],
* [signal@Gio.DBusObjectManager::interface-removed],
* [signal@Gio.DBusObjectManagerClient::interface-proxy-properties-changed] and
* [signal@Gio.DBusObjectManagerClient::interface-proxy-signal].
*
* Note that all callbacks and signals are emitted in the
* thread-default main context (see
* [method@GLib.MainContext.push_thread_default]) that the
* `GDBusObjectManagerClient` object was constructed in. Additionally, the
* [class@Gio.DBusObjectProxy] and [class@Gio.DBusProxy] objects
* originating from the `GDBusObjectManagerClient` object will be created in
* the same context and, consequently, will deliver signals in the
* same main loop.
*
* Since: 2.30
*/
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", NULL, NULL,
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", NULL, NULL,
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", NULL, NULL,
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", NULL, NULL,
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", NULL, NULL,
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", NULL, NULL,
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", NULL, NULL,
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", NULL, NULL,
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", NULL, NULL,
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, DBUS_INTERFACE_PROPERTIES) == 0)
{
if (g_strcmp0 (signal_name, "PropertiesChanged") == 0)
{
const gchar *properties_interface_name;
GVariant *changed_properties;
const gchar **invalidated_properties;
g_variant_get (parameters,
"(&s@a{sv}^a&s)",
&properties_interface_name,
&changed_properties,
&invalidated_properties);
interface = g_dbus_object_get_interface (G_DBUS_OBJECT (object_proxy), properties_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,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_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,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_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)
{
GPtrArray *proxies;
/* remote manager changed; nuke all local proxies */
proxies = g_hash_table_steal_all_values (
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 (guint i = 0; i < proxies->len; ++i)
{
GDBusObjectProxy *object_proxy =
G_DBUS_OBJECT_PROXY (g_ptr_array_index (proxies, i));
g_signal_emit_by_name (manager, "object-removed", object_proxy);
}
g_clear_pointer (&proxies, g_ptr_array_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,
DBUS_INTERFACE_OBJECT_MANAGER,
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
* cant use a strong reference here, as theres 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
* cant 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,
G_CONNECT_DEFAULT);
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,
G_CONNECT_DEFAULT);
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_debug ("%s: Processing InterfaceRemoved signal for path %s but no object proxy exists",
G_STRLOC,
object_path);
g_mutex_unlock (&manager->priv->lock);
return;
}
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);
}
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;
}
/* ---------------------------------------------------------------------------------------------------- */