mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-09 02:46:16 +01:00
7c609f8142
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>
1190 lines
40 KiB
C
1190 lines
40 KiB
C
/* 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 "gdbusobjectmanager.h"
|
|
#include "gdbusobjectmanagerserver.h"
|
|
#include "gdbusobject.h"
|
|
#include "gdbusobjectskeleton.h"
|
|
#include "gdbusinterfaceskeleton.h"
|
|
#include "gdbusconnection.h"
|
|
#include "gdbusintrospection.h"
|
|
#include "gdbusmethodinvocation.h"
|
|
#include "gdbuserror.h"
|
|
|
|
#include "gioerror.h"
|
|
|
|
#include "gdbusprivate.h"
|
|
|
|
#include "glibintl.h"
|
|
|
|
/**
|
|
* GDBusObjectManagerServer:
|
|
*
|
|
* `GDBusObjectManagerServer` is used to export [iface@Gio.DBusObject] instances
|
|
* using the standardized
|
|
* [`org.freedesktop.DBus.ObjectManager`](http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager)
|
|
* interface. For example, remote D-Bus clients can get all objects
|
|
* and properties in a single call. Additionally, any change in the
|
|
* object hierarchy is broadcast using signals. This means that D-Bus
|
|
* clients can keep caches up to date by only listening to D-Bus
|
|
* signals.
|
|
*
|
|
* The recommended path to export an object manager at is the path form of the
|
|
* well-known name of a D-Bus service, or below. For example, if a D-Bus service
|
|
* is available at the well-known name `net.example.ExampleService1`, the object
|
|
* manager should typically be exported at `/net/example/ExampleService1`, or
|
|
* below (to allow for multiple object managers in a service).
|
|
*
|
|
* It is supported, but not recommended, to export an object manager at the root
|
|
* path, `/`.
|
|
*
|
|
* See [class@Gio.DBusObjectManagerClient] for the client-side code that is
|
|
* intended to be used with `GDBusObjectManagerServer` or any D-Bus
|
|
* object implementing the `org.freedesktop.DBus.ObjectManager` interface.
|
|
*
|
|
* Since: 2.30
|
|
*/
|
|
|
|
typedef struct
|
|
{
|
|
GDBusObjectSkeleton *object;
|
|
GDBusObjectManagerServer *manager;
|
|
GHashTable *map_iface_name_to_iface;
|
|
gboolean exported;
|
|
} RegistrationData;
|
|
|
|
static void registration_data_free (RegistrationData *data);
|
|
|
|
static void export_all (GDBusObjectManagerServer *manager);
|
|
static void unexport_all (GDBusObjectManagerServer *manager, gboolean only_manager);
|
|
|
|
static void g_dbus_object_manager_server_emit_interfaces_added (GDBusObjectManagerServer *manager,
|
|
RegistrationData *data,
|
|
const gchar *const *interfaces,
|
|
const gchar *object_path);
|
|
|
|
static void g_dbus_object_manager_server_emit_interfaces_removed (GDBusObjectManagerServer *manager,
|
|
RegistrationData *data,
|
|
const gchar *const *interfaces);
|
|
|
|
static gboolean g_dbus_object_manager_server_unexport_unlocked (GDBusObjectManagerServer *manager,
|
|
const gchar *object_path);
|
|
|
|
struct _GDBusObjectManagerServerPrivate
|
|
{
|
|
GMutex lock;
|
|
GDBusConnection *connection;
|
|
gchar *object_path;
|
|
gchar *object_path_ending_in_slash;
|
|
GHashTable *map_object_path_to_data;
|
|
guint manager_reg_id;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CONNECTION,
|
|
PROP_OBJECT_PATH
|
|
};
|
|
|
|
static void dbus_object_manager_interface_init (GDBusObjectManagerIface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GDBusObjectManagerServer, g_dbus_object_manager_server, G_TYPE_OBJECT,
|
|
G_ADD_PRIVATE (GDBusObjectManagerServer)
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_DBUS_OBJECT_MANAGER, dbus_object_manager_interface_init))
|
|
|
|
static void g_dbus_object_manager_server_constructed (GObject *object);
|
|
|
|
static void
|
|
g_dbus_object_manager_server_finalize (GObject *object)
|
|
{
|
|
GDBusObjectManagerServer *manager = G_DBUS_OBJECT_MANAGER_SERVER (object);
|
|
|
|
if (manager->priv->connection != NULL)
|
|
{
|
|
unexport_all (manager, TRUE);
|
|
g_object_unref (manager->priv->connection);
|
|
}
|
|
g_hash_table_unref (manager->priv->map_object_path_to_data);
|
|
g_free (manager->priv->object_path);
|
|
g_free (manager->priv->object_path_ending_in_slash);
|
|
|
|
g_mutex_clear (&manager->priv->lock);
|
|
|
|
if (G_OBJECT_CLASS (g_dbus_object_manager_server_parent_class)->finalize != NULL)
|
|
G_OBJECT_CLASS (g_dbus_object_manager_server_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
g_dbus_object_manager_server_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GDBusObjectManagerServer *manager = G_DBUS_OBJECT_MANAGER_SERVER (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CONNECTION:
|
|
g_mutex_lock (&manager->priv->lock);
|
|
g_value_set_object (value, manager->priv->connection);
|
|
g_mutex_unlock (&manager->priv->lock);
|
|
break;
|
|
|
|
case PROP_OBJECT_PATH:
|
|
g_value_set_string (value, g_dbus_object_manager_get_object_path (G_DBUS_OBJECT_MANAGER (manager)));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
g_dbus_object_manager_server_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GDBusObjectManagerServer *manager = G_DBUS_OBJECT_MANAGER_SERVER (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CONNECTION:
|
|
g_dbus_object_manager_server_set_connection (manager, g_value_get_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);
|
|
if (g_str_equal (manager->priv->object_path, "/"))
|
|
manager->priv->object_path_ending_in_slash = g_strdup (manager->priv->object_path);
|
|
else
|
|
manager->priv->object_path_ending_in_slash = g_strdup_printf ("%s/", manager->priv->object_path);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
g_dbus_object_manager_server_class_init (GDBusObjectManagerServerClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->finalize = g_dbus_object_manager_server_finalize;
|
|
gobject_class->constructed = g_dbus_object_manager_server_constructed;
|
|
gobject_class->set_property = g_dbus_object_manager_server_set_property;
|
|
gobject_class->get_property = g_dbus_object_manager_server_get_property;
|
|
|
|
/**
|
|
* GDBusObjectManagerServer:connection:
|
|
*
|
|
* The #GDBusConnection to export objects on.
|
|
*
|
|
* 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_STATIC_STRINGS));
|
|
|
|
/**
|
|
* GDBusObjectManagerServer:object-path:
|
|
*
|
|
* The object path to register the manager object at.
|
|
*
|
|
* 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));
|
|
}
|
|
|
|
static void
|
|
g_dbus_object_manager_server_init (GDBusObjectManagerServer *manager)
|
|
{
|
|
manager->priv = g_dbus_object_manager_server_get_instance_private (manager);
|
|
g_mutex_init (&manager->priv->lock);
|
|
manager->priv->map_object_path_to_data = g_hash_table_new_full (g_str_hash,
|
|
g_str_equal,
|
|
g_free,
|
|
(GDestroyNotify) registration_data_free);
|
|
}
|
|
|
|
/**
|
|
* g_dbus_object_manager_server_new:
|
|
* @object_path: The object path to export the manager object at.
|
|
*
|
|
* Creates a new #GDBusObjectManagerServer object.
|
|
*
|
|
* The returned server isn't yet exported on any connection. To do so,
|
|
* use g_dbus_object_manager_server_set_connection(). Normally you
|
|
* want to export all of your objects before doing so to avoid
|
|
* [InterfacesAdded](http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager)
|
|
* signals being emitted.
|
|
*
|
|
* Returns: A #GDBusObjectManagerServer object. Free with g_object_unref().
|
|
*
|
|
* Since: 2.30
|
|
*/
|
|
GDBusObjectManagerServer *
|
|
g_dbus_object_manager_server_new (const gchar *object_path)
|
|
{
|
|
g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
|
|
return G_DBUS_OBJECT_MANAGER_SERVER (g_object_new (G_TYPE_DBUS_OBJECT_MANAGER_SERVER,
|
|
"object-path", object_path,
|
|
NULL));
|
|
}
|
|
|
|
/**
|
|
* g_dbus_object_manager_server_set_connection:
|
|
* @manager: A #GDBusObjectManagerServer.
|
|
* @connection: (nullable): A #GDBusConnection or %NULL.
|
|
*
|
|
* Exports all objects managed by @manager on @connection. If
|
|
* @connection is %NULL, stops exporting objects.
|
|
*/
|
|
void
|
|
g_dbus_object_manager_server_set_connection (GDBusObjectManagerServer *manager,
|
|
GDBusConnection *connection)
|
|
{
|
|
g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER_SERVER (manager));
|
|
g_return_if_fail (connection == NULL || G_IS_DBUS_CONNECTION (connection));
|
|
|
|
g_mutex_lock (&manager->priv->lock);
|
|
|
|
if (manager->priv->connection == connection)
|
|
{
|
|
g_mutex_unlock (&manager->priv->lock);
|
|
goto out;
|
|
}
|
|
|
|
if (manager->priv->connection != NULL)
|
|
{
|
|
unexport_all (manager, FALSE);
|
|
g_object_unref (manager->priv->connection);
|
|
manager->priv->connection = NULL;
|
|
}
|
|
|
|
manager->priv->connection = connection != NULL ? g_object_ref (connection) : NULL;
|
|
if (manager->priv->connection != NULL)
|
|
export_all (manager);
|
|
|
|
g_mutex_unlock (&manager->priv->lock);
|
|
|
|
g_object_notify (G_OBJECT (manager), "connection");
|
|
out:
|
|
;
|
|
}
|
|
|
|
/**
|
|
* g_dbus_object_manager_server_get_connection:
|
|
* @manager: A #GDBusObjectManagerServer
|
|
*
|
|
* Gets the #GDBusConnection used by @manager.
|
|
*
|
|
* Returns: (transfer full) (nullable): A #GDBusConnection object or %NULL if
|
|
* @manager isn't exported on a connection. The returned object should
|
|
* be freed with g_object_unref().
|
|
*
|
|
* Since: 2.30
|
|
*/
|
|
GDBusConnection *
|
|
g_dbus_object_manager_server_get_connection (GDBusObjectManagerServer *manager)
|
|
{
|
|
GDBusConnection *ret;
|
|
g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_SERVER (manager), NULL);
|
|
g_mutex_lock (&manager->priv->lock);
|
|
ret = manager->priv->connection != NULL ? g_object_ref (manager->priv->connection) : NULL;
|
|
g_mutex_unlock (&manager->priv->lock);
|
|
return ret;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
static void
|
|
registration_data_export_interface (RegistrationData *data,
|
|
GDBusInterfaceSkeleton *interface_skeleton,
|
|
const gchar *object_path)
|
|
{
|
|
GDBusInterfaceInfo *info;
|
|
GError *error;
|
|
|
|
info = g_dbus_interface_skeleton_get_info (interface_skeleton);
|
|
error = NULL;
|
|
if (data->manager->priv->connection != NULL)
|
|
{
|
|
if (!g_dbus_interface_skeleton_export (interface_skeleton,
|
|
data->manager->priv->connection,
|
|
object_path,
|
|
&error))
|
|
{
|
|
g_warning ("%s: Error registering object at %s with interface %s: %s",
|
|
G_STRLOC,
|
|
object_path,
|
|
info->name,
|
|
error->message);
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
|
|
g_assert (g_hash_table_lookup (data->map_iface_name_to_iface, info->name) == NULL);
|
|
g_hash_table_insert (data->map_iface_name_to_iface,
|
|
info->name,
|
|
g_object_ref (interface_skeleton));
|
|
|
|
/* if we are already exported, then... */
|
|
if (data->exported)
|
|
{
|
|
const gchar *interfaces[2];
|
|
/* emit InterfacesAdded on the ObjectManager object */
|
|
interfaces[0] = info->name;
|
|
interfaces[1] = NULL;
|
|
g_dbus_object_manager_server_emit_interfaces_added (data->manager, data, interfaces, object_path);
|
|
}
|
|
}
|
|
|
|
static void
|
|
registration_data_unexport_interface (RegistrationData *data,
|
|
GDBusInterfaceSkeleton *interface_skeleton)
|
|
{
|
|
GDBusInterfaceInfo *info;
|
|
GDBusInterfaceSkeleton *iface;
|
|
|
|
info = g_dbus_interface_skeleton_get_info (interface_skeleton);
|
|
iface = g_hash_table_lookup (data->map_iface_name_to_iface, info->name);
|
|
g_assert (iface != NULL);
|
|
|
|
if (data->manager->priv->connection != NULL)
|
|
g_dbus_interface_skeleton_unexport (iface);
|
|
|
|
g_warn_if_fail (g_hash_table_remove (data->map_iface_name_to_iface, info->name));
|
|
|
|
/* if we are already exported, then... */
|
|
if (data->exported)
|
|
{
|
|
const gchar *interfaces[2];
|
|
/* emit InterfacesRemoved on the ObjectManager object */
|
|
interfaces[0] = info->name;
|
|
interfaces[1] = NULL;
|
|
g_dbus_object_manager_server_emit_interfaces_removed (data->manager, data, interfaces);
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
static void
|
|
on_interface_added (GDBusObject *object,
|
|
GDBusInterface *interface,
|
|
gpointer user_data)
|
|
{
|
|
RegistrationData *data = user_data;
|
|
const gchar *object_path;
|
|
g_mutex_lock (&data->manager->priv->lock);
|
|
object_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (data->object));
|
|
registration_data_export_interface (data, G_DBUS_INTERFACE_SKELETON (interface), object_path);
|
|
g_mutex_unlock (&data->manager->priv->lock);
|
|
}
|
|
|
|
static void
|
|
on_interface_removed (GDBusObject *object,
|
|
GDBusInterface *interface,
|
|
gpointer user_data)
|
|
{
|
|
RegistrationData *data = user_data;
|
|
g_mutex_lock (&data->manager->priv->lock);
|
|
registration_data_unexport_interface (data, G_DBUS_INTERFACE_SKELETON (interface));
|
|
g_mutex_unlock (&data->manager->priv->lock);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
|
|
static void
|
|
registration_data_free (RegistrationData *data)
|
|
{
|
|
GHashTableIter iter;
|
|
GDBusInterfaceSkeleton *iface;
|
|
|
|
data->exported = FALSE;
|
|
|
|
g_hash_table_iter_init (&iter, data->map_iface_name_to_iface);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer) &iface))
|
|
{
|
|
if (data->manager->priv->connection != NULL)
|
|
g_dbus_interface_skeleton_unexport (iface);
|
|
}
|
|
|
|
g_signal_handlers_disconnect_by_func (data->object, G_CALLBACK (on_interface_added), data);
|
|
g_signal_handlers_disconnect_by_func (data->object, G_CALLBACK (on_interface_removed), data);
|
|
g_object_unref (data->object);
|
|
g_hash_table_destroy (data->map_iface_name_to_iface);
|
|
g_free (data);
|
|
}
|
|
|
|
/* Validate whether an object path is valid as a child of the manager. According
|
|
* to the specification:
|
|
* https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
|
|
* this means that:
|
|
* > All returned object paths are children of the object path implementing this
|
|
* > interface, i.e. their object paths start with the ObjectManager's object
|
|
* > path plus '/'
|
|
*
|
|
* For example, if the manager is at `/org/gnome/Example`, children will be
|
|
* `/org/gnome/Example/(.+)`.
|
|
*
|
|
* It is permissible (but not encouraged) for the manager to be at `/`. If so,
|
|
* children will be `/(.+)`.
|
|
*/
|
|
static gboolean
|
|
is_valid_child_object_path (GDBusObjectManagerServer *manager,
|
|
const gchar *child_object_path)
|
|
{
|
|
/* Historically GDBus accepted @child_object_paths at `/` if the @manager
|
|
* itself is also at `/". This is not spec-compliant, but making GDBus enforce
|
|
* the spec more strictly would be an incompatible change.
|
|
*
|
|
* See https://gitlab.gnome.org/GNOME/glib/-/issues/2500 */
|
|
g_warn_if_fail (!g_str_equal (child_object_path, manager->priv->object_path_ending_in_slash));
|
|
|
|
return g_str_has_prefix (child_object_path, manager->priv->object_path_ending_in_slash);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
static void
|
|
g_dbus_object_manager_server_export_unlocked (GDBusObjectManagerServer *manager,
|
|
GDBusObjectSkeleton *object,
|
|
const gchar *object_path)
|
|
{
|
|
RegistrationData *data;
|
|
GList *existing_interfaces;
|
|
GList *l;
|
|
GPtrArray *interface_names;
|
|
|
|
g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER_SERVER (manager));
|
|
g_return_if_fail (G_IS_DBUS_OBJECT (object));
|
|
g_return_if_fail (is_valid_child_object_path (manager, object_path));
|
|
|
|
interface_names = g_ptr_array_new ();
|
|
|
|
data = g_hash_table_lookup (manager->priv->map_object_path_to_data, object_path);
|
|
if (data != NULL)
|
|
g_dbus_object_manager_server_unexport_unlocked (manager, object_path);
|
|
|
|
data = g_new0 (RegistrationData, 1);
|
|
data->object = g_object_ref (object);
|
|
data->manager = manager;
|
|
data->map_iface_name_to_iface = g_hash_table_new_full (g_str_hash,
|
|
g_str_equal,
|
|
NULL,
|
|
(GDestroyNotify) g_object_unref);
|
|
|
|
g_signal_connect (object,
|
|
"interface-added",
|
|
G_CALLBACK (on_interface_added),
|
|
data);
|
|
g_signal_connect (object,
|
|
"interface-removed",
|
|
G_CALLBACK (on_interface_removed),
|
|
data);
|
|
|
|
/* Register all known interfaces - note that data->exported is FALSE so
|
|
* we don't emit any InterfacesAdded signals.
|
|
*/
|
|
existing_interfaces = g_dbus_object_get_interfaces (G_DBUS_OBJECT (object));
|
|
for (l = existing_interfaces; l != NULL; l = l->next)
|
|
{
|
|
GDBusInterfaceSkeleton *interface_skeleton = G_DBUS_INTERFACE_SKELETON (l->data);
|
|
registration_data_export_interface (data, interface_skeleton, object_path);
|
|
g_ptr_array_add (interface_names, g_dbus_interface_skeleton_get_info (interface_skeleton)->name);
|
|
}
|
|
g_list_free_full (existing_interfaces, g_object_unref);
|
|
g_ptr_array_add (interface_names, NULL);
|
|
|
|
data->exported = TRUE;
|
|
|
|
/* now emit InterfacesAdded() for all the interfaces */
|
|
g_dbus_object_manager_server_emit_interfaces_added (manager, data, (const gchar *const *) interface_names->pdata, object_path);
|
|
g_ptr_array_unref (interface_names);
|
|
|
|
g_hash_table_insert (manager->priv->map_object_path_to_data,
|
|
g_strdup (object_path),
|
|
data);
|
|
}
|
|
|
|
/**
|
|
* g_dbus_object_manager_server_export:
|
|
* @manager: A #GDBusObjectManagerServer.
|
|
* @object: A #GDBusObjectSkeleton.
|
|
*
|
|
* Exports @object on @manager.
|
|
*
|
|
* If there is already a #GDBusObject exported at the object path,
|
|
* then the old object is removed.
|
|
*
|
|
* The object path for @object must be in the hierarchy rooted by the
|
|
* object path for @manager.
|
|
*
|
|
* Note that @manager will take a reference on @object for as long as
|
|
* it is exported.
|
|
*
|
|
* Since: 2.30
|
|
*/
|
|
void
|
|
g_dbus_object_manager_server_export (GDBusObjectManagerServer *manager,
|
|
GDBusObjectSkeleton *object)
|
|
{
|
|
g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER_SERVER (manager));
|
|
g_mutex_lock (&manager->priv->lock);
|
|
g_dbus_object_manager_server_export_unlocked (manager, object,
|
|
g_dbus_object_get_object_path (G_DBUS_OBJECT (object)));
|
|
g_mutex_unlock (&manager->priv->lock);
|
|
}
|
|
|
|
/**
|
|
* g_dbus_object_manager_server_export_uniquely:
|
|
* @manager: A #GDBusObjectManagerServer.
|
|
* @object: An object.
|
|
*
|
|
* Like g_dbus_object_manager_server_export() but appends a string of
|
|
* the form _N (with N being a natural number) to @object's object path
|
|
* if an object with the given path already exists. As such, the
|
|
* #GDBusObjectProxy:g-object-path property of @object may be modified.
|
|
*
|
|
* Since: 2.30
|
|
*/
|
|
void
|
|
g_dbus_object_manager_server_export_uniquely (GDBusObjectManagerServer *manager,
|
|
GDBusObjectSkeleton *object)
|
|
{
|
|
const gchar *orig_object_path;
|
|
gchar *object_path;
|
|
guint count;
|
|
gboolean modified;
|
|
|
|
orig_object_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (object));
|
|
|
|
g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER_SERVER (manager));
|
|
g_return_if_fail (G_IS_DBUS_OBJECT (object));
|
|
g_return_if_fail (is_valid_child_object_path (manager, orig_object_path));
|
|
|
|
g_mutex_lock (&manager->priv->lock);
|
|
|
|
object_path = g_strdup (orig_object_path);
|
|
count = 1;
|
|
modified = FALSE;
|
|
while (TRUE)
|
|
{
|
|
RegistrationData *data;
|
|
data = g_hash_table_lookup (manager->priv->map_object_path_to_data, object_path);
|
|
if (data == NULL)
|
|
{
|
|
break;
|
|
}
|
|
g_free (object_path);
|
|
object_path = g_strdup_printf ("%s_%d", orig_object_path, count++);
|
|
modified = TRUE;
|
|
}
|
|
|
|
g_dbus_object_manager_server_export_unlocked (manager, object, object_path);
|
|
|
|
g_mutex_unlock (&manager->priv->lock);
|
|
|
|
if (modified)
|
|
g_dbus_object_skeleton_set_object_path (G_DBUS_OBJECT_SKELETON (object), object_path);
|
|
|
|
g_free (object_path);
|
|
|
|
}
|
|
|
|
/**
|
|
* g_dbus_object_manager_server_is_exported:
|
|
* @manager: A #GDBusObjectManagerServer.
|
|
* @object: An object.
|
|
*
|
|
* Returns whether @object is currently exported on @manager.
|
|
*
|
|
* Returns: %TRUE if @object is exported
|
|
*
|
|
* Since: 2.34
|
|
**/
|
|
gboolean
|
|
g_dbus_object_manager_server_is_exported (GDBusObjectManagerServer *manager,
|
|
GDBusObjectSkeleton *object)
|
|
{
|
|
RegistrationData *data = NULL;
|
|
const gchar *object_path;
|
|
gboolean object_is_exported;
|
|
|
|
g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_SERVER (manager), FALSE);
|
|
g_return_val_if_fail (G_IS_DBUS_OBJECT (object), FALSE);
|
|
|
|
g_mutex_lock (&manager->priv->lock);
|
|
|
|
object_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (object));
|
|
if (object_path != NULL)
|
|
data = g_hash_table_lookup (manager->priv->map_object_path_to_data, object_path);
|
|
object_is_exported = (data != NULL);
|
|
|
|
g_mutex_unlock (&manager->priv->lock);
|
|
|
|
return object_is_exported;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
static gboolean
|
|
g_dbus_object_manager_server_unexport_unlocked (GDBusObjectManagerServer *manager,
|
|
const gchar *object_path)
|
|
{
|
|
RegistrationData *data;
|
|
gboolean ret;
|
|
|
|
g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_SERVER (manager), FALSE);
|
|
g_return_val_if_fail (g_variant_is_object_path (object_path), FALSE);
|
|
g_return_val_if_fail (is_valid_child_object_path (manager, object_path), FALSE);
|
|
|
|
ret = FALSE;
|
|
|
|
data = g_hash_table_lookup (manager->priv->map_object_path_to_data, object_path);
|
|
if (data != NULL)
|
|
{
|
|
GPtrArray *interface_names;
|
|
GHashTableIter iter;
|
|
const gchar *iface_name;
|
|
|
|
interface_names = g_ptr_array_new ();
|
|
g_hash_table_iter_init (&iter, data->map_iface_name_to_iface);
|
|
while (g_hash_table_iter_next (&iter, (gpointer) &iface_name, NULL))
|
|
g_ptr_array_add (interface_names, (gpointer) iface_name);
|
|
g_ptr_array_add (interface_names, NULL);
|
|
/* now emit InterfacesRemoved() for all the interfaces */
|
|
g_dbus_object_manager_server_emit_interfaces_removed (manager, data, (const gchar *const *) interface_names->pdata);
|
|
g_ptr_array_unref (interface_names);
|
|
|
|
g_hash_table_remove (manager->priv->map_object_path_to_data, object_path);
|
|
ret = TRUE;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* g_dbus_object_manager_server_unexport:
|
|
* @manager: A #GDBusObjectManagerServer.
|
|
* @object_path: An object path.
|
|
*
|
|
* If @manager has an object at @path, removes the object. Otherwise
|
|
* does nothing.
|
|
*
|
|
* Note that @object_path must be in the hierarchy rooted by the
|
|
* object path for @manager.
|
|
*
|
|
* Returns: %TRUE if object at @object_path was removed, %FALSE otherwise.
|
|
*
|
|
* Since: 2.30
|
|
*/
|
|
gboolean
|
|
g_dbus_object_manager_server_unexport (GDBusObjectManagerServer *manager,
|
|
const gchar *object_path)
|
|
{
|
|
gboolean ret;
|
|
g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_SERVER (manager), FALSE);
|
|
g_mutex_lock (&manager->priv->lock);
|
|
ret = g_dbus_object_manager_server_unexport_unlocked (manager, object_path);
|
|
g_mutex_unlock (&manager->priv->lock);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
static const GDBusArgInfo manager_interfaces_added_signal_info_arg0 =
|
|
{
|
|
-1,
|
|
"object_path",
|
|
"o",
|
|
(GDBusAnnotationInfo**) NULL,
|
|
};
|
|
|
|
static const GDBusArgInfo manager_interfaces_added_signal_info_arg1 =
|
|
{
|
|
-1,
|
|
"interfaces_and_properties",
|
|
"a{sa{sv}}",
|
|
(GDBusAnnotationInfo**) NULL,
|
|
};
|
|
|
|
static const GDBusArgInfo * const manager_interfaces_added_signal_info_arg_pointers[] =
|
|
{
|
|
&manager_interfaces_added_signal_info_arg0,
|
|
&manager_interfaces_added_signal_info_arg1,
|
|
NULL
|
|
};
|
|
|
|
static const GDBusSignalInfo manager_interfaces_added_signal_info =
|
|
{
|
|
-1,
|
|
"InterfacesAdded",
|
|
(GDBusArgInfo**) &manager_interfaces_added_signal_info_arg_pointers,
|
|
(GDBusAnnotationInfo**) NULL
|
|
};
|
|
|
|
/* ---------- */
|
|
|
|
static const GDBusArgInfo manager_interfaces_removed_signal_info_arg0 =
|
|
{
|
|
-1,
|
|
"object_path",
|
|
"o",
|
|
(GDBusAnnotationInfo**) NULL,
|
|
};
|
|
|
|
static const GDBusArgInfo manager_interfaces_removed_signal_info_arg1 =
|
|
{
|
|
-1,
|
|
"interfaces",
|
|
"as",
|
|
(GDBusAnnotationInfo**) NULL,
|
|
};
|
|
|
|
static const GDBusArgInfo * const manager_interfaces_removed_signal_info_arg_pointers[] =
|
|
{
|
|
&manager_interfaces_removed_signal_info_arg0,
|
|
&manager_interfaces_removed_signal_info_arg1,
|
|
NULL
|
|
};
|
|
|
|
static const GDBusSignalInfo manager_interfaces_removed_signal_info =
|
|
{
|
|
-1,
|
|
"InterfacesRemoved",
|
|
(GDBusArgInfo**) &manager_interfaces_removed_signal_info_arg_pointers,
|
|
(GDBusAnnotationInfo**) NULL
|
|
};
|
|
|
|
/* ---------- */
|
|
|
|
static const GDBusSignalInfo * const manager_signal_info_pointers[] =
|
|
{
|
|
&manager_interfaces_added_signal_info,
|
|
&manager_interfaces_removed_signal_info,
|
|
NULL
|
|
};
|
|
|
|
/* ---------- */
|
|
|
|
static const GDBusArgInfo manager_get_all_method_info_out_arg0 =
|
|
{
|
|
-1,
|
|
"object_paths_interfaces_and_properties",
|
|
"a{oa{sa{sv}}}",
|
|
(GDBusAnnotationInfo**) NULL,
|
|
};
|
|
|
|
static const GDBusArgInfo * const manager_get_all_method_info_out_arg_pointers[] =
|
|
{
|
|
&manager_get_all_method_info_out_arg0,
|
|
NULL
|
|
};
|
|
|
|
static const GDBusMethodInfo manager_get_all_method_info =
|
|
{
|
|
-1,
|
|
"GetManagedObjects",
|
|
(GDBusArgInfo**) NULL,
|
|
(GDBusArgInfo**) &manager_get_all_method_info_out_arg_pointers,
|
|
(GDBusAnnotationInfo**) NULL
|
|
};
|
|
|
|
static const GDBusMethodInfo * const manager_method_info_pointers[] =
|
|
{
|
|
&manager_get_all_method_info,
|
|
NULL
|
|
};
|
|
|
|
/* ---------- */
|
|
|
|
static const GDBusInterfaceInfo manager_interface_info =
|
|
{
|
|
-1,
|
|
DBUS_INTERFACE_OBJECT_MANAGER,
|
|
(GDBusMethodInfo **) manager_method_info_pointers,
|
|
(GDBusSignalInfo **) manager_signal_info_pointers,
|
|
(GDBusPropertyInfo **) NULL,
|
|
(GDBusAnnotationInfo **) NULL
|
|
};
|
|
|
|
static void
|
|
manager_method_call (GDBusConnection *connection,
|
|
const gchar *sender,
|
|
const gchar *object_path,
|
|
const gchar *interface_name,
|
|
const gchar *method_name,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation,
|
|
gpointer user_data)
|
|
{
|
|
GDBusObjectManagerServer *manager = G_DBUS_OBJECT_MANAGER_SERVER (user_data);
|
|
GVariantBuilder array_builder;
|
|
GHashTableIter object_iter;
|
|
RegistrationData *data;
|
|
|
|
g_mutex_lock (&manager->priv->lock);
|
|
|
|
if (g_strcmp0 (method_name, "GetManagedObjects") == 0)
|
|
{
|
|
g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("a{oa{sa{sv}}}"));
|
|
g_hash_table_iter_init (&object_iter, manager->priv->map_object_path_to_data);
|
|
while (g_hash_table_iter_next (&object_iter, NULL, (gpointer) &data))
|
|
{
|
|
GVariantBuilder interfaces_builder;
|
|
GHashTableIter interface_iter;
|
|
GDBusInterfaceSkeleton *iface;
|
|
const gchar *iter_object_path;
|
|
|
|
g_variant_builder_init (&interfaces_builder, G_VARIANT_TYPE ("a{sa{sv}}"));
|
|
g_hash_table_iter_init (&interface_iter, data->map_iface_name_to_iface);
|
|
while (g_hash_table_iter_next (&interface_iter, NULL, (gpointer) &iface))
|
|
{
|
|
GVariant *properties = g_dbus_interface_skeleton_get_properties (iface);
|
|
g_variant_builder_add (&interfaces_builder, "{s@a{sv}}",
|
|
g_dbus_interface_skeleton_get_info (iface)->name,
|
|
properties);
|
|
g_variant_unref (properties);
|
|
}
|
|
iter_object_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (data->object));
|
|
g_variant_builder_add (&array_builder,
|
|
"{oa{sa{sv}}}",
|
|
iter_object_path,
|
|
&interfaces_builder);
|
|
}
|
|
|
|
g_dbus_method_invocation_return_value (invocation,
|
|
g_variant_new ("(a{oa{sa{sv}}})",
|
|
&array_builder));
|
|
}
|
|
else
|
|
{
|
|
g_dbus_method_invocation_return_error (invocation,
|
|
G_DBUS_ERROR,
|
|
G_DBUS_ERROR_UNKNOWN_METHOD,
|
|
"Unknown method %s - only GetManagedObjects() is supported",
|
|
method_name);
|
|
}
|
|
g_mutex_unlock (&manager->priv->lock);
|
|
}
|
|
|
|
static const GDBusInterfaceVTable manager_interface_vtable =
|
|
{
|
|
manager_method_call, /* handle_method_call */
|
|
NULL, /* get_property */
|
|
NULL, /* set_property */
|
|
{ 0 }
|
|
};
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
static void
|
|
g_dbus_object_manager_server_constructed (GObject *object)
|
|
{
|
|
GDBusObjectManagerServer *manager = G_DBUS_OBJECT_MANAGER_SERVER (object);
|
|
|
|
if (manager->priv->connection != NULL)
|
|
export_all (manager);
|
|
|
|
if (G_OBJECT_CLASS (g_dbus_object_manager_server_parent_class)->constructed != NULL)
|
|
G_OBJECT_CLASS (g_dbus_object_manager_server_parent_class)->constructed (object);
|
|
}
|
|
|
|
static void
|
|
g_dbus_object_manager_server_emit_interfaces_added (GDBusObjectManagerServer *manager,
|
|
RegistrationData *data,
|
|
const gchar *const *interfaces,
|
|
const gchar *object_path)
|
|
{
|
|
GVariantBuilder array_builder;
|
|
GError *error;
|
|
guint n;
|
|
|
|
if (data->manager->priv->connection == NULL)
|
|
goto out;
|
|
|
|
g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("a{sa{sv}}"));
|
|
for (n = 0; interfaces[n] != NULL; n++)
|
|
{
|
|
GDBusInterfaceSkeleton *iface;
|
|
GVariant *properties;
|
|
|
|
iface = g_hash_table_lookup (data->map_iface_name_to_iface, interfaces[n]);
|
|
g_assert (iface != NULL);
|
|
properties = g_dbus_interface_skeleton_get_properties (iface);
|
|
g_variant_builder_add (&array_builder, "{s@a{sv}}", interfaces[n], properties);
|
|
g_variant_unref (properties);
|
|
}
|
|
|
|
error = NULL;
|
|
g_dbus_connection_emit_signal (data->manager->priv->connection,
|
|
NULL, /* destination_bus_name */
|
|
manager->priv->object_path,
|
|
manager_interface_info.name,
|
|
"InterfacesAdded",
|
|
g_variant_new ("(oa{sa{sv}})",
|
|
object_path,
|
|
&array_builder),
|
|
&error);
|
|
if (error)
|
|
{
|
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED))
|
|
g_warning ("Couldn't emit InterfacesAdded signal: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
out:
|
|
;
|
|
}
|
|
|
|
static void
|
|
g_dbus_object_manager_server_emit_interfaces_removed (GDBusObjectManagerServer *manager,
|
|
RegistrationData *data,
|
|
const gchar *const *interfaces)
|
|
{
|
|
GVariantBuilder array_builder;
|
|
GError *error;
|
|
guint n;
|
|
const gchar *object_path;
|
|
|
|
if (data->manager->priv->connection == NULL)
|
|
goto out;
|
|
|
|
g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("as"));
|
|
for (n = 0; interfaces[n] != NULL; n++)
|
|
g_variant_builder_add (&array_builder, "s", interfaces[n]);
|
|
|
|
error = NULL;
|
|
object_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (data->object));
|
|
g_dbus_connection_emit_signal (data->manager->priv->connection,
|
|
NULL, /* destination_bus_name */
|
|
manager->priv->object_path,
|
|
manager_interface_info.name,
|
|
"InterfacesRemoved",
|
|
g_variant_new ("(oas)",
|
|
object_path,
|
|
&array_builder),
|
|
&error);
|
|
if (error)
|
|
{
|
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED))
|
|
g_warning ("Couldn't emit InterfacesRemoved signal: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
out:
|
|
;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
static GList *
|
|
g_dbus_object_manager_server_get_objects (GDBusObjectManager *_manager)
|
|
{
|
|
GDBusObjectManagerServer *manager = G_DBUS_OBJECT_MANAGER_SERVER (_manager);
|
|
GList *ret;
|
|
GHashTableIter iter;
|
|
RegistrationData *data;
|
|
|
|
g_mutex_lock (&manager->priv->lock);
|
|
|
|
ret = NULL;
|
|
g_hash_table_iter_init (&iter, manager->priv->map_object_path_to_data);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer) &data))
|
|
{
|
|
ret = g_list_prepend (ret, g_object_ref (data->object));
|
|
}
|
|
|
|
g_mutex_unlock (&manager->priv->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const gchar *
|
|
g_dbus_object_manager_server_get_object_path (GDBusObjectManager *_manager)
|
|
{
|
|
GDBusObjectManagerServer *manager = G_DBUS_OBJECT_MANAGER_SERVER (_manager);
|
|
return manager->priv->object_path;
|
|
}
|
|
|
|
static GDBusObject *
|
|
g_dbus_object_manager_server_get_object (GDBusObjectManager *_manager,
|
|
const gchar *object_path)
|
|
{
|
|
GDBusObjectManagerServer *manager = G_DBUS_OBJECT_MANAGER_SERVER (_manager);
|
|
GDBusObject *ret;
|
|
RegistrationData *data;
|
|
|
|
ret = NULL;
|
|
|
|
g_mutex_lock (&manager->priv->lock);
|
|
data = g_hash_table_lookup (manager->priv->map_object_path_to_data, object_path);
|
|
if (data != NULL)
|
|
ret = g_object_ref (G_DBUS_OBJECT (data->object));
|
|
g_mutex_unlock (&manager->priv->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GDBusInterface *
|
|
g_dbus_object_manager_server_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 void
|
|
dbus_object_manager_interface_init (GDBusObjectManagerIface *iface)
|
|
{
|
|
iface->get_object_path = g_dbus_object_manager_server_get_object_path;
|
|
iface->get_objects = g_dbus_object_manager_server_get_objects;
|
|
iface->get_object = g_dbus_object_manager_server_get_object;
|
|
iface->get_interface = g_dbus_object_manager_server_get_interface;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
static void
|
|
export_all (GDBusObjectManagerServer *manager)
|
|
{
|
|
GHashTableIter iter;
|
|
const gchar *object_path;
|
|
RegistrationData *data;
|
|
GHashTableIter iface_iter;
|
|
GDBusInterfaceSkeleton *iface;
|
|
GError *error;
|
|
|
|
g_return_if_fail (manager->priv->connection != NULL);
|
|
|
|
error = NULL;
|
|
g_warn_if_fail (manager->priv->manager_reg_id == 0);
|
|
manager->priv->manager_reg_id = g_dbus_connection_register_object (manager->priv->connection,
|
|
manager->priv->object_path,
|
|
(GDBusInterfaceInfo *) &manager_interface_info,
|
|
&manager_interface_vtable,
|
|
manager,
|
|
NULL, /* user_data_free_func */
|
|
&error);
|
|
if (manager->priv->manager_reg_id == 0)
|
|
{
|
|
g_warning ("%s: Error registering manager at %s: %s",
|
|
G_STRLOC,
|
|
manager->priv->object_path,
|
|
error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_hash_table_iter_init (&iter, manager->priv->map_object_path_to_data);
|
|
while (g_hash_table_iter_next (&iter, (gpointer) &object_path, (gpointer) &data))
|
|
{
|
|
g_hash_table_iter_init (&iface_iter, data->map_iface_name_to_iface);
|
|
while (g_hash_table_iter_next (&iface_iter, NULL, (gpointer) &iface))
|
|
{
|
|
g_warn_if_fail (g_dbus_interface_skeleton_get_connection (iface) == NULL);
|
|
error = NULL;
|
|
if (!g_dbus_interface_skeleton_export (iface,
|
|
manager->priv->connection,
|
|
object_path,
|
|
&error))
|
|
{
|
|
g_warning ("%s: Error registering object at %s with interface %s: %s",
|
|
G_STRLOC,
|
|
object_path,
|
|
g_dbus_interface_skeleton_get_info (iface)->name,
|
|
error->message);
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
unexport_all (GDBusObjectManagerServer *manager, gboolean only_manager)
|
|
{
|
|
GHashTableIter iter;
|
|
RegistrationData *data;
|
|
GHashTableIter iface_iter;
|
|
GDBusInterfaceSkeleton *iface;
|
|
|
|
g_return_if_fail (manager->priv->connection != NULL);
|
|
|
|
g_warn_if_fail (manager->priv->manager_reg_id > 0);
|
|
if (manager->priv->manager_reg_id > 0)
|
|
{
|
|
g_warn_if_fail (g_dbus_connection_unregister_object (manager->priv->connection,
|
|
manager->priv->manager_reg_id));
|
|
manager->priv->manager_reg_id = 0;
|
|
}
|
|
if (only_manager)
|
|
goto out;
|
|
|
|
g_hash_table_iter_init (&iter, manager->priv->map_object_path_to_data);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer) &data))
|
|
{
|
|
g_hash_table_iter_init (&iface_iter, data->map_iface_name_to_iface);
|
|
while (g_hash_table_iter_next (&iface_iter, NULL, (gpointer) &iface))
|
|
{
|
|
g_warn_if_fail (g_dbus_interface_skeleton_get_connection (iface) != NULL);
|
|
g_dbus_interface_skeleton_unexport (iface);
|
|
}
|
|
}
|
|
out:
|
|
;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|