--- a/meson.build +++ b/meson.build @@ -75,6 +75,7 @@ gio_unix_req_version = '>= 2.25.7' glib_dep = dependency('glib-2.0', version: '>= 2.44.0') gio_unix_dep = dependency('gio-unix-2.0', version: gio_unix_req_version) +dbus_1_dep = dependency('dbus-1') gio_querymodules = find_program('gio-querymodules', required: false) if gio_querymodules.found() @@ -101,6 +102,7 @@ subdir('gvdb') subdir('common') subdir('engine') subdir('service') +subdir('dbus-1') subdir('gdbus') subdir('gsettings') subdir('client') --- /dev/null +++ b/dbus-1/meson.build @@ -0,0 +1,43 @@ +client_inc = include_directories('.') + +libdbus_1_sources = files('dconf-libdbus-1.c', 'dconf-libdbus-1.h') + +cflags = '-DG_LOG_DOMAIN="@0@"'.format(meson.project_name()) + +libdconf_libdbus_1_shared = static_library( + meson.project_name() + '-libdbus-1-shared', + sources: libdbus_1_sources, + include_directories: top_inc, + dependencies: [gio_unix_dep, dbus_1_dep], + c_args: cflags, + pic: true +) + +libdconf_libdbus_1 = static_library( + meson.project_name() + '-libdbus-1', + sources: libdbus_1_sources, + include_directories: top_inc, + dependencies: [gio_unix_dep, dbus_1_dep], + c_args: cflags, +) + +dbus_1_sources = files('dconf-dbus-1.c') + +libdconf_dbus_1 = shared_library( + meson.project_name() + '-dbus-1', + sources: dbus_1_sources, + version: '0.0.0', + soversion: '0', + include_directories: top_inc, + dependencies: [gio_unix_dep, dbus_1_dep], + c_args: cflags, + link_with: [ + libdconf_common_hidden, + libdconf_engine_shared, + libdconf_libdbus_1_shared, + libdconf_shm_shared, + libgvdb_shared + ], + install: true, + install_dir: dconf_libdir +) --- /dev/null +++ b/dbus-1/dconf-dbus-1.c @@ -0,0 +1,188 @@ +/** + * Copyright © 2010 Canonical Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the licence, or (at + * your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author: Ryan Lortie + **/ + +#include "config.h" + +#include "dconf-dbus-1.h" + +#include "../engine/dconf-engine.h" +#include "dconf-libdbus-1.h" + +#include + +struct _DConfDBusClient +{ + DConfEngine *engine; + GSList *watches; + gint ref_count; +}; + +typedef struct +{ + gchar *path; + DConfDBusNotify notify; + gpointer user_data; +} Watch; + +void +dconf_engine_change_notify (DConfEngine *engine, + const gchar *prefix, + const gchar * const *changes, + const gchar *tag, + gboolean is_writability, + gpointer origin_tag, + gpointer user_data) +{ + DConfDBusClient *dcdbc = user_data; + gchar **my_changes; + gint n_changes; + GSList *iter; + gint i; + + n_changes = g_strv_length ((gchar **) changes); + my_changes = g_new (gchar *, n_changes + 1); + + for (i = 0; i < n_changes; i++) + my_changes[i] = g_strconcat (prefix, changes[i], NULL); + my_changes[i] = NULL; + + for (iter = dcdbc->watches; iter; iter = iter->next) + { + Watch *watch = iter->data; + + for (i = 0; i < n_changes; i++) + if (g_str_has_prefix (my_changes[i], watch->path)) + watch->notify (dcdbc, my_changes[i], watch->user_data); + } + + g_strfreev (my_changes); +} + +GVariant * +dconf_dbus_client_read (DConfDBusClient *dcdbc, + const gchar *key) +{ + return dconf_engine_read (dcdbc->engine, DCONF_READ_FLAGS_NONE, NULL, key); +} + +gboolean +dconf_dbus_client_write (DConfDBusClient *dcdbc, + const gchar *key, + GVariant *value) +{ + DConfChangeset *changeset; + gboolean success; + + changeset = dconf_changeset_new_write (key, value); + success = dconf_engine_change_fast (dcdbc->engine, changeset, NULL, NULL); + dconf_changeset_unref (changeset); + + return success; +} + +void +dconf_dbus_client_subscribe (DConfDBusClient *dcdbc, + const gchar *path, + DConfDBusNotify notify, + gpointer user_data) +{ + Watch *watch; + + watch = g_slice_new (Watch); + watch->path = g_strdup (path); + watch->notify = notify; + watch->user_data = user_data; + + dcdbc->watches = g_slist_prepend (dcdbc->watches, watch); + + dconf_engine_watch_fast (dcdbc->engine, path); +} + +void +dconf_dbus_client_unsubscribe (DConfDBusClient *dcdbc, + DConfDBusNotify notify, + gpointer user_data) +{ + GSList **ptr; + + for (ptr = &dcdbc->watches; *ptr; ptr = &(*ptr)->next) + { + Watch *watch = (*ptr)->data; + + if (watch->notify == notify && watch->user_data == user_data) + { + *ptr = g_slist_remove_link (*ptr, *ptr); + dconf_engine_unwatch_fast (dcdbc->engine, watch->path); + g_free (watch->path); + g_slice_free (Watch, watch); + return; + } + } + + g_warning ("No matching watch found to unsubscribe"); +} + +gboolean +dconf_dbus_client_has_pending (DConfDBusClient *dcdbc) +{ + return dconf_engine_has_outstanding (dcdbc->engine); +} + +DConfDBusClient * +dconf_dbus_client_new (const gchar *profile, + DBusConnection *session, + DBusConnection *system) +{ + DConfDBusClient *dcdbc; + + if (session == NULL) + session = dbus_bus_get (DBUS_BUS_SESSION, NULL); + + if (system == NULL) + system = dbus_bus_get (DBUS_BUS_SYSTEM, NULL); + + dconf_libdbus_1_provide_bus (G_BUS_TYPE_SESSION, session); + dconf_libdbus_1_provide_bus (G_BUS_TYPE_SYSTEM, system); + + dcdbc = g_slice_new (DConfDBusClient); + dcdbc->engine = dconf_engine_new (NULL, dcdbc, NULL); + dcdbc->watches = NULL; + dcdbc->ref_count = 1; + + return dcdbc; +} + +void +dconf_dbus_client_unref (DConfDBusClient *dcdbc) +{ + if (--dcdbc->ref_count == 0) + { + g_return_if_fail (dcdbc->watches == NULL); + + g_slice_free (DConfDBusClient, dcdbc); + } +} + +DConfDBusClient * +dconf_dbus_client_ref (DConfDBusClient *dcdbc) +{ + dcdbc->ref_count++; + + return dcdbc; +} --- /dev/null +++ b/dbus-1/dconf-dbus-1.h @@ -0,0 +1,56 @@ +/** + * Copyright © 2010 Canonical Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the licence, or (at + * your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author: Ryan Lortie + **/ + +#ifndef _dconf_dbus_1_h_ +#define _dconf_dbus_1_h_ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _DConfDBusClient DConfDBusClient; + +typedef void (* DConfDBusNotify) (DConfDBusClient *dcdbc, + const gchar *key, + gpointer user_data); + +DConfDBusClient * dconf_dbus_client_new (const gchar *profile, + DBusConnection *session, + DBusConnection *system); +void dconf_dbus_client_unref (DConfDBusClient *dcdbc); +DConfDBusClient * dconf_dbus_client_ref (DConfDBusClient *dcdbc); + +GVariant * dconf_dbus_client_read (DConfDBusClient *dcdbc, + const gchar *key); +gboolean dconf_dbus_client_write (DConfDBusClient *dcdbc, + const gchar *key, + GVariant *value); +void dconf_dbus_client_subscribe (DConfDBusClient *dcdbc, + const gchar *name, + DConfDBusNotify notify, + gpointer user_data); +void dconf_dbus_client_unsubscribe (DConfDBusClient *dcdbc, + DConfDBusNotify notify, + gpointer user_data); +gboolean dconf_dbus_client_has_pending (DConfDBusClient *dcdbc); + +G_END_DECLS + +#endif /* _dconf_dbus_1_h_ */ --- /dev/null +++ b/dbus-1/dconf-libdbus-1.c @@ -0,0 +1,365 @@ +/** + * Copyright © 2010 Canonical Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the licence, or (at + * your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author: Ryan Lortie + **/ + +#include "config.h" + +#include "dconf-libdbus-1.h" + +#include "../engine/dconf-engine.h" + +#include + +static DBusConnection *dconf_libdbus_1_buses[5]; + +struct _DConfDBusClient +{ + DConfEngine *engine; + GSList *watches; + gint ref_count; +}; + +#define DCONF_LIBDBUS_1_ERROR (g_quark_from_static_string("DCONF_LIBDBUS_1_ERROR")) +#define DCONF_LIBDBUS_1_ERROR_FAILED 0 + +static DBusMessage * +dconf_libdbus_1_new_method_call (const gchar *bus_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters) +{ + DBusMessageIter dbus_iter; + DBusMessage *message; + GVariantIter iter; + GVariant *child; + + g_variant_ref_sink (parameters); + + message = dbus_message_new_method_call (bus_name, object_path, interface_name, method_name); + dbus_message_iter_init_append (message, &dbus_iter); + g_variant_iter_init (&iter, parameters); + + while ((child = g_variant_iter_next_value (&iter))) + { + if (g_variant_is_of_type (child, G_VARIANT_TYPE_STRING)) + { + const gchar *str; + + str = g_variant_get_string (child, NULL); + dbus_message_iter_append_basic (&dbus_iter, DBUS_TYPE_STRING, &str); + } + + else if (g_variant_is_of_type (child, G_VARIANT_TYPE_UINT32)) + { + guint32 uint; + + uint = g_variant_get_uint32 (child); + dbus_message_iter_append_basic (&dbus_iter, DBUS_TYPE_UINT32, &uint); + } + + else + { + DBusMessageIter subiter; + const guint8 *bytes; + gsize n_elements; + + g_assert (g_variant_is_of_type (child, G_VARIANT_TYPE_BYTESTRING)); + + bytes = g_variant_get_fixed_array (child, &n_elements, sizeof (guint8)); + dbus_message_iter_open_container (&dbus_iter, DBUS_TYPE_ARRAY, "y", &subiter); + dbus_message_iter_append_fixed_array (&subiter, DBUS_TYPE_BYTE, &bytes, n_elements); + dbus_message_iter_close_container (&dbus_iter, &subiter); + } + + g_variant_unref (child); + } + + g_variant_unref (parameters); + + return message; +} + +static GVariant * +dconf_libdbus_1_get_message_body (DBusMessage *message, + GError **error) +{ + GVariantBuilder builder; + const gchar *signature; + DBusMessageIter iter; + + /* We support two types: strings and arrays of strings. + * + * It's very simple to detect if the message contains only these + * types: check that the signature contains only the letters "a" and + * "s" and that it does not contain "aa". + */ + signature = dbus_message_get_signature (message); + if (signature[strspn(signature, "as")] != '\0' || strstr (signature, "aa")) + { + g_set_error (error, DCONF_LIBDBUS_1_ERROR, DCONF_LIBDBUS_1_ERROR_FAILED, + "unable to handle message type '(%s)'", signature); + return NULL; + } + + g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE); + dbus_message_iter_init (message, &iter); + while (dbus_message_iter_get_arg_type (&iter)) + { + const gchar *string; + + if (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING) + { + dbus_message_iter_get_basic (&iter, &string); + g_variant_builder_add (&builder, "s", string); + } + else + { + DBusMessageIter sub; + + g_assert (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_ARRAY && + dbus_message_iter_get_element_type (&iter) == DBUS_TYPE_STRING); + + g_variant_builder_open (&builder, G_VARIANT_TYPE_STRING_ARRAY); + dbus_message_iter_recurse (&iter, &sub); + + while (dbus_message_iter_get_arg_type (&sub)) + { + const gchar *string; + dbus_message_iter_get_basic (&sub, &string); + g_variant_builder_add (&builder, "s", string); + dbus_message_iter_next (&sub); + } + + g_variant_builder_close (&builder); + } + dbus_message_iter_next (&iter); + } + + return g_variant_ref_sink (g_variant_builder_end (&builder)); +} + +static GVariant * +dconf_libdbus_1_interpret_result (DBusMessage *result, + const GVariantType *expected_type, + GError **error) +{ + GVariant *reply; + + if (dbus_message_get_type (result) == DBUS_MESSAGE_TYPE_ERROR) + { + const gchar *errstr = "(no message)"; + + dbus_message_get_args (result, NULL, DBUS_TYPE_STRING, &errstr, DBUS_TYPE_INVALID); + g_set_error (error, DCONF_LIBDBUS_1_ERROR, DCONF_LIBDBUS_1_ERROR_FAILED, + "%s: %s", dbus_message_get_error_name (result), errstr); + return NULL; + } + + reply = dconf_libdbus_1_get_message_body (result, error); + + if (reply && expected_type && !g_variant_is_of_type (reply, expected_type)) + { + gchar *expected_string; + + expected_string = g_variant_type_dup_string (expected_type); + g_set_error (error, DCONF_LIBDBUS_1_ERROR, DCONF_LIBDBUS_1_ERROR_FAILED, + "received reply '%s' is not of the expected type %s", + g_variant_get_type_string (reply), expected_string); + g_free (expected_string); + + g_variant_unref (reply); + reply = NULL; + } + + return reply; +} + +static void +dconf_libdbus_1_method_call_done (DBusPendingCall *pending, + gpointer user_data) +{ + DConfEngineCallHandle *handle = user_data; + const GVariantType *expected_type; + DBusMessage *message; + GError *error = NULL; + GVariant *reply; + + if (pending == NULL) + return; + + message = dbus_pending_call_steal_reply (pending); + dbus_pending_call_unref (pending); + + expected_type = dconf_engine_call_handle_get_expected_type (handle); + reply = dconf_libdbus_1_interpret_result (message, expected_type, &error); + dbus_message_unref (message); + + dconf_engine_call_handle_reply (handle, reply, error); + + if (reply) + g_variant_unref (reply); + if (error) + g_error_free (error); +} + +gboolean +dconf_engine_dbus_call_async_func (GBusType bus_type, + const gchar *bus_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + DConfEngineCallHandle *handle, + GError **error) +{ + DBusConnection *connection; + DBusPendingCall *pending; + DBusMessage *message; + + g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_libdbus_1_buses)); + connection = dconf_libdbus_1_buses[bus_type]; + g_assert (connection != NULL); + + message = dconf_libdbus_1_new_method_call (bus_name, object_path, interface_name, method_name, parameters); + dbus_connection_send_with_reply (connection, message, &pending, -1); + dbus_pending_call_set_notify (pending, dconf_libdbus_1_method_call_done, handle, NULL); + dbus_message_unref (message); + + return TRUE; +} + +static void +dconf_libdbus_1_convert_error (DBusError *dbus_error, + GError **error) +{ + g_set_error (error, DCONF_LIBDBUS_1_ERROR, DCONF_LIBDBUS_1_ERROR_FAILED, + "%s: %s", dbus_error->name, dbus_error->message); +} + +GVariant * +dconf_engine_dbus_call_sync_func (GBusType bus_type, + const gchar *bus_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + const GVariantType *expected_type, + GError **error) +{ + DBusConnection *connection; + DBusMessage *message; + DBusError dbus_error; + DBusMessage *result; + GVariant *reply; + + g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_libdbus_1_buses)); + connection = dconf_libdbus_1_buses[bus_type]; + g_assert (connection != NULL); + + dbus_error_init (&dbus_error); + message = dconf_libdbus_1_new_method_call (bus_name, object_path, interface_name, method_name, parameters); + result = dbus_connection_send_with_reply_and_block (connection, message, -1, &dbus_error); + dbus_message_unref (message); + + if (result == NULL) + { + dconf_libdbus_1_convert_error (&dbus_error, error); + dbus_error_free (&dbus_error); + return NULL; + } + + reply = dconf_libdbus_1_interpret_result (result, expected_type, error); + dbus_message_unref (result); + + return reply; +} + +static DBusHandlerResult +dconf_libdbus_1_filter (DBusConnection *connection, + DBusMessage *message, + gpointer user_data) +{ + GBusType bus_type = GPOINTER_TO_INT (user_data); + + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_SIGNAL) + { + const gchar *interface; + + interface = dbus_message_get_interface (message); + + if (interface && g_str_equal (interface, "ca.desrt.dconf.Writer")) + { + GVariant *parameters; + + parameters = dconf_libdbus_1_get_message_body (message, NULL); + + if (parameters != NULL) + { + dconf_engine_handle_dbus_signal (bus_type, + dbus_message_get_sender (message), + dbus_message_get_path (message), + dbus_message_get_member (message), + parameters); + g_variant_unref (parameters); + } + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +void +dconf_libdbus_1_provide_bus (GBusType bus_type, + DBusConnection *connection) +{ + g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_libdbus_1_buses)); + + if (!dconf_libdbus_1_buses[bus_type]) + { + dconf_libdbus_1_buses[bus_type] = dbus_connection_ref (connection); + dbus_connection_add_filter (connection, dconf_libdbus_1_filter, GINT_TO_POINTER (bus_type), NULL); + } +} + +#ifndef PIC +static gboolean +dconf_libdbus_1_check_connection (gpointer user_data) +{ + DBusConnection *connection = user_data; + + dbus_connection_read_write (connection, 0); + dbus_connection_dispatch (connection); + + return G_SOURCE_CONTINUE; +} + +void +dconf_engine_dbus_init_for_testing (void) +{ + DBusConnection *session; + DBusConnection *system; + + dconf_libdbus_1_provide_bus (G_BUS_TYPE_SESSION, session = dbus_bus_get (DBUS_BUS_SESSION, NULL)); + dconf_libdbus_1_provide_bus (G_BUS_TYPE_SYSTEM, system = dbus_bus_get (DBUS_BUS_SYSTEM, NULL)); + + /* "mainloop integration" */ + g_timeout_add (1, dconf_libdbus_1_check_connection, session); + g_timeout_add (1, dconf_libdbus_1_check_connection, system); +} +#endif --- /dev/null +++ b/dbus-1/dconf-libdbus-1.h @@ -0,0 +1,11 @@ +#ifndef __dconf_libdbus_1_h__ +#define __dconf_libdbus_1_h__ + +#include +#include + +G_GNUC_INTERNAL +void dconf_libdbus_1_provide_bus (GBusType bus_type, + DBusConnection *connection); + +#endif /* __dconf_libdbus_1_h__ */ --- a/tests/meson.build +++ b/tests/meson.build @@ -23,6 +23,7 @@ unit_tests = [ ['gvdb', 'gvdb.c', '-DSRCDIR="@0@"'.format(test_dir), glib_dep, libgvdb], ['gdbus-thread', 'dbus.c', '-DDBUS_BACKEND="/gdbus/thread"', gio_unix_dep, libdconf_gdbus_thread], ['gdbus-filter', 'dbus.c', '-DDBUS_BACKEND="/gdbus/filter"', gio_unix_dep, libdconf_gdbus_filter], + ['libdbus-1', 'dbus.c', '-DDBUS_BACKEND="/libdbus-1"', gio_unix_dep, libdconf_libdbus_1], ['engine', 'engine.c', '-DSRCDIR="@0@"'.format(test_dir), [glib_dep, dl_dep, m_dep], [libdconf_engine, libdconf_common, libdconf_mock]], ['client', 'client.c', '-DSRCDIR="@0@"'.format(test_dir), gio_unix_dep, [libdconf_client, libdconf_engine, libdconf_common, libdconf_mock]] ]