diff options
Diffstat (limited to '')
-rw-r--r-- | src/terminal-settings-bridge-backend.cc | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/src/terminal-settings-bridge-backend.cc b/src/terminal-settings-bridge-backend.cc new file mode 100644 index 0000000..4e950de --- /dev/null +++ b/src/terminal-settings-bridge-backend.cc @@ -0,0 +1,563 @@ +/* + * Copyright © 2008, 2010, 2011, 2022 Christian Persch + * + * 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 License, 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 <http://www.gnu.org/licenses/>. + */ + +#include "config.h" +#define G_SETTINGS_ENABLE_BACKEND + +#include <cassert> + +#include "terminal-debug.hh" +#include "terminal-libgsystem.hh" +#include "terminal-settings-bridge-backend.hh" +#include "terminal-settings-utils.hh" +#include "terminal-settings-bridge-generated.h" + +#include <gio/gio.h> +#include <gio/gsettingsbackend.h> + +struct _TerminalSettingsBridgeBackend { + GSettingsBackend parent_instance; + + TerminalSettingsBridge* bridge; + GCancellable* cancellable; + + GHashTable* cache; +}; + +struct _TerminalSettingsBridgeBackendClass { + GSettingsBackendClass parent_class; +}; + +enum { + PROP_SETTINGS_BRIDGE = 1, +}; + +#define PRIORITY (10000) + +// _g_io_modules_ensure_extension_points_registered() is not public, +// so just ensure a type that does this call on registration, which +// all of the glib-internal settings backends do. However as an added +// complication, none of their get_type() functions are public. So +// instead we need to create an object using the public function and +// immediately delete it again. +// However, this *still* does not work; the +// g_null_settings_backend_new() call prints the warning about a non- +// registered extension point even though its get_type() function calls +// _g_io_modules_ensure_extension_points_registered() immediately before. +// Therefore we can only use this backend when creating a GSettings +// ourself, by explicitly passing it at that time. + +G_DEFINE_TYPE_WITH_CODE(TerminalSettingsBridgeBackend, + terminal_settings_bridge_backend, + G_TYPE_SETTINGS_BACKEND, + // _g_io_modules_ensure_extension_points_registered(); + // { gs_unref_object auto dummy = g_null_settings_backend_new(); } + // + // g_io_extension_point_implement(G_SETTINGS_BACKEND_EXTENSION_POINT_NAME, + // g_define_type_id, "bridge", PRIORITY) +); + +// Note that since D-Bus doesn't support maybe values, we use arrays +// with either zero or one item to send/receive a maybe. +// If we get more than one item, just use the first one. + +/* helper functions */ + +template<typename T> +inline constexpr auto +IMPL(T* that) noexcept +{ + return reinterpret_cast<TerminalSettingsBridgeBackend*>(that); +} + +typedef struct { + GVariant* value; + bool value_set; + bool writable; + bool writable_set; +} CacheEntry; + +static auto +cache_entry_new(void) +{ + return g_new0(CacheEntry, 1); +} + +static void +cache_entry_free(CacheEntry* e) noexcept +{ + if (e->value) + g_variant_unref(e->value); + g_free(e); +} + +static auto +cache_lookup_entry(TerminalSettingsBridgeBackend* impl, + char const* key) noexcept +{ + return reinterpret_cast<CacheEntry*>(g_hash_table_lookup(impl->cache, key)); +} + +static auto +cache_ensure_entry(TerminalSettingsBridgeBackend* impl, + char const* key) noexcept +{ + g_hash_table_insert(impl->cache, g_strdup(key), cache_entry_new()); + return cache_lookup_entry(impl, key); +} + +static void +cache_insert_value(TerminalSettingsBridgeBackend* impl, + char const* key, + GVariant* value) noexcept +{ + auto const ce = cache_ensure_entry(impl, key); + g_clear_pointer(&ce->value, g_variant_unref); + ce->value = value ? g_variant_ref(value) : nullptr; + ce->value_set = true; +} + +static void +cache_insert_writable(TerminalSettingsBridgeBackend* impl, + char const* key, + bool writable) noexcept +{ + auto const ce = cache_ensure_entry(impl, key); + ce->writable = writable; + ce->writable_set = true; +} + +static void +cache_remove_path(TerminalSettingsBridgeBackend* impl, + char const* path) noexcept +{ + auto iter = GHashTableIter{}; + g_hash_table_iter_init(&iter, impl->cache); + void* keyp = nullptr; + void* valuep = nullptr; + while (g_hash_table_iter_next(&iter, &keyp, &valuep)) { + auto const key = reinterpret_cast<char const*>(keyp); + if (g_str_has_prefix(key, path)) { + auto ce = reinterpret_cast<CacheEntry*>(valuep); + g_clear_pointer(&ce->value, g_variant_unref); + ce->value_set = false; + } + } +} + +static void +cache_remove_value(TerminalSettingsBridgeBackend* impl, + char const* key) noexcept +{ + auto const ce = cache_ensure_entry(impl, key); + g_clear_pointer(&ce->value, g_variant_unref); + ce->value_set = false; +} + +static void +cache_remove_writable(TerminalSettingsBridgeBackend* impl, + char const* key) noexcept +{ + auto const ce = cache_ensure_entry(impl, key); + ce->writable_set = false; +} + +/* GSettingsBackend class implementation */ + +static GPermission* +terminal_settings_bridge_backend_get_permission(GSettingsBackend* backend, + char const* path) noexcept +{ + _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, + "Bridge backend ::get_permission\n"); + + return g_simple_permission_new(true); +} + +static gboolean +terminal_settings_bridge_backend_get_writable(GSettingsBackend* backend, + char const* key) noexcept +{ + auto const impl = IMPL(backend); + + auto const ce = cache_lookup_entry(impl, key); + if (ce && ce->writable_set) + return ce->writable; + + auto writable = gboolean{false}; + auto const r = + terminal_settings_bridge_call_get_writable_sync(impl->bridge, + key, + &writable, + impl->cancellable, + nullptr); + + if (r) + cache_insert_writable(impl, key, writable); + else + cache_remove_writable(impl, key); + + _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, + "Bridge backend ::get_writable key %s success %d writable %d\n", + key, r, writable); + + return writable; +} + +static GVariant* +terminal_settings_bridge_backend_read(GSettingsBackend* backend, + char const* key, + GVariantType const* type, + gboolean default_value) noexcept +{ + if (default_value) + return nullptr; + + auto const impl = IMPL(backend); + auto const ce = cache_lookup_entry(impl, key); + if (ce && ce->value_set) + return ce->value ? g_variant_ref(ce->value) : nullptr; + + gs_unref_variant GVariant* rv = nullptr; + auto r = + terminal_settings_bridge_call_read_sync(impl->bridge, + key, + g_variant_type_peek_string(type), + default_value, + &rv, + impl->cancellable, + nullptr); + + auto const value = r ? terminal_g_variant_unwrap(rv) : nullptr; + + if (r && value && !g_variant_is_of_type(value, type)) { + _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, + "Bridge backend ::read key %s got type %s expected type %s\n", + key, + g_variant_get_type_string(value), + g_variant_type_peek_string(type)); + + g_clear_pointer(&value, g_variant_unref); + r = false; + } + + if (r) + cache_insert_value(impl, key, value); + else + cache_remove_value(impl, key); + + _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, + "Bridge backend ::read key %s success %d value %s\n", + key, r, value ? g_variant_print(value, true) : "(null)"); + + return value; +} + +static GVariant* +terminal_settings_bridge_backend_read_user_value(GSettingsBackend* backend, + char const* key, + GVariantType const* type) noexcept +{ + auto const impl = IMPL(backend); + + auto const ce = cache_lookup_entry(impl, key); + if (ce && ce->value_set) + return ce->value ? g_variant_ref(ce->value) : nullptr; + + gs_unref_variant GVariant* rv = nullptr; + auto r = + terminal_settings_bridge_call_read_user_value_sync(impl->bridge, + key, + g_variant_type_peek_string(type), + &rv, + impl->cancellable, + nullptr); + + auto const value = r ? terminal_g_variant_unwrap(rv) : nullptr; + + if (r && value && !g_variant_is_of_type(value, type)) { + _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, + "Bridge backend ::read_user_value key %s got type %s expected type %s\n", + key, + g_variant_get_type_string(value), + g_variant_type_peek_string(type)); + + g_clear_pointer(&value, g_variant_unref); + r = false; + } + + if (r) + cache_insert_value(impl, key, value); + else + cache_remove_value(impl, key); + + _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, + "Bridge backend ::read_user_value key %s success %d value %s\n", + key, r, value ? g_variant_print(value, true) : "(null)"); + + return value; +} + +static void +terminal_settings_bridge_backend_reset(GSettingsBackend* backend, + char const* key, + void* tag) noexcept +{ + auto const impl = IMPL(backend); + auto const r = + terminal_settings_bridge_call_reset_sync(impl->bridge, + key, + impl->cancellable, + nullptr); + + cache_remove_value(impl, key); + + g_settings_backend_changed(backend, key, tag); + + _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, + "Bridge backend ::reset key %s success %d\n", + key, r); +} + +static void +terminal_settings_bridge_backend_sync(GSettingsBackend* backend) noexcept +{ + auto const impl = IMPL(backend); + terminal_settings_bridge_call_sync_sync(impl->bridge, + impl->cancellable, + nullptr); + + _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, + "Bridge backend ::sync\n"); +} + +static void +terminal_settings_bridge_backend_subscribe(GSettingsBackend* backend, + char const* name) noexcept +{ + auto const impl = IMPL(backend); + terminal_settings_bridge_call_subscribe_sync(impl->bridge, + name, + impl->cancellable, + nullptr); + + _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, + "Bridge backend ::subscribe name %s\n", name); +} + +static void +terminal_settings_bridge_backend_unsubscribe(GSettingsBackend* backend, + char const* name) noexcept +{ + auto const impl = IMPL(backend); + terminal_settings_bridge_call_unsubscribe_sync(impl->bridge, + name, + impl->cancellable, + nullptr); + + _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, + "Bridge backend ::unsubscribe name %s\n", name); +} + +static gboolean +terminal_settings_bridge_backend_write(GSettingsBackend* backend, + char const* key, + GVariant* value, + void* tag) noexcept +{ + auto const impl = IMPL(backend); + + gs_unref_variant auto holder = g_variant_ref_sink(value); + + auto success = gboolean{false}; + auto const r = + terminal_settings_bridge_call_write_sync(impl->bridge, + key, + terminal_g_variant_wrap(value), + &success, + impl->cancellable, + nullptr); + + cache_insert_value(impl, key, value); + + g_settings_backend_changed(backend, key, tag); + + _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, + "Bridge backend ::write key %s value %s success %d\n", + key, value ? g_variant_print(value, true) : "(null)", r); + + return r && success; +} + +static gboolean +terminal_settings_bridge_backend_write_tree(GSettingsBackend* backend, + GTree* tree, + void* tag) noexcept +{ + auto const impl = IMPL(backend); + + gs_free char* path_prefix = nullptr; + gs_free char const** keys = nullptr; + gs_free GVariant** values = nullptr; + g_settings_backend_flatten_tree(tree, + &path_prefix, + &keys, + &values); + + auto builder = GVariantBuilder{}; + g_variant_builder_init(&builder, G_VARIANT_TYPE("a(smv)")); + for (auto i = 0; keys[i]; ++i) { + gs_unref_variant auto value = values[i] ? g_variant_ref_sink(values[i]) : nullptr; + + g_variant_builder_add(&builder, + "(smv)", + keys[i], + value ? g_variant_new_variant(value) : nullptr); + + gs_free auto wkey = g_strconcat(path_prefix, keys[i], nullptr); + // Directory reset? + if (g_str_has_suffix(wkey, "/")) { + g_warn_if_fail(!value); + cache_remove_path(impl, wkey); + } else { + cache_insert_value(impl, wkey, value); + } + } + + auto const tree_value = terminal_g_variant_wrap(g_variant_builder_end(&builder)); + + auto success = gboolean{false}; + auto const r = + terminal_settings_bridge_call_write_tree_sync(impl->bridge, + path_prefix, + tree_value, // consumed + &success, + impl->cancellable, + nullptr); + + g_settings_backend_changed_tree(backend, tree, tag); + + _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, + "Bridge backend ::write_tree success %d\n", + r); + + return r && success; +} + +/* GObject class implementation */ + +static void +terminal_settings_bridge_backend_init(TerminalSettingsBridgeBackend* backend) /* noexcept */ +{ + auto const impl = IMPL(backend); + + // Note that unfortunately it appears to be impossible to receive all + // change notifications from a GSettingsBackend directly, so we cannot + // get forwarded change notifications from the bridge. Instead, we have + // to cache written values (since the actual write happens delayed in + // the remote backend and the next read may still return the old value + // otherwise). + impl->cache = g_hash_table_new_full(g_str_hash, + g_str_equal, + g_free, + GDestroyNotify(cache_entry_free)); +} + +static void +terminal_settings_bridge_backend_constructed(GObject* object) noexcept +{ + G_OBJECT_CLASS(terminal_settings_bridge_backend_parent_class)->constructed(object); + + auto const impl = IMPL(object); + assert(impl->bridge); +} + +static void +terminal_settings_bridge_backend_finalize(GObject* object) noexcept +{ + auto const impl = IMPL(object); + g_clear_pointer(&impl->cache, g_hash_table_unref); + g_clear_object(&impl->cancellable); + g_clear_object(&impl->bridge); + + G_OBJECT_CLASS(terminal_settings_bridge_backend_parent_class)->finalize(object); +} + +static void +terminal_settings_bridge_backend_set_property(GObject* object, + guint prop_id, + GValue const* value, + GParamSpec* pspec) noexcept +{ + auto const impl = IMPL(object); + + switch (prop_id) { + case PROP_SETTINGS_BRIDGE: + impl->bridge = TERMINAL_SETTINGS_BRIDGE(g_value_dup_object(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +terminal_settings_bridge_backend_class_init(TerminalSettingsBridgeBackendClass* klass) /* noexcept */ +{ + auto const gobject_class = G_OBJECT_CLASS(klass); + gobject_class->constructed = terminal_settings_bridge_backend_constructed; + gobject_class->finalize = terminal_settings_bridge_backend_finalize; + gobject_class->set_property = terminal_settings_bridge_backend_set_property; + + g_object_class_install_property + (gobject_class, + PROP_SETTINGS_BRIDGE, + g_param_spec_object("settings-bridge", nullptr, nullptr, + TERMINAL_TYPE_SETTINGS_BRIDGE, + GParamFlags(G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS))); + + auto const backend_class = G_SETTINGS_BACKEND_CLASS(klass); + backend_class->get_permission = terminal_settings_bridge_backend_get_permission; + backend_class->get_writable = terminal_settings_bridge_backend_get_writable; + backend_class->read = terminal_settings_bridge_backend_read; + backend_class->read_user_value = terminal_settings_bridge_backend_read_user_value; + backend_class->reset = terminal_settings_bridge_backend_reset; + backend_class->subscribe = terminal_settings_bridge_backend_subscribe; + backend_class->sync = terminal_settings_bridge_backend_sync; + backend_class->unsubscribe = terminal_settings_bridge_backend_unsubscribe; + backend_class->write = terminal_settings_bridge_backend_write; + backend_class->write_tree = terminal_settings_bridge_backend_write_tree; +} + +/* public API */ + +/** + * terminal_settings_bridge_backend_new: + * @bridge: a #TerminalSettingsBridge + * + * Returns: (transfer full): a new #TerminalSettingsBridgeBackend for @bridge + */ +GSettingsBackend* +terminal_settings_bridge_backend_new(TerminalSettingsBridge* bridge) +{ + return reinterpret_cast<GSettingsBackend*> + (g_object_new (TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND, + "settings-bridge", bridge, + nullptr)); +} |