[gnome-terminal] prefs: Make preferences dialogue OOP



commit 0ebf9a5a442f6fd60d9cc9cc56dc15659cc31c95
Author: Christian Persch <chpe src gnome org>
Date:   Fri Aug 26 22:10:31 2022 +0200

    prefs: Make preferences dialogue OOP

 meson.build                               |   1 +
 src/meson.build                           | 210 ++++++----
 src/org.gnome.Terminal.SettingsBridge.xml |  90 +++++
 src/prefs-main.cc                         | 264 +++++++++++++
 src/prefs.gresource.xml                   |  23 ++
 src/server.cc                             |   3 +-
 src/terminal-app.cc                       | 304 +++++++++++---
 src/terminal-app.hh                       |   9 +-
 src/terminal-client-utils.cc              | 183 ++++++++-
 src/terminal-client-utils.hh              |  16 +-
 src/terminal-dconf.cc                     | 124 ++++++
 src/terminal-dconf.hh                     |  41 ++
 src/terminal-debug.cc                     |   1 +
 src/terminal-debug.hh                     |   3 +-
 src/terminal-defines.hh                   |   8 +
 src/terminal-libgsystem.hh                |  11 +
 src/terminal-options.cc                   |   2 +-
 src/terminal-prefs-process.cc             | 511 ++++++++++++++++++++++++
 src/terminal-prefs-process.hh             |  53 +++
 src/terminal-prefs.cc                     |   6 +-
 src/terminal-prefs.hh                     |   4 +-
 src/terminal-profiles-list.cc             |   7 +-
 src/terminal-profiles-list.hh             |   3 +-
 src/terminal-screen.cc                    |   3 +-
 src/terminal-settings-bridge-backend.cc   | 635 ++++++++++++++++++++++++++++++
 src/terminal-settings-bridge-backend.hh   |  49 +++
 src/terminal-settings-bridge-impl.cc      | 478 ++++++++++++++++++++++
 src/terminal-settings-bridge-impl.hh      |  38 ++
 src/terminal-settings-list.cc             | 176 +++++----
 src/terminal-settings-list.hh             |   3 +-
 src/terminal-util.cc                      |  16 +-
 src/terminal-window.cc                    |   3 +-
 src/terminal.cc                           |  48 ++-
 src/terminal.gresource.xml                |   1 -
 34 files changed, 3091 insertions(+), 236 deletions(-)
---
diff --git a/meson.build b/meson.build
index 3a4bcb92..a25a04f7 100644
--- a/meson.build
+++ b/meson.build
@@ -71,6 +71,7 @@ gt_micro_version = version_split[2].to_int()
 
 # Directories
 
+gt_bindir     = get_option('bindir')
 gt_datadir    = get_option('datadir')
 gt_includedir = get_option('includedir')
 gt_libdir     = get_option('libdir')
diff --git a/src/meson.build b/src/meson.build
index 99f47f82..456ba6b2 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,4 +1,4 @@
-# Copyright © 2019, 2020, 2021 Christian Persch
+# Copyright © 2019, 2020, 2021, 2022 Christian Persch
 #
 # This programme is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by
@@ -15,8 +15,46 @@
 
 src_inc = include_directories('.')
 
+# UI files
+
+ui_xsltproc_command = [
+  xsltproc,
+  '--nonet',
+  '-o', '@OUTPUT@',
+  '@INPUT@',
+]
+
+menubar_ui_mnemonics = custom_target(
+  'terminal-menubar-with-mnemonics.ui',
+  command: ui_xsltproc_command,
+  input: files(
+    '..' / 'data' / 'ui-filter-mnemonics.xslt',
+    'terminal-menubar.ui.in',
+  ),
+  install: false,
+  output: 'terminal-menubar-with-mnemonics.ui',
+)
+
+menubar_ui_nomnemonics = custom_target(
+  'terminal-menubar-without-mnemonics.ui',
+  command: ui_xsltproc_command,
+  input: files(
+    '..' / 'data' / 'ui-filter-no-mnemonics.xslt',
+    'terminal-menubar.ui.in',
+  ),
+  install: false,
+  output: 'terminal-menubar-without-mnemonics.ui',
+)
+
 # Common sources
 
+app_sources = files(
+  'terminal-accels.cc',
+  'terminal-accels.hh',
+  'terminal-app.cc',
+  'terminal-app.hh',
+)
+
 client_util_sources = files(
   'terminal-client-utils.cc',
   'terminal-client-utils.hh',
@@ -37,18 +75,37 @@ dbus_sources = gnome.gdbus_codegen(
   object_manager: true,
 )
 
+egg_sources = files(
+  'eggshell.cc',
+  'eggshell.hh',
+)
+
 i18n_sources = files(
   'terminal-i18n.cc',
   'terminal-i18n.hh',
   'terminal-intl.hh',
 )
 
+marshal_sources = gnome.genmarshal(
+  'terminal-marshal',
+  internal: true,
+  install_header: false,
+  prefix: '_terminal_marshal',
+  sources: files(
+    'terminal-marshal.list',
+  ),
+  stdinc: true,
+  valist_marshallers: true,
+)
+
 misc_sources = files(
   'terminal-defines.hh',
   'terminal-libgsystem.hh',
 )
 
 profiles_sources = files(
+  'terminal-dconf.cc',
+  'terminal-dconf.hh',
   'terminal-profiles-list.cc',
   'terminal-profiles-list.hh',
   'terminal-schemas.hh',
@@ -56,6 +113,16 @@ profiles_sources = files(
   'terminal-settings-list.hh',
 )
 
+settings_dbus_sources = gnome.gdbus_codegen(
+  'terminal-settings-bridge-generated',
+  'org.gnome.Terminal.SettingsBridge.xml',
+  autocleanup: 'all',
+  install_header: false,
+  interface_prefix: gt_dns_name,
+  namespace: 'Terminal',
+  object_manager: false,
+)
+
 regex_sources = files(
   'terminal-regex.hh',
 )
@@ -85,47 +152,39 @@ version_sources = [configure_file(
   output: '@BASENAME@',
 )]
 
-# Server
+util_sources = files(
+  'terminal-util.cc',
+  'terminal-util.hh',
+)
 
-ui_xsltproc_command = [
-  xsltproc,
-  '--nonet',
-  '-o', '@OUTPUT@',
-  '@INPUT@',
+# Flags
+
+common_cxxflags = version_cxxflags + [
+  '-DTERMINAL_COMPILATION',
+  '-DTERM_BINDIR="@0@"'.format(gt_prefix / gt_bindir),
+  '-DTERM_DATADIR="@0@"'.format(gt_prefix / gt_datadir),
+  '-DTERM_LIBEXECDIR="@0@"'.format(gt_prefix / gt_libexecdir),
+  '-DTERM_LOCALEDIR="@0@"'.format(gt_prefix / gt_localedir),
+  '-DTERM_PKGDATADIR="@0@"'.format(gt_prefix / gt_pkgdatadir),
+  '-DTERM_PKGLIBDIR="@0@"'.format(gt_prefix / gt_pkglibdir),
+  '-DVTE_DISABLE_DEPRECATION_WARNINGS',
 ]
 
-menubar_ui_mnemonics = custom_target(
-  'terminal-menubar-with-mnemonics.ui',
-  command: ui_xsltproc_command,
-  input: files(
-    '..' / 'data' / 'ui-filter-mnemonics.xslt',
-    'terminal-menubar.ui.in',
-  ),
-  install: false,
-  output: 'terminal-menubar-with-mnemonics.ui',
-)
+# Server
 
-menubar_ui_nomnemonics = custom_target(
-  'terminal-menubar-without-mnemonics.ui',
-  command: ui_xsltproc_command,
-  input: files(
-    '..' / 'data' / 'ui-filter-no-mnemonics.xslt',
-    'terminal-menubar.ui.in',
-  ),
-  install: false,
-  output: 'terminal-menubar-without-mnemonics.ui',
+server_resources_sources = gnome.compile_resources(
+  'terminal-server-resources',
+  'terminal.gresource.xml',
+  c_name: 'terminal',
+  dependencies: [
+    menubar_ui_mnemonics,
+    menubar_ui_nomnemonics,
+  ],
+  export: false,
 )
 
-server_sources = client_util_sources + debug_sources + dbus_sources + i18n_sources + misc_sources + 
profiles_sources + regex_sources + types_sources + version_sources + files(
-  'eggshell.cc',
-  'eggshell.hh',
-  'profile-editor.cc',
-  'profile-editor.hh',
+server_sources = app_sources + client_util_sources + debug_sources + dbus_sources + egg_sources + 
i18n_sources + marshal_sources + misc_sources + profiles_sources + regex_sources + server_resources_sources + 
settings_dbus_sources + types_sources + util_sources + version_sources + files(
   'server.cc',
-  'terminal-accels.cc',
-  'terminal-accels.hh',
-  'terminal-app.cc',
-  'terminal-app.hh',
   'terminal-enums.hh',
   'terminal-gdbus.cc',
   'terminal-gdbus.hh',
@@ -142,45 +201,22 @@ server_sources = client_util_sources + debug_sources + dbus_sources + i18n_sourc
   'terminal-notebook.cc',
   'terminal-notebook.hh',
   'terminal-pcre2.hh',
-  'terminal-prefs.cc',
-  'terminal-prefs.hh',
+  'terminal-prefs-process.cc',
+  'terminal-prefs-process.hh',
   'terminal-screen-container.cc',
   'terminal-screen-container.hh',
   'terminal-screen.cc',
   'terminal-screen.hh',
   'terminal-search-popover.cc',
   'terminal-search-popover.hh',
+  'terminal-settings-bridge-impl.cc',
+  'terminal-settings-bridge-impl.hh',
   'terminal-tab-label.cc',
   'terminal-tab-label.hh',
-  'terminal-util.cc',
-  'terminal-util.hh',
   'terminal-window.cc',
   'terminal-window.hh',
 )
 
-server_sources += gnome.compile_resources(
-  'terminal-resources',
-  'terminal.gresource.xml',
-  c_name: 'terminal',
-  dependencies: [
-    menubar_ui_mnemonics,
-    menubar_ui_nomnemonics,
-  ],
-  export: false,
-)
-
-server_sources += gnome.genmarshal(
-  'terminal-marshal',
-  internal: true,
-  install_header: false,
-  prefix: '_terminal_marshal',
-  sources: files(
-    'terminal-marshal.list',
-  ),
-  stdinc: true,
-  valist_marshallers: true,
-)
-
 if get_option('search_provider')
 
   server_sources += files(
@@ -204,11 +240,8 @@ server_incs = [
   src_inc,
 ]
 
-server_cxxflags = version_cxxflags + [
-  '-DTERMINAL_COMPILATION',
-  '-DVTE_DISABLE_DEPRECATION_WARNINGS',
-  '-DTERM_LOCALEDIR="@0@"'.format(gt_prefix / gt_localedir),
-  '-DTERM_PKGLIBDIR="@0@"'.format(gt_prefix / gt_pkglibdir),
+server_cxxflags = common_cxxflags + [
+  '-DTERMINAL_SERVER',
 ]
 
 server_deps = [
@@ -270,6 +303,42 @@ if get_option('search_provider')
   )
 endif # option 'search_provider'
 
+# Preferences
+
+prefs_resources_sources = gnome.compile_resources(
+  'terminal-prefs-resources',
+  'prefs.gresource.xml',
+  c_name: 'terminal',
+  export: false,
+)
+
+prefs_main_sources = app_sources + client_util_sources + debug_sources + i18n_sources + marshal_sources + 
misc_sources + prefs_resources_sources + profiles_sources + settings_dbus_sources + types_sources + 
util_sources + version_sources + files(
+  'prefs-main.cc',
+  'profile-editor.cc',
+  'profile-editor.hh',
+  'terminal-prefs.cc',
+  'terminal-prefs.hh',
+  'terminal-settings-bridge-backend.cc',
+  'terminal-settings-bridge-backend.hh',
+)
+
+prefs_main_cxxflags = common_cxxflags + [
+  '-DTERMINAL_PREFERENCES',
+]
+
+prefs_main_incs = server_incs
+prefs_main_deps = server_deps
+
+prefs_main = executable(
+  gt_name + '-preferences',
+  cpp_args: prefs_main_cxxflags,
+  include_directories: prefs_main_incs,
+  dependencies: prefs_main_deps,
+  install: true,
+  install_dir: gt_prefix / gt_pkglibdir,
+  sources: prefs_main_sources,
+)
+
 # Legacy client
 
 client_sources = client_util_sources + debug_sources + dbus_sources + i18n_sources + profiles_sources + 
types_sources + files(
@@ -288,13 +357,8 @@ client_incs = [
   src_inc,
 ]
 
-client_cxxflags = version_cxxflags + [
-  '-DTERMINAL_COMPILATION',
+client_cxxflags = common_cxxflags + [
   '-DTERMINAL_CLIENT',
-  '-DTERM_DATADIR="@0@"'.format(gt_prefix / gt_datadir),
-  '-DTERM_LOCALEDIR="@0@"'.format(gt_prefix / gt_localedir),
-  '-DTERM_PKGDATADIR="@0@"'.format(gt_prefix / gt_pkgdatadir),
-  '-DTERM_PKGLIBDIR="@0@"'.format(gt_prefix / gt_pkglibdir),
 ]
 
 client_deps = [
diff --git a/src/org.gnome.Terminal.SettingsBridge.xml b/src/org.gnome.Terminal.SettingsBridge.xml
new file mode 100644
index 00000000..f07551e2
--- /dev/null
+++ b/src/org.gnome.Terminal.SettingsBridge.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Introspection 0.1//EN"
+                      "http://www.freedesktop.org/software/dbus/introspection.dtd";>
+<!--
+  Copyright © 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, or (at your option)
+  any later version.
+
+  This program is distributed in the hope conf 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/>.
+-->
+<node>
+  <interface name="org.gnome.Terminal.SettingsBridge">
+    <annotation name="org.gtk.GDBus.C.Name" value="SettingsBridge" />
+
+    <method name="clone_schema">
+      <arg type="s" name="schema_id" direction="in" />
+      <arg type="s" name="path_from" direction="in" />
+      <arg type="s" name="path_to" direction="in" />
+      <arg type="a(sv)" name="extra_prefs" direction="in">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true" />
+      </arg>
+    </method>
+
+    <method name="erase_path">
+      <arg type="s" name="path" direction="in" />
+    </method>
+
+    <method name="get_permission">
+      <arg type="s" name="path" direction="in" />
+      <arg type="v" name="permission" direction="out" />
+    </method>
+
+    <method name="get_writable">
+      <arg type="s" name="key" direction="in" />
+      <arg type="b" name="writable" direction="out" />
+    </method>
+
+    <method name="read">
+      <arg type="s" name="key" direction="in" />
+      <arg type="s" name="type" direction="in" />
+      <arg type="b" name="default" direction="in" />
+      <arg type="av" name="value" direction="out" />
+    </method>
+
+    <method name="read_user_value">
+      <arg type="s" name="key" direction="in" />
+      <arg type="s" name="type" direction="in" />
+      <arg type="av" name="value" direction="out" />
+    </method>
+
+    <method name="reset">
+      <arg type="s" name="key" direction="in" />
+    </method>
+
+    <method name="subscribe">
+      <arg type="s" name="name" direction="in" />
+    </method>
+
+    <method name="sync">
+    </method>
+
+    <method name="unsubscribe">
+      <arg type="s" name="name" direction="in" />
+    </method>
+
+    <method name="write">
+      <arg type="s" name="key" direction="in" />
+      <arg type="av" name="value" direction="in" />
+      <arg type="b" name="success" direction="out" />
+    </method>
+
+    <method name="write_tree">
+      <arg type="s" name="path_prefix" direction="in" />
+      <arg type="a(sav)" name="tree" direction="in">
+        <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true" />
+      </arg>
+      <arg type="b" name="success" direction="out" />
+    </method>
+
+  </interface>
+</node>
diff --git a/src/prefs-main.cc b/src/prefs-main.cc
new file mode 100644
index 00000000..94e9846b
--- /dev/null
+++ b/src/prefs-main.cc
@@ -0,0 +1,264 @@
+/*
+ * Copyright © 2008, 2010, 2011, 2021, 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"
+
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "terminal-app.hh"
+#include "terminal-debug.hh"
+#include "terminal-i18n.hh"
+#include "terminal-defines.hh"
+#include "terminal-libgsystem.hh"
+#include "terminal-settings-bridge-backend.hh"
+#include "terminal-settings-bridge-generated.h"
+
+static char* arg_profile_uuid = nullptr;
+static char* arg_hint = nullptr;
+static int arg_bus_fd = -1;
+static int arg_timestamp = -1;
+
+static const GOptionEntry options[] = {
+  {"profile", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &arg_profile_uuid, "Profile", "UUID"},
+  {"hint", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &arg_hint, "Hint", "HINT"},
+  {"bus-fd", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_INT, &arg_bus_fd, "Bus FD", "FD"},
+  {"timestamp", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_INT, &arg_timestamp, "Timestamp", "VALUE"},
+  {nullptr}
+};
+
+static GSettings*
+profile_from_uuid(TerminalApp* app,
+                  char const* uuid_str) noexcept
+{
+  if (!uuid_str)
+    return nullptr;
+
+  auto const profiles_list = terminal_app_get_profiles_list(app);
+
+  GSettings* profile = nullptr;
+  if (g_str_equal(uuid_str, "default"))
+    profile = terminal_settings_list_ref_default_child(profiles_list);
+  else if (terminal_settings_list_valid_uuid(uuid_str))
+    profile = terminal_settings_list_ref_child(profiles_list, uuid_str);
+
+  return profile;
+}
+
+static void
+preferences_cb(GSimpleAction* action,
+               GVariant* parameter,
+               void* user_data)
+{
+  auto const app = terminal_app_get();
+
+  gs_free char* uuid_str = nullptr;
+  g_variant_lookup(parameter, "profile", "s", &uuid_str);
+
+  gs_unref_object auto profile = profile_from_uuid(app, uuid_str);
+
+  gs_free char* hint_str = nullptr;
+  g_variant_lookup(parameter, "hint", "s", &hint_str);
+
+  guint32 ts = 0;
+  g_variant_lookup(parameter, "timestamp", "u", &ts);
+
+  terminal_app_edit_preferences(app, profile, hint_str, ts);
+}
+
+static void
+connection_closed_cb(GDBusConnection* connection,
+                     gboolean peer_vanished,
+                     GError* error,
+                     void* user_data)
+{
+  auto ptr = reinterpret_cast<void**>(user_data);
+
+  if (error)
+    g_printerr("D-Bus connection closed: %s\n", error->message);
+
+  // As per glib docs, unref the connection at this point
+  g_object_unref(g_steal_pointer(ptr));
+
+  // Exit cleanly
+  auto const app = g_application_get_default();
+  if (app)
+    g_application_quit(app);
+}
+
+int
+main(int argc,
+     char* argv[])
+{
+  // Sanitise environment
+  g_unsetenv("CHARSET");
+  g_unsetenv("DBUS_STARTER_BUS_TYPE");
+  // Not interested in silly debug spew polluting the journal, bug #749195
+  if (g_getenv("G_ENABLE_DIAGNOSTIC") == nullptr)
+    g_setenv("G_ENABLE_DIAGNOSTIC", "0", true);
+  // Maybe: g_setenv("GSETTINGS_BACKEND", "bridge", true);
+
+  if (setlocale(LC_ALL, "") == nullptr) {
+    g_printerr("Locale not supported.\n");
+    return _EXIT_FAILURE_UNSUPPORTED_LOCALE;
+  }
+
+  terminal_i18n_init(true);
+
+  char const* charset = nullptr;
+  if (!g_get_charset(&charset)) {
+    g_printerr("Non UTF-8 locale (%s) is not supported!\n", charset);
+    return _EXIT_FAILURE_NO_UTF8;
+  }
+
+  _terminal_debug_init();
+
+  auto const home_dir = g_get_home_dir();
+  if (home_dir == nullptr || chdir(home_dir) < 0)
+   (void) chdir("/");
+
+  g_set_prgname("gnome-terminal-preferences");
+  g_set_application_name(_("Terminal Preferences"));
+
+  gs_free_error GError *error = nullptr;
+  if (!gtk_init_with_args(&argc, &argv, nullptr, options, nullptr, &error)) {
+    g_printerr("Failed to parse arguments: %s\n", error->message);
+    return _EXIT_FAILURE_GTK_INIT;
+  }
+
+  gs_unref_object GDBusConnection* connection = nullptr;
+  gs_unref_object GSettingsBackend* backend = nullptr;
+  gs_unref_object GSimpleActionGroup* action_group = nullptr;
+  auto export_id = 0u;
+  if (arg_bus_fd != -1) {
+    gs_unref_object auto socket = g_socket_new_from_fd(arg_bus_fd, &error);
+    if (!socket) {
+      g_printerr("Failed to create bridge socket: %s\n", error->message);
+      close(arg_bus_fd);
+      return EXIT_FAILURE;
+    }
+
+    gs_unref_object auto sockconn =
+      g_socket_connection_factory_create_connection(socket);
+    if (!G_IS_IO_STREAM(sockconn)) {
+      g_printerr("Bridge socket has incorrect type %s\n", G_OBJECT_TYPE_NAME(sockconn));
+      return EXIT_FAILURE;
+    }
+
+    connection =
+      g_dbus_connection_new_sync(G_IO_STREAM(sockconn),
+                                 nullptr, // guid=nullptr for the client
+                                 GDBusConnectionFlags(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+                                                      G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING),
+                                 nullptr, // auth observer,
+                                 nullptr, // cancellable,
+                                 &error);
+    if (!connection) {
+      g_printerr("Failed to create bus: %s\n", error->message);
+      return EXIT_FAILURE;
+    }
+
+    GActionEntry const action_entries[] = {
+      { "preferences", preferences_cb, "a{sv}", nullptr, nullptr },
+    };
+
+    action_group = g_simple_action_group_new();
+    g_action_map_add_action_entries(G_ACTION_MAP(action_group),
+                                    action_entries, G_N_ELEMENTS(action_entries),
+                                    nullptr);
+
+    export_id = g_dbus_connection_export_action_group(connection,
+                                                      TERMINAL_PREFERENCES_OBJECT_PATH,
+                                                      G_ACTION_GROUP(action_group),
+                                                      &error);
+    if (export_id == 0) {
+      g_printerr("Failed to export actions: %s\n", error->message);
+      return EXIT_FAILURE;
+    }
+
+    g_dbus_connection_start_message_processing(connection);
+
+    gs_unref_object auto bridge =
+      terminal_settings_bridge_proxy_new_sync
+      (connection,
+       GDBusProxyFlags(
+                       G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START |
+                       G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION |
+                       G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+                       G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES),
+       nullptr, // no name
+       TERMINAL_SETTINGS_BRIDGE_OBJECT_PATH,
+       nullptr, // cancellable
+       &error);
+    if (!bridge) {
+      g_printerr("Failed to create settings bridge proxy: %s\n", error->message);
+      return EXIT_FAILURE;
+    }
+
+    backend = terminal_settings_bridge_backend_new(bridge);
+
+    g_dbus_connection_set_exit_on_close(connection, false);
+    g_signal_connect(connection, "closed", G_CALLBACK(connection_closed_cb), &connection);
+  }
+
+  gs_unref_object auto app =
+    terminal_app_new(TERMINAL_PREFERENCES_APPLICATION_ID,
+                     GApplicationFlags(G_APPLICATION_NON_UNIQUE |
+                                       G_APPLICATION_IS_SERVICE),
+                     backend);
+
+  // Need to startup the application now, otherwise we can't use
+  // gtk_application_add_window() before g_application_run() below.
+  // This should always succeed.
+  if (!g_application_register(G_APPLICATION(app), nullptr, &error)) {
+    g_printerr("Failed to register application: %s\n", error->message);
+    return EXIT_FAILURE;
+  }
+
+  // If started from gnome-terminal-server, the "preferences" action
+  // will be activated to actually show the preferences dialogue. However
+  // if started directly, need to show the dialogue right now.
+  if (!connection) {
+    gs_unref_object GSettings* profile = profile_from_uuid(TERMINAL_APP(app),
+                                                           arg_profile_uuid);
+    if (arg_profile_uuid && !profile)
+      g_printerr("No profile with UUID \"%s\": %s\n", arg_profile_uuid, error->message);
+    return EXIT_FAILURE;
+
+    terminal_app_edit_preferences(TERMINAL_APP(app),
+                                  profile,
+                                  arg_hint,
+                                  unsigned(arg_timestamp));
+  }
+
+  auto const r = g_application_run(app, 0, nullptr);
+
+  if (connection && export_id != 0) {
+    g_dbus_connection_unexport_action_group(connection, export_id);
+    export_id = 0;
+  }
+
+  if (connection &&
+      !g_dbus_connection_flush_sync(connection, nullptr, &error)) {
+      g_printerr("Failed to flush D-Bus connection: %s\n", error->message);
+  }
+
+  return r;
+}
diff --git a/src/prefs.gresource.xml b/src/prefs.gresource.xml
new file mode 100644
index 00000000..db489765
--- /dev/null
+++ b/src/prefs.gresource.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright © 2012, 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, or (at your option)
+  any later version.
+
+  This program is distributed in the hope conf 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/>.
+-->
+<gresources>
+  <gresource prefix="/org/gnome/terminal">
+    <file alias="css/terminal.css" compressed="true">terminal.common.css</file>
+    <file alias="ui/preferences.ui" compressed="true" preprocess="xml-stripblanks">preferences.ui</file>
+  </gresource>
+</gresources>
diff --git a/src/server.cc b/src/server.cc
index 5d7e4991..48c09f33 100644
--- a/src/server.cc
+++ b/src/server.cc
@@ -157,6 +157,7 @@ init_server (int argc,
       g_printerr ("Failed to parse arguments: %s\n", error->message);
       g_error_free (error);
     }
+
     return _EXIT_FAILURE_GTK_INIT;
   }
 
@@ -166,7 +167,7 @@ init_server (int argc,
   }
 
   /* Now we can create the app */
-  GApplication *app = terminal_app_new (app_id);
+  auto const app = terminal_app_new (app_id, G_APPLICATION_IS_SERVICE, nullptr);
   g_free (app_id);
   app_id = nullptr;
 
diff --git a/src/terminal-app.cc b/src/terminal-app.cc
index a06485cf..4dab8d36 100644
--- a/src/terminal-app.cc
+++ b/src/terminal-app.cc
@@ -3,7 +3,7 @@
  * Copyright © 2002 Red Hat, Inc.
  * Copyright © 2002 Sun Microsystems
  * Copyright © 2003 Mariano Suarez-Alvarez
- * Copyright © 2008, 2010, 2011, 2015, 2017 Christian Persch
+ * Copyright © 2008, 2010, 2011, 2015, 2017, 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
@@ -33,18 +33,28 @@
 #include "terminal-app.hh"
 #include "terminal-accels.hh"
 #include "terminal-client-utils.hh"
-#include "terminal-screen.hh"
-#include "terminal-screen-container.hh"
-#include "terminal-window.hh"
 #include "terminal-profiles-list.hh"
 #include "terminal-util.hh"
-#include "profile-editor.hh"
 #include "terminal-schemas.hh"
-#include "terminal-gdbus.hh"
 #include "terminal-defines.hh"
-#include "terminal-prefs.hh"
 #include "terminal-libgsystem.hh"
 
+#ifdef TERMINAL_SERVER
+#include "terminal-gdbus.hh"
+#include "terminal-prefs-process.hh"
+#include "terminal-screen-container.hh"
+#include "terminal-screen.hh"
+#include "terminal-window.hh"
+#endif
+
+#ifdef TERMINAL_PREFERENCES
+#include "terminal-prefs.hh"
+#endif
+
+#ifndef TERMINAL_SERVER
+#undef ENABLE_SEARCH_PROVIDER
+#endif
+
 #ifdef ENABLE_SEARCH_PROVIDER
 #include "terminal-search-provider.hh"
 #endif /* ENABLE_SEARCH_PROVIDER */
@@ -75,6 +85,10 @@
 #error Use a gsettings override instead
 #endif
 
+enum {
+  PROP_SETTINGS_BACKEND = 1,
+};
+
 /*
  * Session state is stored entirely in the RestartCommand command line.
  *
@@ -95,12 +109,9 @@ struct _TerminalApp
 {
   GtkApplication parent_instance;
 
-  GDBusObjectManagerServer *object_manager;
-
   TerminalSettingsList *profiles_list;
 
-  GHashTable *screen_map;
-
+  GSettingsBackend* settings_backend;
   GSettingsSchemaSource* schema_source;
   GSettings *global_settings;
   GSettings *desktop_interface_settings;
@@ -108,6 +119,10 @@ struct _TerminalApp
   GSettings* system_proxy_protocol_settings[4];
   GSettings *gtk_debug_settings;
 
+#ifdef TERMINAL_SERVER
+  GDBusObjectManagerServer *object_manager;
+  GHashTable *screen_map;
+
 #ifdef ENABLE_SEARCH_PROVIDER
   TerminalSearchProvider *search_provider;
 #endif /* ENABLE_SEARCH_PROVIDER */
@@ -126,6 +141,9 @@ struct _TerminalApp
   GdkAtom *clipboard_targets;
   int n_clipboard_targets;
 
+  GWeakRef prefs_process_ref;
+#endif /* TERMINAL_SERVER */
+
   gboolean unified_menu;
   gboolean use_headerbar;
 };
@@ -345,6 +363,7 @@ terminal_app_remove_profile (TerminalApp *app,
   if (default_profile == profile)
     return;
 
+#ifdef TERMINAL_SERVER
   /* First, we need to switch any screen using this profile to the default profile */
   gs_free_list GList *screens = g_hash_table_get_values (app->screen_map);
   for (GList *l = screens; l != nullptr; l = l->next) {
@@ -354,6 +373,7 @@ terminal_app_remove_profile (TerminalApp *app,
 
     terminal_screen_set_profile (screen, default_profile);
   }
+#endif /* TERMINAL_SERVER */
 
   /* Now we can safely remove the profile */
   gs_free char *uuid = terminal_settings_list_dup_uuid_from_child (app->profiles_list, profile);
@@ -379,8 +399,11 @@ terminal_app_theme_variant_changed_cb (GSettings   *settings,
 
 /* Submenus for New Terminal per profile, and to change profiles */
 
+#ifdef TERMINAL_SERVER
+
 static void terminal_app_update_profile_menus (TerminalApp *app);
 
+
 typedef struct {
   char *uuid;
   char *label;
@@ -698,6 +721,70 @@ clipboard_owner_change_cb (GtkClipboard *clipboard,
                                  app);
 }
 
+/* Preferences */
+
+struct PrefsLaunchData {
+  GWeakRef app_ref;
+  char* profile_uuid;
+  char* hint;
+  unsigned timestamp;
+};
+
+static auto
+prefs_launch_data_new(TerminalApp* app,
+                      char const* profile_uuid,
+                      char const* hint,
+                      unsigned timestamp)
+{
+  auto data = g_new(PrefsLaunchData, 1);
+  g_weak_ref_init(&data->app_ref, app);
+  data->profile_uuid = g_strdup(profile_uuid);
+  data->hint = g_strdup(hint);
+  data->timestamp = timestamp;
+
+  return data;
+}
+
+static void
+prefs_launch_data_free(PrefsLaunchData* data)
+{
+  g_weak_ref_clear(&data->app_ref);
+  g_free(data->profile_uuid);
+  g_free(data->hint);
+  g_free(data);
+}
+
+static void
+launch_prefs_cb(GObject* source,
+                GAsyncResult* result,
+                void* user_data)
+{
+  auto const data = reinterpret_cast<PrefsLaunchData*>(user_data);
+  auto const app = reinterpret_cast<TerminalApp*>(g_weak_ref_get(&data->app_ref));
+
+  // @process holds a ref on itself via the g_subprocess_wait_async() call,
+  // so we only keep a weak ref that gets cleared when the process exits.
+  gs_free_error GError* error = nullptr;
+  gs_unref_object auto process = terminal_prefs_process_new_finish(result, &error);
+  if (app)
+    g_weak_ref_init(&app->prefs_process_ref, process);
+
+  if (process) {
+    _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                          "Preferences process launched successfully.\n");
+
+    terminal_prefs_process_show(process,
+                                data->profile_uuid,
+                                data->hint,
+                                data->timestamp);
+  } else {
+    _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                          "Failed to launch preferences process: %s\n", error->message);
+  }
+
+  prefs_launch_data_free(data);
+}
+
 /* Callbacks from former app menu.
  * The preferences one is still used with the "--preferences" cmdline option. */
 
@@ -708,7 +795,7 @@ app_menu_preferences_cb (GSimpleAction *action,
 {
   TerminalApp *app = (TerminalApp*)user_data;
 
-  terminal_app_edit_preferences (app, nullptr, nullptr);
+  terminal_app_edit_preferences (app, nullptr, nullptr, gtk_get_current_event_time());
 }
 
 static void
@@ -742,6 +829,8 @@ app_menu_quit_cb (GSimpleAction *action,
     gtk_widget_destroy (GTK_WIDGET (window));
 }
 
+#endif /* TERMINAL_SERVER */
+
 /* Class implementation */
 
 G_DEFINE_TYPE (TerminalApp, terminal_app, GTK_TYPE_APPLICATION)
@@ -757,14 +846,6 @@ terminal_app_activate (GApplication *application)
 static void
 terminal_app_startup (GApplication *application)
 {
-  TerminalApp *app = TERMINAL_APP (application);
-  const GActionEntry action_entries[] = {
-    { "preferences", app_menu_preferences_cb,   nullptr, nullptr, nullptr },
-    { "help",        app_menu_help_cb,          nullptr, nullptr, nullptr },
-    { "about",       app_menu_about_cb,         nullptr, nullptr, nullptr },
-    { "quit",        app_menu_quit_cb,          nullptr, nullptr, nullptr }
-  };
-
   g_application_set_resource_base_path (application, TERMINAL_RESOURCES_PATH_PREFIX);
 
   G_APPLICATION_CLASS (terminal_app_parent_class)->startup (application);
@@ -772,11 +853,21 @@ terminal_app_startup (GApplication *application)
   /* Need to set the WM class (bug #685742) */
   gdk_set_program_class("Gnome-terminal");
 
+  app_load_css (application);
+
+#ifdef TERMINAL_SERVER
+  GActionEntry const action_entries[] = {
+    { "preferences", app_menu_preferences_cb,   nullptr, nullptr, nullptr },
+    { "help",        app_menu_help_cb,          nullptr, nullptr, nullptr },
+    { "about",       app_menu_about_cb,         nullptr, nullptr, nullptr },
+    { "quit",        app_menu_quit_cb,          nullptr, nullptr, nullptr }
+  };
+
   g_action_map_add_action_entries (G_ACTION_MAP (application),
                                    action_entries, G_N_ELEMENTS (action_entries),
                                    application);
 
-  app_load_css (application);
+  auto const app = TERMINAL_APP(application);
 
   /* Figure out whether the shell shows the menubar */
   gboolean shell_shows_menubar;
@@ -796,22 +887,42 @@ terminal_app_startup (GApplication *application)
     gtk_application_set_menubar (GTK_APPLICATION (app),
                                  terminal_app_get_menubar (app));
 
+#endif /* TERMINAL_SERVER */
+
   _terminal_debug_print (TERMINAL_DEBUG_SERVER, "Startup complete\n");
 }
 
 /* GObjectClass impl */
 
 static void
-terminal_app_init (TerminalApp *app)
+terminal_app_init (TerminalApp* app)
 {
+#ifdef TERMINAL_SERVER
+  g_weak_ref_init(&app->prefs_process_ref, nullptr);
+
+  app->screen_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nullptr);
+#endif
+}
+
+static void
+terminal_app_constructed(GObject *object)
+{
+  auto app = TERMINAL_APP(object);
+
+  G_OBJECT_CLASS(terminal_app_parent_class)->constructed(object);
+
   terminal_app_init_debug ();
 
   gtk_window_set_default_icon_name (GNOME_TERMINAL_ICON_NAME);
 
+  if (app->settings_backend == nullptr)
+    app->settings_backend = g_settings_backend_get_default ();
+
   app->schema_source = terminal_g_settings_schema_source_get_default();
 
   /* Desktop proxy settings */
-  app->system_proxy_settings = terminal_g_settings_new(app->schema_source,
+  app->system_proxy_settings = terminal_g_settings_new(app->settings_backend,
+                                                       app->schema_source,
                                                        SYSTEM_PROXY_SETTINGS_SCHEMA);
 
   /* Since there is no way to get the schema ID of a child schema, we cannot
@@ -822,28 +933,35 @@ terminal_app_init (TerminalApp *app)
    * we construct the child GSettings directly.
    */
   app->system_proxy_protocol_settings[TERMINAL_PROXY_HTTP] =
-    terminal_g_settings_new(app->schema_source,
+    terminal_g_settings_new(app->settings_backend,
+                            app->schema_source,
                             SYSTEM_HTTP_PROXY_SETTINGS_SCHEMA);
   app->system_proxy_protocol_settings[TERMINAL_PROXY_HTTPS] =
-    terminal_g_settings_new(app->schema_source,
+    terminal_g_settings_new(app->settings_backend,
+                            app->schema_source,
                             SYSTEM_HTTPS_PROXY_SETTINGS_SCHEMA);
   app->system_proxy_protocol_settings[TERMINAL_PROXY_FTP] =
-    terminal_g_settings_new(app->schema_source,
+    terminal_g_settings_new(app->settings_backend,
+                            app->schema_source,
                             SYSTEM_FTP_PROXY_SETTINGS_SCHEMA);
   app->system_proxy_protocol_settings[TERMINAL_PROXY_SOCKS] =
-    terminal_g_settings_new(app->schema_source,
+    terminal_g_settings_new(app->settings_backend,
+                            app->schema_source,
                             SYSTEM_SOCKS_PROXY_SETTINGS_SCHEMA);
 
   /* Desktop Interface settings */
-  app->desktop_interface_settings = terminal_g_settings_new(app->schema_source,
+  app->desktop_interface_settings = terminal_g_settings_new(app->settings_backend,
+                                                            app->schema_source,
                                                             DESKTOP_INTERFACE_SETTINGS_SCHEMA);
 
   /* Terminal global settings */
-  app->global_settings = terminal_g_settings_new(app->schema_source,
+  app->global_settings = terminal_g_settings_new(app->settings_backend,
+                                                 app->schema_source,
                                                  TERMINAL_SETTING_SCHEMA);
 
   /* Gtk debug settings */
-  app->gtk_debug_settings = terminal_g_settings_new(app->schema_source,
+  app->gtk_debug_settings = terminal_g_settings_new(app->settings_backend,
+                                                    app->schema_source,
                                                     GTK_DEBUG_SETTING_SCHEMA);
 
   /* These are internal settings that exists only for distributions
@@ -860,6 +978,7 @@ terminal_app_init (TerminalApp *app)
                     G_CALLBACK (terminal_app_theme_variant_changed_cb),
                     gtk_settings);
 
+#ifdef TERMINAL_SERVER
   /* Clipboard targets */
   GdkDisplay *display = gdk_display_get_default ();
   app->clipboard = gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD);
@@ -872,14 +991,15 @@ terminal_app_init (TerminalApp *app)
       !gdk_display_supports_selection_notification (display))
     g_printerr ("Display does not support owner-change; copy/paste will be broken!\n");
 #endif
+#endif /* TERMINAL_SERVER */
 
   /* Get the profiles */
-  app->profiles_list = terminal_profiles_list_new(app->schema_source);
-
-  app->screen_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, nullptr);
+  app->profiles_list = terminal_profiles_list_new(app->settings_backend,
+                                                  app->schema_source);
 
-  gs_unref_object GSettings *settings =
-    terminal_g_settings_new_with_path(app->schema_source,
+  gs_unref_object auto settings =
+    terminal_g_settings_new_with_path(app->settings_backend,
+                                      app->schema_source,
                                       TERMINAL_KEYBINDINGS_SCHEMA,
                                       TERMINAL_KEYBINDINGS_SCHEMA_PATH);
   terminal_accels_init (G_APPLICATION (app), settings, app->use_headerbar);
@@ -888,8 +1008,9 @@ terminal_app_init (TerminalApp *app)
 static void
 terminal_app_finalize (GObject *object)
 {
-  TerminalApp *app = TERMINAL_APP (object);
+  auto app = TERMINAL_APP(object);
 
+#ifdef TERMINAL_SERVER
   g_signal_handlers_disconnect_by_func (app->clipboard,
                                         (void*)clipboard_owner_change_cb,
                                         app);
@@ -899,6 +1020,7 @@ terminal_app_finalize (GObject *object)
                                         (void*)terminal_app_update_profile_menus,
                                         app);
   g_hash_table_destroy (app->screen_map);
+#endif
 
   g_object_unref (app->global_settings);
   g_object_unref (app->desktop_interface_settings);
@@ -907,7 +1029,9 @@ terminal_app_finalize (GObject *object)
     g_object_unref(app->system_proxy_protocol_settings[i]);
   g_clear_object (&app->gtk_debug_settings);
   g_settings_schema_source_unref(app->schema_source);
+  g_clear_object (&app->settings_backend);
 
+#ifdef TERMINAL_SERVER
   g_clear_object (&app->menubar);
   g_clear_object (&app->menubar_new_terminal_section);
   g_clear_object (&app->menubar_set_profile_section);
@@ -916,11 +1040,40 @@ terminal_app_finalize (GObject *object)
   g_clear_object (&app->headermenu_set_profile_section);
   g_clear_object (&app->set_profile_menu);
 
+  {
+    gs_unref_object auto process = 
reinterpret_cast<TerminalPrefsProcess*>(g_weak_ref_get(&app->prefs_process_ref));
+    if (process)
+      terminal_prefs_process_abort(process);
+  }
+
+  g_weak_ref_clear(&app->prefs_process_ref);
+#endif /* TERMINAL_SERVER */
+
   terminal_accels_shutdown ();
 
   G_OBJECT_CLASS (terminal_app_parent_class)->finalize (object);
 }
 
+static void
+terminal_app_set_property(GObject* object,
+                          guint prop_id,
+                          GValue const* value,
+                          GParamSpec* pspec)
+{
+  auto app = TERMINAL_APP(object);
+
+  switch (prop_id) {
+  case PROP_SETTINGS_BACKEND:
+    app->settings_backend = G_SETTINGS_BACKEND(g_value_dup_object(value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    break;
+  }
+}
+
+#ifdef TERMINAL_SERVER
+
 static gboolean
 terminal_app_dbus_register (GApplication    *application,
                             GDBusConnection *connection,
@@ -991,18 +1144,33 @@ terminal_app_dbus_unregister (GApplication    *application,
                                                                     object_path);
 }
 
+#endif /* TERMINAL_SERVER */
+
 static void
 terminal_app_class_init (TerminalAppClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GApplicationClass *g_application_class = G_APPLICATION_CLASS (klass);
 
+  object_class->constructed = terminal_app_constructed;
   object_class->finalize = terminal_app_finalize;
+  object_class->set_property = terminal_app_set_property;
+
+  g_object_class_install_property
+    (object_class,
+     PROP_SETTINGS_BACKEND,
+     g_param_spec_object("settings-backend", nullptr, nullptr,
+                         G_TYPE_SETTINGS_BACKEND,
+                         GParamFlags(G_PARAM_WRITABLE |
+                                     G_PARAM_CONSTRUCT_ONLY |
+                                     G_PARAM_STATIC_STRINGS)));
 
   g_application_class->activate = terminal_app_activate;
   g_application_class->startup = terminal_app_startup;
+#ifdef TERMINAL_SERVER
   g_application_class->dbus_register = terminal_app_dbus_register;
   g_application_class->dbus_unregister = terminal_app_dbus_unregister;
+#endif
 
   signals[CLIPBOARD_TARGETS_CHANGED] =
     g_signal_new (I_("clipboard-targets-changed"),
@@ -1016,18 +1184,21 @@ terminal_app_class_init (TerminalAppClass *klass)
 
 /* Public API */
 
-GApplication *
-terminal_app_new (const char *app_id)
+GApplication*
+terminal_app_new(char const* app_id,
+                 GApplicationFlags flags,
+                 GSettingsBackend* backend)
 {
-  const GApplicationFlags flags = G_APPLICATION_IS_SERVICE;
-
   return reinterpret_cast<GApplication*>
     (g_object_new (TERMINAL_TYPE_APP,
                   "application-id", app_id ? app_id : TERMINAL_APPLICATION_ID,
                   "flags", flags,
+                   "settings-backend", backend,
                   nullptr));
 }
 
+#ifdef TERMINAL_SERVER
+
 TerminalScreen *
 terminal_app_get_screen_by_uuid (TerminalApp *app,
                                  const char  *uuid)
@@ -1149,12 +1320,34 @@ terminal_app_get_clipboard_targets (TerminalApp *app,
   return app->clipboard_targets;
 }
 
+#endif /* TERMINAL_SERVER */
+
 void
-terminal_app_edit_preferences (TerminalApp     *app,
-                               GSettings       *profile,
-                               const char      *widget_name)
+terminal_app_edit_preferences(TerminalApp* app,
+                              GSettings* profile,
+                              char const* hint,
+                              unsigned timestamp)
 {
-  terminal_prefs_show_preferences (profile, widget_name);
+#ifdef TERMINAL_SERVER
+  gs_free char* uuid = nullptr;
+  if (profile)
+    uuid = terminal_settings_list_dup_uuid_from_child (app->profiles_list, profile);
+
+  gs_unref_object auto process = 
reinterpret_cast<TerminalPrefsProcess*>(g_weak_ref_get(&app->prefs_process_ref));
+  if (process) {
+    terminal_prefs_process_show(process,
+                                uuid,
+                                hint,
+                                timestamp);
+  } else {
+    terminal_prefs_process_new_async(nullptr, // cancellable,
+                                     GAsyncReadyCallback(launch_prefs_cb),
+                                     prefs_launch_data_new(app, uuid, hint, timestamp));
+  }
+#endif /* TERMINAL_SERVER */
+#ifdef TERMINAL_PREFERENCES
+  terminal_prefs_show_preferences(profile, hint, timestamp);
+#endif
 }
 
 /**
@@ -1168,6 +1361,8 @@ terminal_app_get_profiles_list (TerminalApp *app)
   return app->profiles_list;
 }
 
+#ifdef TERMINAL_SERVER
+
 /**
  * terminal_app_get_menubar:
  * @app: a #TerminalApp
@@ -1222,6 +1417,20 @@ terminal_app_get_profile_section (TerminalApp *app)
   return G_MENU_MODEL (app->set_profile_menu);
 }
 
+#endif /* TERMINAL_SERVER */
+
+/**
+ * terminal_app_get_settings_backend:
+ * @app: a #TerminalApp
+ *
+ * Returns: (tranfer none): the #GSettingsBackend to use for all #GSettings instances
+ */
+GSettingsBackend*
+terminal_app_get_settings_backend(TerminalApp *app)
+{
+  return app->settings_backend;
+}
+
 /**
  * terminal_app_get_schema_source:
  * @app: a #TerminalApp
@@ -1310,9 +1519,8 @@ terminal_app_get_system_font (TerminalApp *app)
   return pango_font_description_from_string (font);
 }
 
-/**
- * FIXME
- */
+#ifdef TERMINAL_SERVER
+
 GDBusObjectManagerServer *
 terminal_app_get_object_manager (TerminalApp *app)
 {
@@ -1320,6 +1528,8 @@ terminal_app_get_object_manager (TerminalApp *app)
   return app->object_manager;
 }
 
+#endif /* TERMINAL_SERVER */
+
 gboolean
 terminal_app_get_menu_unified (TerminalApp *app)
 {
diff --git a/src/terminal-app.hh b/src/terminal-app.hh
index b86aea5d..0437a882 100644
--- a/src/terminal-app.hh
+++ b/src/terminal-app.hh
@@ -46,7 +46,9 @@ typedef struct _TerminalApp TerminalApp;
 
 GType terminal_app_get_type (void);
 
-GApplication *terminal_app_new (const char *app_id);
+GApplication *terminal_app_new (const char *app_id,
+                                GApplicationFlags flags,
+                                GSettingsBackend* backend);
 
 #define terminal_app_get (TerminalApp *) g_application_get_default
 
@@ -58,7 +60,8 @@ GdkAtom *terminal_app_get_clipboard_targets (TerminalApp *app,
 
 void terminal_app_edit_preferences (TerminalApp *app,
                                     GSettings   *profile,
-                                    const char  *widget_name);
+                                    const char  *widget_name,
+                                    unsigned timestamp);
 
 char *terminal_app_new_profile (TerminalApp *app,
                                 GSettings   *default_base_profile,
@@ -109,6 +112,8 @@ typedef enum {
   TERMINAL_PROXY_SOCKS = 3,
 } TerminalProxyProtocol;
 
+GSettingsBackend* terminal_app_get_settings_backend(TerminalApp* app);
+
 GSettingsSchemaSource* terminal_app_get_schema_source(TerminalApp* app);
 
 GSettings *terminal_app_get_global_settings (TerminalApp *app);
diff --git a/src/terminal-client-utils.cc b/src/terminal-client-utils.cc
index acd1719f..d20f984a 100644
--- a/src/terminal-client-utils.cc
+++ b/src/terminal-client-utils.cc
@@ -21,10 +21,16 @@
 
 #include "config.h"
 
+#include <unistd.h>
 #include "terminal-client-utils.hh"
+#include "terminal-debug.hh"
 #include "terminal-defines.hh"
 #include "terminal-libgsystem.hh"
 
+#ifdef TERMINAL_PREFERENCES
+#include "terminal-debug.hh"
+#endif
+
 #include <string.h>
 
 #include <gio/gio.h>
@@ -34,6 +40,98 @@
 #include <gdk/gdkx.h>
 #endif
 
+static char*
+get_binary_path_if_uninstalled(char const* install_dir) noexcept
+{
+#ifdef __linux__
+  char buf[1024];
+  auto const r = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
+  if (r < 0 || r >= ssize_t(sizeof(buf)))
+    return nullptr;
+
+  buf[r] = '\0'; // nul terminate
+
+  gs_free auto path = g_path_get_dirname(buf);
+  if (!path)
+    return nullptr;
+
+  if (g_str_equal(path, install_dir))
+    return nullptr;
+
+  return reinterpret_cast<char*>(g_steal_pointer(&path));
+#else
+  return nullptr;
+#endif /* __linux__ */
+}
+
+static char*
+get_path_if_uninstalled(char const* exe_install_dir,
+                        char const* file_name,
+                        GFileTest tests)
+{
+  gs_free auto path = get_binary_path_if_uninstalled(exe_install_dir);
+  if (!path)
+    return nullptr;
+
+  gs_free auto file = g_build_filename(path, file_name, nullptr);
+  if (!g_file_test(file, GFileTest(tests | G_FILE_TEST_EXISTS)))
+    return nullptr;
+
+  return reinterpret_cast<char*>(g_steal_pointer(&path));
+}
+
+/**
+ * terminal_client_find_file_uninstalled:
+ * @exe_install_dir: the directory where the current exe is installed
+ * @file_install_dir: the directory where the file to locate is installed
+ * @file_name: the name of the file to locate
+ * @tests: extra tests from #GFileTest
+ *
+ * Tries to locate the directory that contains @file_name in a build directory,
+ * and returns the directory.  If @file_name is not found, returns the
+ * installed location for it.
+ */
+char*
+terminal_client_get_directory_uninstalled(char const* exe_install_dir,
+                                          char const *file_install_dir,
+                                          char const* file_name,
+                                          GFileTest tests)
+{
+#ifdef ENABLE_DEBUG
+  auto path = get_path_if_uninstalled(exe_install_dir, file_name, tests);
+  if (path)
+    return path;
+#endif /* ENABLE_DEBUG */
+
+  return g_strdup(file_install_dir);
+}
+
+/**
+ * terminal_client_find_file_uninstalled:
+ * @exe_install_dir: the directory where the current exe is installed
+ * @file_install_dir: the directory where the file to locate is installed
+ * @file_name: the name of the file to locate
+ * @tests: extra tests from #GFileTest
+ *
+ * Tries to locate the file @file_name in a build directory, and
+ * returns a full path to it.  If @file_name is not found, returns the
+ * installed location for it.
+ */
+char*
+terminal_client_get_file_uninstalled(char const* exe_install_dir,
+                                     char const *file_install_dir,
+                                     char const* file_name,
+                                     GFileTest tests)
+{
+#ifdef ENABLE_DEBUG
+  gs_free auto path = get_path_if_uninstalled(exe_install_dir, file_name, tests);
+  if (path)
+    return g_build_filename(path, file_name, nullptr);
+#endif /* ENABLE_DEBUG */
+
+  return g_build_filename(file_install_dir, file_name, nullptr);
+}
+
 /**
  * terminal_client_append_create_instance_options:
  * @builder: a #GVariantBuilder of #GVariantType "a{sv}"
@@ -333,8 +431,56 @@ out:
   return nullptr;
 }
 
+#ifdef ENABLE_DEBUG
+
+static gboolean
+settings_change_event_cb(GSettings* settings,
+                         void* keys,
+                         int n_keys,
+                         void* data)
+{
+  gs_free char* schema_id = nullptr;
+  gs_free char* path = nullptr;
+  g_object_get(settings,
+               "schema-id", &schema_id,
+               "path", &path,
+               nullptr);
+
+  auto const qkeys = reinterpret_cast<GQuark*>(keys);
+  for (auto i = 0; i < n_keys; ++i) {
+    auto key = g_quark_to_string(qkeys[i]);
+    _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                          "Bridge backend ::change-event schema %s path %s key %s\n",
+                          schema_id, path, key);
+  }
+
+  return false; // propagate
+}
+
+static gboolean
+settings_writable_change_event_cb(GSettings* settings,
+                                  char const* key,
+                                  void* data)
+{
+  gs_free char* schema_id = nullptr;
+  gs_free char* path = nullptr;
+  g_object_get(settings,
+               "schema-id", &schema_id,
+               "path", &path,
+               nullptr);
+
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge backend ::writeable-change-event schema %s path %s key %s\n",
+                        schema_id, path, key);
+
+  return false; // propagate
+}
+
+#endif /* ENABLE_DEBUG */
+
 GSettings*
-terminal_g_settings_new_with_path (GSettingsSchemaSource* source,
+terminal_g_settings_new_with_path (GSettingsBackend* backend,
+                                   GSettingsSchemaSource* source,
                                    char const* schema_id,
                                    char const* path)
 {
@@ -344,14 +490,39 @@ terminal_g_settings_new_with_path (GSettingsSchemaSource* source,
                                     TRUE /* recursive */);
   g_assert_nonnull(schema);
 
-  return g_settings_new_full(schema,
-                             nullptr /* default backend */,
-                             path);
+  auto const settings = g_settings_new_full(schema,
+                                            backend,
+                                            path);
+
+#ifdef ENABLE_DEBUG
+  _TERMINAL_DEBUG_IF(TERMINAL_DEBUG_BRIDGE) {
+
+    _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                          "Creating GSettings for schema %s at %s with backend %s\n",
+                          schema_id, path,
+                          backend ? G_OBJECT_TYPE_NAME(backend) : "(default)");
+
+    if (backend != nullptr &&
+        g_str_equal(G_OBJECT_TYPE_NAME(backend), "TerminalSettingsBridgeBackend")) {
+      g_signal_connect(settings,
+                       "change-event",
+                       G_CALLBACK(settings_change_event_cb),
+                       nullptr);
+      g_signal_connect(settings,
+                       "writable-change-event",
+                       G_CALLBACK(settings_writable_change_event_cb),
+                       nullptr);
+    }
+  }
+#endif /* ENABLE_DEBUG */
+
+  return settings;
 }
 
 GSettings*
-terminal_g_settings_new(GSettingsSchemaSource* source,
+terminal_g_settings_new(GSettingsBackend* backend,
+                        GSettingsSchemaSource* source,
                         char const* schema_id)
 {
-  return terminal_g_settings_new_with_path(source, schema_id, nullptr);
+  return terminal_g_settings_new_with_path(backend, source, schema_id, nullptr);
 }
diff --git a/src/terminal-client-utils.hh b/src/terminal-client-utils.hh
index 88a6fd56..ed556de5 100644
--- a/src/terminal-client-utils.hh
+++ b/src/terminal-client-utils.hh
@@ -23,6 +23,16 @@
 
 G_BEGIN_DECLS
 
+char* terminal_client_get_directory_uninstalled(char const* exe_install_dir,
+                                                char const *file_install_dir,
+                                                char const* file_name,
+                                                GFileTest tests);
+
+char* terminal_client_get_file_uninstalled(char const* exe_install_dir,
+                                           char const *file_install_dir,
+                                           char const* file_name,
+                                           GFileTest tests);
+
 void terminal_client_append_create_instance_options (GVariantBuilder *builder,
                                                      const char      *display_name,
                                                      const char      *startup_id,
@@ -55,10 +65,12 @@ char const* const* terminal_client_get_environment_prefix_filters (void);
 
 char** terminal_client_filter_environment           (char** envv) G_GNUC_MALLOC;
 
-GSettings* terminal_g_settings_new (GSettingsSchemaSource* source,
+GSettings* terminal_g_settings_new (GSettingsBackend* backend,
+                                    GSettingsSchemaSource* source,
                                     char const* schema_id);
 
-GSettings* terminal_g_settings_new_with_path (GSettingsSchemaSource* source,
+GSettings* terminal_g_settings_new_with_path (GSettingsBackend* backend,
+                                              GSettingsSchemaSource* source,
                                               char const* schema_id,
                                               char const* path);
 
diff --git a/src/terminal-dconf.cc b/src/terminal-dconf.cc
new file mode 100644
index 00000000..0a46a6fc
--- /dev/null
+++ b/src/terminal-dconf.cc
@@ -0,0 +1,124 @@
+/*
+ * Copyright © 2013, 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"
+
+#include "terminal-dconf.hh"
+#include "terminal-libgsystem.hh"
+
+// See https://gitlab.gnome.org/GNOME/dconf/-/issues/23
+extern "C" {
+#include <dconf.h>
+}
+
+gboolean
+terminal_dconf_backend_is_dconf(GSettingsBackend* backend)
+{
+  return g_str_equal(G_OBJECT_TYPE_NAME(backend), "DConfSettingsBackend");
+}
+
+static bool
+clone_schema(GSettingsSchemaSource* schema_source,
+             char const* schema_id,
+             char const* path,
+             char const* new_path,
+             DConfClient** client,
+             DConfChangeset** changeset)
+{
+  gs_unref_settings_schema auto schema =
+    g_settings_schema_source_lookup(schema_source, schema_id, true);
+   /* shouldn't really happen ever */
+  if (schema == nullptr)
+    return false;
+
+  *client = dconf_client_new();
+  *changeset = dconf_changeset_new();
+
+  gs_strfreev auto keys = g_settings_schema_list_keys(schema);
+
+  for (auto i = 0; keys[i]; i++) {
+    gs_free auto rkey = g_strconcat(path, keys[i], nullptr);
+    gs_unref_variant auto value = dconf_client_read(*client, rkey);
+    if (value) {
+      gs_free auto wkey = g_strconcat(new_path, keys[i], nullptr);
+      dconf_changeset_set(*changeset, wkey, value);
+    }
+  }
+
+  return true;
+}
+
+void
+terminal_dconf_clone_schema(GSettingsSchemaSource* schema_source,
+                            char const* schema_id,
+                            char const* path,
+                            char const* new_path,
+                            char const* first_key,
+                            ...)
+{
+  gs_unref_object DConfClient* client = nullptr;
+  DConfChangeset* changeset = nullptr;
+  if (!clone_schema(schema_source, schema_id, path, new_path, &client, &changeset))
+    return;
+
+  va_list args;
+  va_start(args, first_key);
+  while (first_key != nullptr) {
+    auto const type = va_arg(args, char const*);
+    auto const value = g_variant_new_va(type, nullptr, &args);
+    gs_free auto wkey = g_strconcat(new_path, first_key, nullptr);
+
+    dconf_changeset_set(changeset, wkey, value);
+    first_key = va_arg(args, char const*);
+  }
+  va_end(args);
+
+  dconf_client_change_sync(client, changeset, nullptr, nullptr, nullptr);
+  dconf_changeset_unref(changeset);
+}
+
+void
+terminal_dconf_clone_schemav(GSettingsSchemaSource*schema_source,
+                             char const* schema_id,
+                             char const* path,
+                             char const* new_path,
+                             GVariant* asv)
+{
+  gs_unref_object DConfClient* client = nullptr;
+  DConfChangeset* changeset = nullptr;
+  if (!clone_schema(schema_source, schema_id, path, new_path, &client, &changeset))
+    return;
+
+  auto iter = GVariantIter{};
+  g_variant_iter_init(&iter, asv);
+  char* key = nullptr;
+  GVariant* value = nullptr;
+  while (g_variant_iter_loop(&iter, "(&sv)", &key, &value)) {
+    gs_free auto wkey = g_strconcat(new_path, key, nullptr);
+    dconf_changeset_set(changeset, wkey, value);
+  }
+
+  dconf_client_change_sync(client, changeset, nullptr, nullptr, nullptr);
+  dconf_changeset_unref(changeset);
+}
+
+void
+terminal_dconf_erase_path(char const* path)
+{
+  gs_unref_object DConfClient* client = dconf_client_new();
+  dconf_client_write_sync(client, path, nullptr, nullptr, nullptr, nullptr);
+}
diff --git a/src/terminal-dconf.hh b/src/terminal-dconf.hh
new file mode 100644
index 00000000..7c54ad99
--- /dev/null
+++ b/src/terminal-dconf.hh
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2013, 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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+gboolean terminal_dconf_backend_is_dconf(GSettingsBackend* backend);
+
+void terminal_dconf_clone_schema(GSettingsSchemaSource*schema_source,
+                                 char const* schema_id,
+                                 char const* path,
+                                 char const* new_path,
+                                 char const* first_pref,
+                                 ...);
+
+void terminal_dconf_clone_schemav(GSettingsSchemaSource*schema_source,
+                                  char const* schema_id,
+                                  char const* path,
+                                  char const* new_path,
+                                  GVariant* asv);
+
+void terminal_dconf_erase_path(char const* path);
+
+G_END_DECLS
diff --git a/src/terminal-debug.cc b/src/terminal-debug.cc
index 52acc70c..61814819 100644
--- a/src/terminal-debug.cc
+++ b/src/terminal-debug.cc
@@ -38,6 +38,7 @@ _terminal_debug_init(void)
     { "profile",       TERMINAL_DEBUG_PROFILE       },
     { "settings-list", TERMINAL_DEBUG_SETTINGS_LIST },
     { "search",        TERMINAL_DEBUG_SEARCH        },
+    { "bridge",        TERMINAL_DEBUG_BRIDGE        },
   };
 
   _terminal_debug_flags = TerminalDebugFlags(g_parse_debug_string (g_getenv ("GNOME_TERMINAL_DEBUG"),
diff --git a/src/terminal-debug.hh b/src/terminal-debug.hh
index 0fafcc3a..c1a4113c 100644
--- a/src/terminal-debug.hh
+++ b/src/terminal-debug.hh
@@ -34,7 +34,8 @@ typedef enum {
   TERMINAL_DEBUG_PROCESSES     = 1 << 6,
   TERMINAL_DEBUG_PROFILE       = 1 << 7,
   TERMINAL_DEBUG_SETTINGS_LIST = 1 << 8,
-  TERMINAL_DEBUG_SEARCH        = 1 << 9
+  TERMINAL_DEBUG_SEARCH        = 1 << 9,
+  TERMINAL_DEBUG_BRIDGE        = 1 << 10,
 } TerminalDebugFlags;
 
 void _terminal_debug_init(void);
diff --git a/src/terminal-defines.hh b/src/terminal-defines.hh
index b9ffba25..cd85d60c 100644
--- a/src/terminal-defines.hh
+++ b/src/terminal-defines.hh
@@ -40,9 +40,17 @@ enum {
 
 #define TERMINAL_SEARCH_PROVIDER_PATH           TERMINAL_OBJECT_PATH_PREFIX "/SearchProvider"
 
+#define TERMINAL_SETTINGS_BRIDGE_INTERFACE_NAME "org.gnome.Terminal.SettingsBridge0"
+#define TERMINAL_SETTINGS_BRIDGE_OBJECT_PATH    TERMINAL_OBJECT_PATH_PREFIX "/SettingsBridge"
+
+#define TERMINAL_PREFERENCES_APPLICATION_ID     TERMINAL_APPLICATION_ID ".Preferences"
+#define TERMINAL_PREFERENCES_OBJECT_PATH        TERMINAL_OBJECT_PATH_PREFIX  "/Preferences"
+
 #define TERMINAL_ENV_SERVICE_NAME               "GNOME_TERMINAL_SERVICE"
 #define TERMINAL_ENV_SCREEN                     "GNOME_TERMINAL_SCREEN"
 
+#define TERMINAL_PREFERENCES_BINARY_NAME        "gnome-terminal-preferences"
+
 G_END_DECLS
 
 #endif /* !TERMINAL_DEFINES_H */
diff --git a/src/terminal-libgsystem.hh b/src/terminal-libgsystem.hh
index dfa00558..e13c5a9c 100644
--- a/src/terminal-libgsystem.hh
+++ b/src/terminal-libgsystem.hh
@@ -58,6 +58,7 @@ GS_DEFINE_CLEANUP_FUNCTION0(GKeyFile*, gs_local_key_file_unref, g_key_file_unref
 GS_DEFINE_CLEANUP_FUNCTION0(GList*, gs_local_list_free, g_list_free)
 GS_DEFINE_CLEANUP_FUNCTION0(GMatchInfo*, gs_local_match_info_free, g_match_info_free)
 GS_DEFINE_CLEANUP_FUNCTION0(GObject*, gs_local_obj_unref, g_object_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GOptionContext*, gs_local_option_context_free, g_option_context_free)
 GS_DEFINE_CLEANUP_FUNCTION0(GPtrArray*, gs_local_ptrarray_unref, g_ptr_array_unref)
 GS_DEFINE_CLEANUP_FUNCTION0(GRegex*, gs_local_regex_unref, g_regex_unref)
 GS_DEFINE_CLEANUP_FUNCTION0(GSettingsSchema*, gs_local_settings_schema_unref, g_settings_schema_unref)
@@ -263,6 +264,16 @@ static inline void gs_local_gstring_free (void *v) \
  */
 #define gs_free_gstring __attribute__ ((cleanup(gs_local_gstring_free)))
 
+/**
+ * gs_free_option_context:
+ *
+ * Call g_regex_unref() on a variable location when it goes out of
+ * scope.  Note that unlike g_option_context_free(), the variable may be
+ * %NULL.
+
+ */
+#define gs_free_option_context __attribute__ ((cleanup(gs_local_option_context_free)))
+
 G_END_DECLS
 
 #endif
diff --git a/src/terminal-options.cc b/src/terminal-options.cc
index 04c55893..e2e562dc 100644
--- a/src/terminal-options.cc
+++ b/src/terminal-options.cc
@@ -111,7 +111,7 @@ static TerminalSettingsList *
 terminal_options_ensure_profiles_list (TerminalOptions *options)
 {
   if (options->profiles_list == nullptr)
-    options->profiles_list = terminal_profiles_list_new(g_settings_schema_source_get_default());
+    options->profiles_list = terminal_profiles_list_new(nullptr, nullptr);
 
   return options->profiles_list;
 }
diff --git a/src/terminal-prefs-process.cc b/src/terminal-prefs-process.cc
new file mode 100644
index 00000000..00994a75
--- /dev/null
+++ b/src/terminal-prefs-process.cc
@@ -0,0 +1,511 @@
+/*
+ * Copyright © 2020, 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"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+
+#include <glib.h>
+
+#include "terminal-settings-bridge-impl.hh"
+
+#include "terminal-app.hh"
+#include "terminal-client-utils.hh"
+#include "terminal-debug.hh"
+#include "terminal-defines.hh"
+#include "terminal-intl.hh"
+#include "terminal-libgsystem.hh"
+#include "terminal-prefs-process.hh"
+
+#include "terminal-settings-bridge-generated.h"
+
+struct _TerminalPrefsProcess {
+  GObject parent_instance;
+
+  GSubprocess* subprocess;
+  GCancellable* cancellable;
+  GDBusConnection *connection;
+  TerminalSettingsBridgeImpl* bridge_impl;
+};
+
+struct _TerminalPrefsProcessClass {
+  GObjectClass parent_class;
+
+  // Signals
+  void (*exited)(TerminalPrefsProcess* process,
+                 int status);
+};
+
+enum {
+  SIGNAL_EXITED,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+// helper functions
+
+template<typename T>
+inline constexpr auto
+IMPL(T* that) noexcept
+{
+  return reinterpret_cast<TerminalPrefsProcess*>(that);
+}
+
+// BEGIN copied from vte/src/libc-glue.hh
+
+static inline int
+fd_get_descriptor_flags(int fd) noexcept
+{
+        auto flags = int{};
+        do {
+                flags = fcntl(fd, F_GETFD);
+        } while (flags == -1 && errno == EINTR);
+
+        return flags;
+}
+
+static inline int
+fd_set_descriptor_flags(int fd,
+                        int flags) noexcept
+{
+        auto r = int{};
+        do {
+                r = fcntl(fd, F_SETFD, flags);
+        } while (r == -1 && errno == EINTR);
+
+        return r;
+}
+
+static inline int
+fd_change_descriptor_flags(int fd,
+                           int set_flags,
+                           int unset_flags) noexcept
+{
+        auto const flags = fd_get_descriptor_flags(fd);
+        if (flags == -1)
+                return -1;
+
+        auto const new_flags = (flags | set_flags) & ~unset_flags;
+        if (new_flags == flags)
+                return 0;
+
+        return fd_set_descriptor_flags(fd, new_flags);
+}
+
+static inline int
+fd_get_status_flags(int fd) noexcept
+{
+        auto flags = int{};
+        do {
+                flags = fcntl(fd, F_GETFL, 0);
+        } while (flags == -1 && errno == EINTR);
+
+        return flags;
+}
+
+static inline int
+fd_set_status_flags(int fd,
+                    int flags) noexcept
+{
+        auto r = int{};
+        do {
+                r = fcntl(fd, F_SETFL, flags);
+        } while (r == -1 && errno == EINTR);
+
+        return r;
+}
+
+static inline int
+fd_change_status_flags(int fd,
+                       int set_flags,
+                       int unset_flags) noexcept
+{
+        auto const flags = fd_get_status_flags(fd);
+        if (flags == -1)
+                return -1;
+
+        auto const new_flags = (flags | set_flags) & ~unset_flags;
+        if (new_flags == flags)
+                return 0;
+
+        return fd_set_status_flags(fd, new_flags);
+}
+
+static inline int
+fd_set_cloexec(int fd) noexcept
+{
+        return fd_change_descriptor_flags(fd, FD_CLOEXEC, 0);
+}
+
+static inline int
+fd_set_nonblock(int fd) noexcept
+{
+        return fd_change_status_flags(fd, O_NONBLOCK, 0);
+}
+
+// END copied from vte
+
+static int
+socketpair_cloexec_nonblock(int domain,
+                            int type,
+                            int protocol,
+                            int sv[2]) noexcept
+{
+  auto r = int{};
+#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
+  r = socketpair(domain,
+                 type | SOCK_CLOEXEC | SOCK_NONBLOCK,
+                 protocol,
+                 sv);
+  if (r != -1)
+    return r;
+
+  // Maybe cloexec and/or nonblock aren't supported by the kernel
+  if (errno != EINVAL && errno != EPROTOTYPE)
+    return r;
+
+  // If so, fall back to applying the flags after the socketpair() call
+#endif /* SOCK_CLOEXEC && SOCK_NONBLOCK */
+
+  r = socketpair(domain, type, protocol, sv);
+  if (r == -1)
+    return r;
+
+  if (fd_set_cloexec(sv[0]) == -1 ||
+      fd_set_nonblock(sv[0]) == -1 ||
+      fd_set_cloexec(sv[1]) == -1 ||
+      fd_set_nonblock(sv[1]) == -1) {
+    close(sv[0]);
+    close(sv[1]);
+    return -1;
+  }
+
+  return r;
+}
+
+static void
+subprocess_wait_cb(GObject* source,
+                   GAsyncResult* result,
+                   void* user_data)
+{
+  gs_unref_object auto impl = IMPL(user_data); // ref added on g_subprocess_wait_async
+
+  gs_free_error GError* error = nullptr;
+  if (g_subprocess_wait_finish(impl->subprocess, result, &error)) {
+  } // else: @cancellable was cancelled
+
+  auto const status = g_subprocess_get_status(impl->subprocess);
+
+  g_signal_emit(impl, signals[SIGNAL_EXITED], 0, status);
+
+  g_clear_object(&impl->subprocess);
+}
+
+// GInitable implementation
+
+static gboolean
+terminal_prefs_process_initable_init(GInitable* initable,
+                                     GCancellable* cancellable,
+                                     GError** error) noexcept
+{
+  auto const impl = IMPL(initable);
+
+  // Create a private D-Bus connection between the server and the preferences
+  // process, over which we proxy the settings (since otherwise there would
+  // be no way to modify the server's settings on backends other than dconf).
+
+  int socket_fds[2]{-1, -1};
+  auto r = socketpair_cloexec_nonblock(AF_UNIX, SOCK_STREAM, 0, socket_fds);
+  if (r != 0) {
+    auto const errsv = errno;
+    g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv),
+                "Failed to create bridge socketpair: %s",
+                g_strerror(errsv));
+    return false;
+  }
+
+  // Launch process
+  auto const launcher = g_subprocess_launcher_new(GSubprocessFlags(0));
+  // or use G_SUBPROCESS_FLAGS_STDOUT_SILENCE | G_SUBPROCESS_FLAGS_STDERR_SILENCE ?
+
+  // Note that g_subprocess_launcher_set_cwd() is not necessary since
+  // the server's cwd is already $HOME.
+  g_subprocess_launcher_set_environ(launcher, nullptr); // inherit server's environment
+  g_subprocess_launcher_unsetenv(launcher, "DBUS_SESSION_BUS_ADDRESS");
+  g_subprocess_launcher_unsetenv(launcher, "DBUS_STARTER_BUS_TYPE");
+  // ? g_subprocess_launcher_setenv(launcher, "GSETTINGS_BACKEND", "bridge", true);
+  g_subprocess_launcher_take_fd(launcher, socket_fds[1], 3);
+  socket_fds[1] = -1;
+
+  gs_free auto exe = terminal_client_get_file_uninstalled(TERM_LIBEXECDIR,
+                                                          TERM_PKGLIBDIR,
+                                                          TERMINAL_PREFERENCES_BINARY_NAME,
+                                                          G_FILE_TEST_IS_EXECUTABLE);
+
+  char *argv[16];
+  auto argc = 0;
+  _TERMINAL_DEBUG_IF(TERMINAL_DEBUG_BRIDGE) {
+    argv[argc++] = (char*)"vte-2.91";
+    argv[argc++] = (char*)"--fd=3";
+    argv[argc++] = (char*)"--";
+    argv[argc++] = (char*)"gdb";
+    argv[argc++] = (char*)"--args";
+  }
+  argv[argc++] = exe;
+  argv[argc++] = (char*)"--bus-fd=3";
+  argv[argc++] = nullptr;
+  g_assert(argc <= int(G_N_ELEMENTS(argv)));
+
+  impl->subprocess = g_subprocess_launcher_spawnv(launcher, // consumed
+                                                  argv,
+                                                  error);
+  if (!impl->subprocess) {
+    close(socket_fds[0]);
+    return false;
+  }
+
+  g_subprocess_wait_async(impl->subprocess,
+                          impl->cancellable,
+                          GAsyncReadyCallback(subprocess_wait_cb),
+                          g_object_ref(initable));
+
+  // Create server end of the D-Bus connection
+  gs_unref_object auto socket = g_socket_new_from_fd(socket_fds[0], error);
+  socket_fds[0] = -1;
+
+  if (!socket) {
+    g_prefix_error(error, "Failed to create bridge GSocket: ");
+    g_subprocess_force_exit(impl->subprocess);
+    return false;
+  }
+
+  gs_unref_object auto sockconn =
+    g_socket_connection_factory_create_connection(socket);
+  if (!G_IS_IO_STREAM(sockconn)) {
+    g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                "Bridge socket has incorrect type %s", G_OBJECT_TYPE_NAME(sockconn));
+    g_subprocess_force_exit(impl->subprocess);
+    return false;
+  }
+
+  gs_free auto guid = g_dbus_generate_guid();
+
+  impl->connection =
+    g_dbus_connection_new_sync(G_IO_STREAM(sockconn),
+                               guid,
+                               GDBusConnectionFlags(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER |
+                                                    G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS),
+                               nullptr, // auth observer,
+                               cancellable,
+                               error);
+  if (!impl->connection) {
+    g_prefix_error(error, "Failed to create bridge D-Bus connection: ");
+    g_subprocess_force_exit(impl->subprocess);
+    return false;
+  }
+
+  g_dbus_connection_set_exit_on_close(impl->connection, false);
+
+  auto const app = terminal_app_get();
+  impl->bridge_impl =
+    terminal_settings_bridge_impl_new(terminal_app_get_settings_backend(app));
+  if (!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(impl->bridge_impl),
+                                        impl->connection,
+                                        TERMINAL_SETTINGS_BRIDGE_OBJECT_PATH,
+                                        error)) {
+    g_prefix_error(error, "Failed to export D-Bus skeleton: ");
+    g_subprocess_force_exit(impl->subprocess);
+    return false;
+  }
+
+  return true;
+}
+
+static void
+terminal_prefs_process_initable_iface_init(GInitableIface* iface) noexcept
+{
+  iface->init = terminal_prefs_process_initable_init;
+}
+
+static void
+terminal_prefs_process_async_initable_iface_init(GAsyncInitableIface* iface) noexcept
+{
+  // Use the default implementation which runs the GInitiable in a thread.
+}
+
+// Class Implementation
+
+G_DEFINE_TYPE_WITH_CODE(TerminalPrefsProcess,
+                        terminal_prefs_process,
+                        G_TYPE_OBJECT,
+                        G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE,
+                                              terminal_prefs_process_initable_iface_init)
+                        G_IMPLEMENT_INTERFACE(G_TYPE_ASYNC_INITABLE,
+                                              terminal_prefs_process_async_initable_iface_init))
+
+static void
+terminal_prefs_process_init(TerminalPrefsProcess* process) /* noexcept */
+{
+}
+
+static void
+terminal_prefs_process_finalize(GObject* object) noexcept
+{
+  auto const impl = IMPL(object);
+  g_clear_object(&impl->bridge_impl);
+  g_clear_object(&impl->connection);
+  g_clear_object(&impl->cancellable);
+  g_clear_object(&impl->subprocess);
+
+  G_OBJECT_CLASS(terminal_prefs_process_parent_class)->finalize(object);
+}
+
+static void
+terminal_prefs_process_class_init(TerminalPrefsProcessClass* klass) /* noexcept */
+{
+  auto const gobject_class = G_OBJECT_CLASS(klass);
+  gobject_class->finalize = terminal_prefs_process_finalize;
+
+  signals[SIGNAL_EXITED] =
+    g_signal_new(I_("exited"),
+                 G_OBJECT_CLASS_TYPE(gobject_class),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET(TerminalPrefsProcessClass, exited),
+                 nullptr, nullptr,
+                 g_cclosure_marshal_VOID__INT,
+                 G_TYPE_NONE,
+                 1,
+                 G_TYPE_INT);
+  g_signal_set_va_marshaller(signals[SIGNAL_EXITED],
+                             G_OBJECT_CLASS_TYPE(klass),
+                             g_cclosure_marshal_VOID__INTv);
+}
+
+// public API
+
+TerminalPrefsProcess*
+terminal_prefs_process_new_finish(GAsyncResult* result,
+                                  GError** error)
+{
+  auto const source = G_ASYNC_INITABLE(g_async_result_get_source_object(result));
+  auto const o = g_async_initable_new_finish(source, result, error);
+  g_object_unref(source);
+  return reinterpret_cast<TerminalPrefsProcess*>(o);
+}
+
+void
+terminal_prefs_process_new_async(GCancellable* cancellable,
+                                 GAsyncReadyCallback callback,
+                                 void* user_data)
+{
+  g_async_initable_new_async(TERMINAL_TYPE_PREFS_PROCESS,
+                             G_PRIORITY_DEFAULT,
+                             cancellable,
+                             callback,
+                             user_data,
+                             // properties,
+                             nullptr);
+}
+
+TerminalPrefsProcess*
+terminal_prefs_process_new_sync(GCancellable* cancellable,
+                                 GError** error)
+{
+  return reinterpret_cast<TerminalPrefsProcess*>
+    (g_initable_new(TERMINAL_TYPE_PREFS_PROCESS,
+                    cancellable,
+                    error,
+                    // properties,
+                    nullptr));
+}
+
+void
+terminal_prefs_process_abort(TerminalPrefsProcess* process)
+{
+  g_return_if_fail(TERMINAL_IS_PREFS_PROCESS(process));
+
+  auto const impl = IMPL(process);
+  if (impl->subprocess)
+    g_subprocess_force_exit(impl->subprocess);
+}
+
+#ifdef ENABLE_DEBUG
+
+static void
+show_cb(GObject* source,
+        GAsyncResult* result,
+        void* user_data)
+{
+  gs_unref_object auto process = IMPL(user_data); // added on g_dbus_connection_call
+
+  gs_free_error GError *error = nullptr;
+  gs_unref_variant auto rv = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source),
+                                                           result,
+                                                           &error);
+
+  if (error)
+    _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, "terminal_prefs_process_show failed: %s\n", error->message);
+}
+
+#endif /* ENABLE_DEBUG */
+
+void
+terminal_prefs_process_show(TerminalPrefsProcess* process,
+                            char const* profile_uuid,
+                            char const* hint,
+                            unsigned timestamp)
+{
+  auto const impl = IMPL(process);
+
+  auto builder = GVariantBuilder{};
+  g_variant_builder_init(&builder, G_VARIANT_TYPE("(sava{sv})"));
+  g_variant_builder_add(&builder, "s", "preferences");
+  g_variant_builder_open(&builder, G_VARIANT_TYPE("av")); // parameter
+  g_variant_builder_open(&builder, G_VARIANT_TYPE("v"));
+  g_variant_builder_open(&builder, G_VARIANT_TYPE("a{sv}"));
+  if (profile_uuid)
+    g_variant_builder_add(&builder, "{sv}", "profile", g_variant_new_string(profile_uuid));
+  if (hint)
+    g_variant_builder_add(&builder, "{sv}", "hint", g_variant_new_string(hint));
+  g_variant_builder_add(&builder, "{sv}", "timestamp", g_variant_new_uint32(timestamp));
+  g_variant_builder_close(&builder); // a{sv}
+  g_variant_builder_close(&builder); // v
+  g_variant_builder_close(&builder); // av
+  g_variant_builder_open(&builder, G_VARIANT_TYPE("a{sv}")); // platform data
+  g_variant_builder_close(&builder); // a{sv}
+
+  g_dbus_connection_call(impl->connection,
+                         nullptr, // since not on a message bus
+                         TERMINAL_PREFERENCES_OBJECT_PATH,
+                         "org.gtk.Actions",
+                         "Activate",
+                         g_variant_builder_end(&builder),
+                         G_VARIANT_TYPE("()"),
+                         G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                         30 * 1000, // ms timeout
+                         impl->cancellable, // cancelleable
+#ifdef ENABLE_DEBUG
+                         show_cb, // callback
+                         g_object_ref(process) // callback data
+#else
+                         nullptr, nullptr
+#endif
+                         );
+}
diff --git a/src/terminal-prefs-process.hh b/src/terminal-prefs-process.hh
new file mode 100644
index 00000000..a93a532d
--- /dev/null
+++ b/src/terminal-prefs-process.hh
@@ -0,0 +1,53 @@
+/*
+ *  Copyright © 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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_PREFS_PROCESS         (terminal_prefs_process_get_type ())
+#define TERMINAL_PREFS_PROCESS(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_PREFS_PROCESS, 
TerminalPrefsProcess))
+#define TERMINAL_PREFS_PROCESS_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_PREFS_PROCESS, 
TerminalPrefsProcessClass))
+#define TERMINAL_IS_PREFS_PROCESS(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_PREFS_PROCESS))
+#define TERMINAL_IS_PREFS_PROCESS_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_PREFS_PROCESS))
+#define TERMINAL_PREFS_PROCESS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_PREFS_PROCESS, 
TerminalPrefsProcessClass))
+
+typedef struct _TerminalPrefsProcess        TerminalPrefsProcess;
+typedef struct _TerminalPrefsProcessClass   TerminalPrefsProcessClass;
+
+GType terminal_prefs_process_get_type(void);
+
+void terminal_prefs_process_new_async(GCancellable* cancellable,
+                                      GAsyncReadyCallback callback,
+                                      void* user_data);
+
+TerminalPrefsProcess* terminal_prefs_process_new_finish(GAsyncResult* result,
+                                                        GError** error);
+
+TerminalPrefsProcess* terminal_prefs_process_new_sync(GCancellable* cancellable,
+                                                      GError** error);
+
+void terminal_prefs_process_abort(TerminalPrefsProcess* process);
+
+void terminal_prefs_process_show(TerminalPrefsProcess* process,
+                                 char const* profile_uuid,
+                                 char const* hint,
+                                 unsigned timestamp);
+
+G_END_DECLS
diff --git a/src/terminal-prefs.cc b/src/terminal-prefs.cc
index 0c0c248d..300b2868 100644
--- a/src/terminal-prefs.cc
+++ b/src/terminal-prefs.cc
@@ -716,7 +716,9 @@ prefs_dialog_destroy_cb (GtkWidget *widget,
 }
 
 void
-terminal_prefs_show_preferences (GSettings *profile, const char *widget_name)
+terminal_prefs_show_preferences(GSettings* profile,
+                                char const* widget_name,
+                                unsigned timestamp)
 {
   TerminalApp *app = terminal_app_get ();
   PrefData *data;
@@ -920,5 +922,5 @@ done:
 
   terminal_util_dialog_focus_widget (the_pref_data->builder, widget_name);
 
-  gtk_window_present (GTK_WINDOW (the_pref_data->dialog));
+  gtk_window_present_with_time(GTK_WINDOW(the_pref_data->dialog), timestamp);
 }
diff --git a/src/terminal-prefs.hh b/src/terminal-prefs.hh
index 72cf36ca..e1ce7849 100644
--- a/src/terminal-prefs.hh
+++ b/src/terminal-prefs.hh
@@ -48,7 +48,9 @@ typedef struct {
 
 extern PrefData *the_pref_data;  /* global */
 
-void terminal_prefs_show_preferences (GSettings *profile, const char *widget_name);
+void terminal_prefs_show_preferences(GSettings* profile,
+                                     char const* widget_name,
+                                     unsigned timestamp);
 
 G_END_DECLS
 
diff --git a/src/terminal-profiles-list.cc b/src/terminal-profiles-list.cc
index 07a97636..e7910871 100644
--- a/src/terminal-profiles-list.cc
+++ b/src/terminal-profiles-list.cc
@@ -65,14 +65,17 @@ valid_uuid (const char *str,
 
 /**
  * terminal_profiles_list_new:
+ * @backend: a #GSettingsBackend
  * @schema_source: a #GSettingsSchemaSource
  *
  * Returns: (transfer full): a new #TerminalSettingsList for the profiles list
  */
 TerminalSettingsList *
-terminal_profiles_list_new(GSettingsSchemaSource* schema_source)
+terminal_profiles_list_new(GSettingsBackend* backend,
+                           GSettingsSchemaSource* schema_source)
 {
-  return terminal_settings_list_new (schema_source,
+  return terminal_settings_list_new (backend,
+                                     schema_source,
                                      TERMINAL_PROFILES_PATH_PREFIX,
                                      TERMINAL_PROFILES_LIST_SCHEMA,
                                      TERMINAL_PROFILE_SCHEMA,
diff --git a/src/terminal-profiles-list.hh b/src/terminal-profiles-list.hh
index d8323751..afd8da50 100644
--- a/src/terminal-profiles-list.hh
+++ b/src/terminal-profiles-list.hh
@@ -25,7 +25,8 @@
 
 G_BEGIN_DECLS
 
-TerminalSettingsList *terminal_profiles_list_new(GSettingsSchemaSource* schema_source);
+TerminalSettingsList *terminal_profiles_list_new(GSettingsBackend* backend,
+                                                 GSettingsSchemaSource* schema_source);
 
 GList *terminal_profiles_list_ref_children_sorted (TerminalSettingsList *list);
 
diff --git a/src/terminal-screen.cc b/src/terminal-screen.cc
index 7c739d25..5a1a1db0 100644
--- a/src/terminal-screen.cc
+++ b/src/terminal-screen.cc
@@ -1509,7 +1509,8 @@ info_bar_response_cb (GtkWidget *info_bar,
     case RESPONSE_EDIT_PREFERENCES:
       terminal_app_edit_preferences (terminal_app_get (),
                                      terminal_screen_get_profile (screen),
-                                     "custom-command-entry");
+                                     "custom-command-entry",
+                                     gtk_get_current_event_time());
       break;
     default:
       gtk_widget_destroy (info_bar);
diff --git a/src/terminal-settings-bridge-backend.cc b/src/terminal-settings-bridge-backend.cc
new file mode 100644
index 00000000..66805bbd
--- /dev/null
+++ b/src/terminal-settings-bridge-backend.cc
@@ -0,0 +1,635 @@
+/*
+ *  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-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;
+}
+
+static auto
+wrap(GVariant* value) noexcept
+{
+  auto builder = GVariantBuilder{};
+  g_variant_builder_init(&builder, G_VARIANT_TYPE("av"));
+  if (value)
+    g_variant_builder_add(&builder, "v", value);
+  return g_variant_builder_end(&builder);
+}
+
+static auto
+unwrap(GVariant* value) noexcept
+{
+  auto iter = GVariantIter{};
+  g_variant_iter_init(&iter, value);
+  gs_unref_variant auto cv = g_variant_iter_next_value(&iter);
+  return cv ? g_variant_get_variant(cv) : nullptr;
+}
+
+/* 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 ? 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,
+                                                 const GVariantType* 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 ? 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,
+                                             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(sav)"));
+  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,
+                          "(s@av)",
+                          keys[i],
+                          wrap(value));
+
+    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 success = gboolean{false};
+  auto const r =
+    terminal_settings_bridge_call_write_tree_sync(impl->bridge,
+                                                  path_prefix,
+                                                  g_variant_builder_end(&builder),
+                                                  &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));
+}
+
+void
+terminal_settings_bridge_backend_clone_schema(TerminalSettingsBridgeBackend* backend,
+                                              GSettingsSchemaSource*schema_source,
+                                              char const* schema_id,
+                                              char const* path,
+                                              char const* new_path,
+                                              char const* first_key,
+                                              ...)
+{
+  auto const impl = IMPL(backend);
+
+  auto builder = GVariantBuilder{};
+  g_variant_builder_init(&builder, G_VARIANT_TYPE("a(sv)"));
+
+  va_list args;
+  va_start(args, first_key);
+  while (first_key != nullptr) {
+    auto const type = va_arg(args, char const*);
+    auto const value = g_variant_new_va(type, nullptr, &args);
+    gs_free auto wkey = g_strconcat(new_path, first_key, nullptr);
+
+    g_variant_builder_add(&builder, "(sv)", wkey, value);
+    first_key = va_arg(args, char const*);
+  }
+  va_end(args);
+
+  auto const r =
+    terminal_settings_bridge_call_clone_schema_sync(impl->bridge,
+                                                    schema_id,
+                                                    path,
+                                                    new_path,
+                                                    g_variant_builder_end(&builder),
+                                                    impl->cancellable,
+                                                    nullptr);
+
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge backend ::clone_schema schema %s from-path %s to-path %s success %d\n",
+                        schema_id, path, new_path, r);
+}
+
+void
+terminal_settings_bridge_backend_erase_path(TerminalSettingsBridgeBackend* backend,
+                                            char const* path)
+{
+  auto const impl = IMPL(backend);
+  auto const r =
+    terminal_settings_bridge_call_erase_path_sync(impl->bridge,
+                                                  path,
+                                                  impl->cancellable,
+                                                  nullptr);
+
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge backend ::erase_path path %s success %d\n",
+                        path, r);
+}
diff --git a/src/terminal-settings-bridge-backend.hh b/src/terminal-settings-bridge-backend.hh
new file mode 100644
index 00000000..8a6bff15
--- /dev/null
+++ b/src/terminal-settings-bridge-backend.hh
@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2008, 2010, 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/>.
+ */
+
+#pragma once
+
+#include "terminal-settings-bridge-generated.h"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND         (terminal_settings_bridge_backend_get_type ())
+#define TERMINAL_SETTINGS_BRIDGE_BACKEND(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND, TerminalSettingsBridgeBackend))
+#define TERMINAL_SETTINGS_BRIDGE_BACKEND_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), 
TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND, TerminalSettingsBridgeBackendClass))
+#define TERMINAL_IS_SETTINGS_BRIDGE_BACKEND(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND))
+#define TERMINAL_IS_SETTINGS_BRIDGE_BACKEND_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), 
TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND))
+#define TERMINAL_SETTINGS_BRIDGE_BACKEND_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND, TerminalSettingsBridgeBackendClass))
+
+typedef struct _TerminalSettingsBridgeBackend        TerminalSettingsBridgeBackend;
+typedef struct _TerminalSettingsBridgeBackendClass   TerminalSettingsBridgeBackendClass;
+
+GType terminal_settings_bridge_backend_get_type(void);
+
+GSettingsBackend* terminal_settings_bridge_backend_new(TerminalSettingsBridge* bridge);
+
+void terminal_settings_bridge_backend_clone_schema(TerminalSettingsBridgeBackend* backend,
+                                                   GSettingsSchemaSource*schema_source,
+                                                   char const* schema_id,
+                                                   char const* path,
+                                                   char const* new_path,
+                                                   char const* first_key,
+                                                   ...);
+
+void terminal_settings_bridge_backend_erase_path(TerminalSettingsBridgeBackend* backend,
+                                                 char const* path);
+
+G_END_DECLS
diff --git a/src/terminal-settings-bridge-impl.cc b/src/terminal-settings-bridge-impl.cc
new file mode 100644
index 00000000..57ea4285
--- /dev/null
+++ b/src/terminal-settings-bridge-impl.cc
@@ -0,0 +1,478 @@
+/*
+ *  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-settings-bridge-impl.hh"
+
+#include "terminal-app.hh"
+#include "terminal-dconf.hh"
+#include "terminal-debug.hh"
+#include "terminal-libgsystem.hh"
+#include "terminal-settings-bridge-generated.h"
+
+#include <gio/gio.h>
+#include <gio/gsettingsbackend.h>
+
+enum {
+  PROP_SETTINGS_BACKEND = 1,
+};
+
+struct _TerminalSettingsBridgeImpl {
+  TerminalSettingsBridgeSkeleton parent_instance;
+
+  GSettingsBackend* backend;
+  GSettingsBackendClass* backend_class;
+  void* tag;
+};
+
+struct _TerminalSettingsBridgeImplClass {
+  TerminalSettingsBridgeSkeletonClass parent_class;
+};
+
+// 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>
+static inline constexpr auto
+IMPL(T* that) noexcept
+{
+  return reinterpret_cast<TerminalSettingsBridgeImpl*>(that);
+}
+
+static inline int
+compare_string(void const* a,
+               void const* b,
+               void* closure)
+{
+  return strcmp(reinterpret_cast<char const*>(a), reinterpret_cast<char const*>(b));
+}
+
+static void
+unref_variant0(void* data) noexcept
+{
+  if (data)
+    g_variant_unref(reinterpret_cast<GVariant*>(data));
+}
+
+static GVariantType*
+type_from_string(GDBusMethodInvocation* invocation,
+                 char const* type) noexcept
+{
+  if (!g_variant_type_string_is_valid(type)) {
+    g_dbus_method_invocation_return_error(invocation,
+                                          G_DBUS_ERROR,
+                                          G_DBUS_ERROR_INVALID_ARGS,
+                                          "Invalid type: %s",
+                                          type);
+    return nullptr;
+  }
+
+  return g_variant_type_new(type);
+}
+
+static auto
+unwrap(GVariantIter* iter) noexcept
+{
+  gs_unref_variant auto iv = g_variant_iter_next_value(iter);
+  return iv ? g_variant_get_variant(iv) : nullptr;
+}
+
+static auto
+unwrap(GVariant* value) noexcept
+{
+  auto iter = GVariantIter{};
+  g_variant_iter_init(&iter, value);
+  return unwrap(&iter);
+}
+
+static auto
+value(GDBusMethodInvocation* invocation,
+      char const* format,
+      ...) noexcept
+{
+  va_list args;
+  va_start(args, format);
+  auto const v = g_variant_new_va(format, nullptr, &args);
+  va_end(args);
+  g_dbus_method_invocation_return_value(invocation, v);
+  return true;
+}
+
+static auto
+wrap(GDBusMethodInvocation* invocation,
+     GVariant* variant) noexcept
+{
+  auto builder = GVariantBuilder{};
+  g_variant_builder_init(&builder, G_VARIANT_TYPE("av"));
+  if (variant)
+    g_variant_builder_add(&builder, "v", variant);
+
+  return value(invocation, "(av)", &builder);
+}
+
+static auto
+nothing(GDBusMethodInvocation* invocation) noexcept
+{
+  return value(invocation, "()");
+}
+
+static auto
+novalue(GDBusMethodInvocation* invocation) noexcept
+{
+  g_dbus_method_invocation_return_error_literal(invocation,
+                                                G_DBUS_ERROR,
+                                                G_DBUS_ERROR_FAILED,
+                                                "No value");
+  return true;
+}
+
+static auto
+success(GDBusMethodInvocation* invocation,
+        bool v = true) noexcept
+{
+  return value(invocation, "(b)", v);
+}
+
+/* TerminalSettingsBridge interface implementation */
+
+static gboolean
+terminal_settings_bridge_impl_clone_schema(TerminalSettingsBridge* object,
+                                           GDBusMethodInvocation* invocation,
+                                           char const* schema_id,
+                                           char const* path_from,
+                                           char const* path_to,
+                                           GVariant* asv)
+{
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge impl ::clone_schema schema %s from-path %s to-path %s\n",
+                        schema_id, path_from, path_to);
+
+  auto const impl = IMPL(object);
+  if (terminal_dconf_backend_is_dconf(impl->backend)) {
+    auto const schema_source = terminal_app_get_schema_source(terminal_app_get());
+    terminal_dconf_clone_schemav(schema_source, schema_id, path_from, path_to, asv);
+  }
+
+  return nothing(invocation);
+}
+
+static gboolean
+terminal_settings_bridge_impl_erase_path(TerminalSettingsBridge* object,
+                                         GDBusMethodInvocation* invocation,
+                                         char const* path)
+{
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge impl ::erase_path path %s\n",
+                        path);
+
+  auto const impl = IMPL(object);
+  if (terminal_dconf_backend_is_dconf(impl->backend)) {
+    terminal_dconf_erase_path(path);
+  }
+
+  return nothing(invocation);
+}
+
+static gboolean
+terminal_settings_bridge_impl_get_permission(TerminalSettingsBridge* object,
+                                             GDBusMethodInvocation* invocation,
+                                             char const* path) noexcept
+{
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge impl ::get_permission path %s\n",
+                        path);
+
+  return novalue(invocation);
+}
+
+static gboolean
+terminal_settings_bridge_impl_get_writable(TerminalSettingsBridge* object,
+                                           GDBusMethodInvocation* invocation,
+                                           char const* key) noexcept
+{
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge impl ::get_writable key %s\n",
+                        key);
+
+  auto const impl = IMPL(object);
+  auto const v = impl->backend_class->get_writable(impl->backend, key);
+  return success(invocation, v);
+}
+
+static gboolean
+terminal_settings_bridge_impl_read(TerminalSettingsBridge* object,
+                                   GDBusMethodInvocation* invocation,
+                                   char const* key,
+                                   char const* type,
+                                   gboolean default_value) noexcept
+{
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge impl ::read key %s type %s default %d\n",
+                        key, type, default_value);
+
+  auto const vtype = type_from_string(invocation, type);
+  if (!vtype)
+    return true;
+
+  auto const impl = IMPL(object);
+  gs_unref_variant auto v = impl->backend_class->read(impl->backend, key, vtype, default_value);
+  if (v)
+    g_variant_take_ref(v);
+
+  g_variant_type_free(vtype);
+  return wrap(invocation, v);
+}
+
+static gboolean
+terminal_settings_bridge_impl_read_user_value(TerminalSettingsBridge* object,
+                                              GDBusMethodInvocation* invocation,
+                                              char const* key,
+                                              char const* type) noexcept
+{
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge impl ::read_user_value key %s type %s\n",
+                        key, type);
+
+  auto const vtype = type_from_string(invocation, type);
+  if (!vtype)
+    return true;
+
+  auto const impl = IMPL(object);
+  gs_unref_variant auto v = impl->backend_class->read_user_value(impl->backend, key, vtype);
+  if (v)
+    g_variant_take_ref(v);
+
+  g_variant_type_free(vtype);
+  return wrap(invocation, v);
+}
+
+static gboolean
+terminal_settings_bridge_impl_reset(TerminalSettingsBridge* object,
+                                    GDBusMethodInvocation* invocation,
+                                    char const* key) noexcept
+{
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge impl ::reset key %s\n",
+                        key);
+
+  auto const impl = IMPL(object);
+  impl->backend_class->reset(impl->backend, key, impl->tag);
+  return nothing(invocation);
+}
+
+static gboolean
+terminal_settings_bridge_impl_subscribe(TerminalSettingsBridge* object,
+                                        GDBusMethodInvocation* invocation,
+                                        char const* name) noexcept
+{
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge impl ::subscribe name %s\n",
+                        name);
+
+  auto const impl = IMPL(object);
+  impl->backend_class->subscribe(impl->backend, name);
+  return nothing(invocation);
+}
+
+static gboolean
+terminal_settings_bridge_impl_sync(TerminalSettingsBridge* object,
+                                   GDBusMethodInvocation* invocation) noexcept
+{
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge impl ::sync\n");
+
+  auto const impl = IMPL(object);
+  impl->backend_class->sync(impl->backend);
+  return nothing(invocation);
+}
+
+static gboolean
+terminal_settings_bridge_impl_unsubscribe(TerminalSettingsBridge* object,
+                                          GDBusMethodInvocation* invocation,
+                                          char const* name) noexcept
+{
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge impl ::unsubscribe name %s\n",
+                        name);
+
+  auto const impl = IMPL(object);
+  impl->backend_class->subscribe(impl->backend, name);
+  return nothing(invocation);
+}
+
+static gboolean
+terminal_settings_bridge_impl_write(TerminalSettingsBridge* object,
+                                    GDBusMethodInvocation* invocation,
+                                    char const* key,
+                                    GVariant* value) noexcept
+{
+  auto const impl = IMPL(object);
+  gs_unref_variant auto v = unwrap(value);
+
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge impl ::write key %s value %s\n",
+                        key, v ? g_variant_print(v, true): "(null)");
+
+  gs_unref_variant auto holder = g_variant_ref_sink(v);
+  auto const r = impl->backend_class->write(impl->backend, key, v, impl->tag);
+  return success(invocation, r);
+}
+
+static gboolean
+terminal_settings_bridge_impl_write_tree(TerminalSettingsBridge* object,
+                                         GDBusMethodInvocation* invocation,
+                                         char const* path_prefix,
+                                         GVariant* tree_value) noexcept
+{
+  _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+                        "Bridge impl ::write_tree path-prefix %s\n",
+                        path_prefix);
+
+  auto const tree = g_tree_new_full(compare_string,
+                                    nullptr,
+                                    g_free,
+                                    unref_variant0);
+
+  auto iter = GVariantIter{};
+  g_variant_iter_init(&iter, tree_value);
+
+  char const* key = nullptr;
+  GVariantIter* viter = nullptr;
+  while (g_variant_iter_loop(&iter, "(&sav)", &key, &viter)) {
+    g_tree_insert(tree,
+                  g_strconcat(path_prefix, key, nullptr), // adopts
+                  unwrap(viter)); // adopts
+  }
+
+  auto const impl = IMPL(object);
+  auto const v = impl->backend_class->write_tree(impl->backend, tree, impl->tag);
+
+  g_tree_unref(tree);
+  return success(invocation, v);
+}
+
+static void
+terminal_settings_bridge_impl_iface_init(TerminalSettingsBridgeIface* iface) noexcept
+{
+  iface->handle_clone_schema = terminal_settings_bridge_impl_clone_schema;
+  iface->handle_erase_path = terminal_settings_bridge_impl_erase_path;
+  iface->handle_get_permission = terminal_settings_bridge_impl_get_permission;
+  iface->handle_get_writable = terminal_settings_bridge_impl_get_writable;
+  iface->handle_read = terminal_settings_bridge_impl_read;
+  iface->handle_read_user_value = terminal_settings_bridge_impl_read_user_value;
+  iface->handle_reset = terminal_settings_bridge_impl_reset;
+  iface->handle_subscribe = terminal_settings_bridge_impl_subscribe;
+  iface->handle_sync= terminal_settings_bridge_impl_sync;
+  iface->handle_unsubscribe = terminal_settings_bridge_impl_unsubscribe;
+  iface->handle_write = terminal_settings_bridge_impl_write;
+  iface->handle_write_tree = terminal_settings_bridge_impl_write_tree;
+}
+
+/* GObject class implementation */
+
+G_DEFINE_TYPE_WITH_CODE(TerminalSettingsBridgeImpl,
+                        terminal_settings_bridge_impl,
+                        TERMINAL_TYPE_SETTINGS_BRIDGE_SKELETON,
+                        G_IMPLEMENT_INTERFACE(TERMINAL_TYPE_SETTINGS_BRIDGE,
+                                              terminal_settings_bridge_impl_iface_init));
+
+static void
+terminal_settings_bridge_impl_init(TerminalSettingsBridgeImpl* impl) /* noexcept */
+{
+  impl->tag = &impl->tag;
+}
+
+static void
+terminal_settings_bridge_impl_constructed(GObject* object) noexcept
+{
+  G_OBJECT_CLASS(terminal_settings_bridge_impl_parent_class)->constructed(object);
+
+  auto const impl = IMPL(object);
+  assert(impl->backend);
+  impl->backend_class = G_SETTINGS_BACKEND_GET_CLASS(impl->backend);
+  assert(impl->backend_class);
+}
+
+static void
+terminal_settings_bridge_impl_finalize(GObject* object) noexcept
+{
+  auto const impl = IMPL(object);
+  impl->backend_class = nullptr;
+  g_clear_object(&impl->backend);
+
+  G_OBJECT_CLASS(terminal_settings_bridge_impl_parent_class)->finalize(object);
+}
+
+static void
+terminal_settings_bridge_impl_set_property(GObject* object,
+                                           guint prop_id,
+                                           GValue const* value,
+                                           GParamSpec* pspec) noexcept
+{
+  auto const impl = IMPL(object);
+
+  switch (prop_id) {
+  case PROP_SETTINGS_BACKEND:
+    impl->backend = G_SETTINGS_BACKEND(g_value_dup_object(value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    break;
+  }
+}
+
+static void
+terminal_settings_bridge_impl_class_init(TerminalSettingsBridgeImplClass* klass) /* noexcept */
+{
+  auto const gobject_class = G_OBJECT_CLASS(klass);
+  gobject_class->constructed = terminal_settings_bridge_impl_constructed;
+  gobject_class->finalize = terminal_settings_bridge_impl_finalize;
+  gobject_class->set_property = terminal_settings_bridge_impl_set_property;
+
+  g_object_class_install_property
+    (gobject_class,
+     PROP_SETTINGS_BACKEND,
+     g_param_spec_object("settings-backend", nullptr, nullptr,
+                         G_TYPE_SETTINGS_BACKEND,
+                         GParamFlags(G_PARAM_WRITABLE |
+                                     G_PARAM_CONSTRUCT_ONLY |
+                                     G_PARAM_STATIC_STRINGS)));
+}
+
+/* public API */
+
+/**
+*  terminal_settings_bridge_impl_new:
+*  @backend: a #GSettingsBackend
+*
+*  Returns: (transfer full): a new #TerminalSettingsBridgeImpl for @backend
+ */
+TerminalSettingsBridgeImpl*
+terminal_settings_bridge_impl_new(GSettingsBackend* backend)
+{
+  return reinterpret_cast<TerminalSettingsBridgeImpl*>
+    (g_object_new (TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL,
+                  "settings-backend", backend,
+                  nullptr));
+}
diff --git a/src/terminal-settings-bridge-impl.hh b/src/terminal-settings-bridge-impl.hh
new file mode 100644
index 00000000..ab8881f4
--- /dev/null
+++ b/src/terminal-settings-bridge-impl.hh
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2008, 2010, 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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL         (terminal_settings_bridge_impl_get_type ())
+#define TERMINAL_SETTINGS_BRIDGE_IMPL(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL, TerminalSettingsBridgeImpl))
+#define TERMINAL_SETTINGS_BRIDGE_IMPL_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), 
TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL, TerminalSettingsBridgeImplClass))
+#define TERMINAL_IS_SETTINGS_BRIDGE_IMPL(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL))
+#define TERMINAL_IS_SETTINGS_BRIDGE_IMPL_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), 
TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL))
+#define TERMINAL_SETTINGS_BRIDGE_IMPL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL, TerminalSettingsBridgeImplClass))
+
+typedef struct _TerminalSettingsBridgeImpl        TerminalSettingsBridgeImpl;
+typedef struct _TerminalSettingsBridgeImplClass   TerminalSettingsBridgeImplClass;
+
+GType terminal_settings_bridge_impl_get_type(void);
+
+TerminalSettingsBridgeImpl* terminal_settings_bridge_impl_new(GSettingsBackend* backend);
+
+G_END_DECLS
diff --git a/src/terminal-settings-list.cc b/src/terminal-settings-list.cc
index 20d29b68..03f85999 100644
--- a/src/terminal-settings-list.cc
+++ b/src/terminal-settings-list.cc
@@ -23,22 +23,23 @@
 #include <string.h>
 #include <uuid.h>
 
-// See https://gitlab.gnome.org/GNOME/dconf/-/issues/23
-extern "C" {
-#include <dconf.h>
-}
-
 #define G_SETTINGS_ENABLE_BACKEND
 #include <gio/gsettingsbackend.h>
 
 #include "terminal-type-builtins.hh"
 #include "terminal-schemas.hh"
 #include "terminal-debug.hh"
+#include "terminal-dconf.hh"
 #include "terminal-libgsystem.hh"
 
+#ifdef TERMINAL_PREFERENCES
+#include "terminal-settings-bridge-backend.hh"
+#endif
+
 struct _TerminalSettingsList {
   GSettings parent;
 
+  GSettingsBackend* settings_backend;
   GSettingsSchemaSource* schema_source;
   char *path;
   char *child_schema_id;
@@ -187,16 +188,6 @@ terminal_settings_list_valid_uuid (const char *str)
   return uuid_parse ((char *) str, u) == 0;
 }
 
-static gboolean
-settings_backend_is_dconf (void)
-{
-  gs_unref_object GSettingsBackend *backend;
-
-  backend = g_settings_backend_get_default ();
-
-  return g_str_equal (G_OBJECT_TYPE_NAME (backend), "DConfSettingsBackend");
-}
-
 static char *
 new_list_entry (void)
 {
@@ -245,9 +236,9 @@ list_map_func (GVariant *value,
   return FALSE;
 }
 
-static char *
+static char*
 path_new (TerminalSettingsList *list,
-          const char *uuid)
+          char const* uuid)
 {
   return g_strdup_printf ("%s:%s/", list->path, uuid);
 }
@@ -270,7 +261,8 @@ terminal_settings_list_ref_child_internal (TerminalSettingsList *list,
     goto done;
 
   path = path_new (list, uuid);
-  child = terminal_g_settings_new_with_path(list->schema_source,
+  child = terminal_g_settings_new_with_path(list->settings_backend,
+                                            list->schema_source,
                                             list->child_schema_id,
                                             path);
   g_hash_table_insert (list->children, g_strdup (uuid), child /* adopted */);
@@ -288,7 +280,8 @@ new_child (TerminalSettingsList *list,
   if (name != nullptr) {
     gs_free char *new_path = path_new (list, new_uuid);
     gs_unref_object GSettings *child =
-      terminal_g_settings_new_with_path(list->schema_source,
+      terminal_g_settings_new_with_path(list->settings_backend,
+                                        list->schema_source,
                                         list->child_schema_id,
                                         new_path);
     g_settings_set_string (child, TERMINAL_PROFILE_VISIBLE_NAME_KEY, name);
@@ -298,66 +291,71 @@ new_child (TerminalSettingsList *list,
 }
 
 static char *
-clone_child (TerminalSettingsList *list,
-             const char *uuid,
-             const char *name)
+clone_child_dconf (TerminalSettingsList *list,
+                   const char *uuid,
+                   const char *name)
 {
-  char *new_uuid;
-  gs_free char *path;
-  gs_free char *new_path;
-  guint i;
-  gs_unref_object DConfClient *client;
-  DConfChangeset *changeset;
-
-  new_uuid = new_list_entry ();
+  char* const new_uuid = new_list_entry();
 
   _terminal_debug_print (TERMINAL_DEBUG_SETTINGS_LIST,
                          "%s UUID %s NEW UUID %s \n", G_STRFUNC, uuid ? uuid : "(null)", new_uuid);
 
-  path = path_new (list, uuid);
-  new_path = path_new (list, new_uuid);
-
-  client = dconf_client_new ();
-  changeset = dconf_changeset_new ();
-
-  gs_unref_settings_schema GSettingsSchema* schema = g_settings_schema_source_lookup (list->schema_source,
-                                                                                      list->child_schema_id,
-                                                                                      TRUE);
-   /* shouldn't really happen ever */
-  if (schema == nullptr)
-    return new_uuid;
-
-  gs_strfreev char **keys = g_settings_schema_list_keys (schema);
-
-  for (i = 0; keys[i]; i++) {
-    gs_free char *rkey;
-    gs_unref_variant GVariant *value;
-
-    rkey = g_strconcat (path, keys[i], nullptr);
-    value = dconf_client_read (client, rkey);
-    if (value) {
-      gs_free char *wkey;
-      wkey = g_strconcat (new_path, keys[i], nullptr);
-      dconf_changeset_set (changeset, wkey, value);
-    }
-  }
+  gs_free auto path = path_new(list, uuid);
+  gs_free auto new_path = path_new(list, new_uuid);
 
-  if (name != nullptr) {
-    GVariant *value;
-    value = g_variant_new_string (name);
-    if (value) {
-      gs_free char *wkey;
-      wkey = g_strconcat (new_path, TERMINAL_PROFILE_VISIBLE_NAME_KEY, nullptr);
-      dconf_changeset_set (changeset, wkey, value);
-    }
-  }
+  if (name)
+    terminal_dconf_clone_schema(list->schema_source,
+                                list->child_schema_id,
+                                path,
+                                new_path,
+                                TERMINAL_PROFILE_VISIBLE_NAME_KEY, "s", name,
+                                nullptr);
+  else
+    terminal_dconf_clone_schema(list->schema_source,
+                                list->child_schema_id,
+                                path,
+                                new_path,
+                                nullptr);
+
+  return new_uuid;
+}
+
+#ifdef TERMINAL_PREFERENCES
+
+static char *
+clone_child_bridge (TerminalSettingsList *list,
+                    const char *uuid,
+                    const char *name)
+{
+  char* const new_uuid = new_list_entry();
 
-  dconf_client_change_sync (client, changeset, nullptr, nullptr, nullptr);
-  dconf_changeset_unref (changeset);
+  _terminal_debug_print (TERMINAL_DEBUG_SETTINGS_LIST,
+                         "%s UUID %s NEW UUID %s \n", G_STRFUNC, uuid ? uuid : "(null)", new_uuid);
+
+  gs_free auto path = path_new(list, uuid);
+  gs_free auto new_path = path_new(list, new_uuid);
+
+  if (name)
+    terminal_settings_bridge_backend_clone_schema(TERMINAL_SETTINGS_BRIDGE_BACKEND(list->settings_backend),
+                                                  list->schema_source,
+                                                  list->child_schema_id,
+                                                  path,
+                                                  new_path,
+                                                  TERMINAL_PROFILE_VISIBLE_NAME_KEY, "s", name,
+                                                  nullptr);
+  else
+    terminal_settings_bridge_backend_clone_schema(TERMINAL_SETTINGS_BRIDGE_BACKEND(list->settings_backend),
+                                                  list->schema_source,
+                                                  list->child_schema_id,
+                                                  path,
+                                                  new_path,
+                                                  nullptr);
 
   return new_uuid;
 }
 
+#endif /* TERMINAL_PREFERENCES */
+
 static char *
 terminal_settings_list_add_child_internal (TerminalSettingsList *list,
                                            const char *uuid,
@@ -366,8 +364,12 @@ terminal_settings_list_add_child_internal (TerminalSettingsList *list,
   char *new_uuid;
   gs_strfreev char **new_uuids;
 
-  if (uuid && settings_backend_is_dconf ())
-    new_uuid = clone_child (list, uuid, name);
+  if (uuid && terminal_dconf_backend_is_dconf (list->settings_backend))
+    new_uuid = clone_child_dconf (list, uuid, name);
+#ifdef TERMINAL_PREFERENCES
+  else if (uuid && TERMINAL_IS_SETTINGS_BRIDGE_BACKEND (list->settings_backend))
+    new_uuid = clone_child_bridge (list, uuid, name);
+#endif
   else
     new_uuid = new_child (list, name);
 
@@ -403,14 +405,17 @@ terminal_settings_list_remove_child_internal (TerminalSettingsList *list,
     g_settings_set_string (&list->parent, TERMINAL_SETTINGS_LIST_DEFAULT_KEY, "");
 
   /* Now we unset all keys under the child */
-  if (settings_backend_is_dconf ()) {
-    gs_free char *path;
-    gs_unref_object DConfClient *client;
-
-    path = path_new (list, uuid);
-    client = dconf_client_new ();
-    dconf_client_write_sync (client, path, nullptr, nullptr, nullptr, nullptr);
+  if (terminal_dconf_backend_is_dconf (list->settings_backend)) {
+    gs_free auto path = path_new(list, uuid);
+    terminal_dconf_erase_path(path);
+  }
+#ifdef TERMINAL_PREFERENCES
+  else if (TERMINAL_IS_SETTINGS_BRIDGE_BACKEND (list->settings_backend)) {
+    gs_free auto path = path_new(list, uuid);
+    terminal_settings_bridge_backend_erase_path(TERMINAL_SETTINGS_BRIDGE_BACKEND (list->settings_backend),
+                                                path);
   }
+#endif
 }
 
 static void
@@ -500,7 +505,7 @@ terminal_settings_list_changed (GSettings *list_settings,
   _terminal_debug_print (TERMINAL_DEBUG_SETTINGS_LIST,
                          "%s key %s", G_STRFUNC, key ? key : "(null)");
 
-  if (key == nullptr || 
+  if (key == nullptr ||
       g_str_equal (key, TERMINAL_SETTINGS_LIST_LIST_KEY)) {
     terminal_settings_list_update_list (list);
     terminal_settings_list_update_default (list);
@@ -527,6 +532,12 @@ terminal_settings_list_constructed (GObject *object)
 
   G_OBJECT_CLASS (terminal_settings_list_parent_class)->constructed (object);
 
+  g_object_get(object, "backend", &list->settings_backend, nullptr);
+  g_assert(list->settings_backend);
+
+  if (list->schema_source == nullptr)
+    list->schema_source = g_settings_schema_source_get_default();
+
   g_assert (list->schema_source != nullptr);
   g_assert (list->child_schema_id != nullptr);
 
@@ -550,6 +561,7 @@ terminal_settings_list_finalize (GObject *object)
   g_free (list->default_uuid);
   g_hash_table_unref (list->children);
   g_settings_schema_source_unref(list->schema_source);
+  g_clear_object(&list->settings_backend);
 
   G_OBJECT_CLASS (terminal_settings_list_parent_class)->finalize (object);
 }
@@ -665,6 +677,7 @@ terminal_settings_list_class_init (TerminalSettingsListClass *klass)
 
 /**
  * terminal_settings_list_new:
+ * @backend: (nullable): a #GSettingsBackend, or %NULL
  * @schema_source: a #GSettingsSchemaSource
  * @path: the settings path for the list
  * @schema_id: the schema of the list, equal to or derived from "org.gnome.Terminal.SettingsList"
@@ -674,12 +687,14 @@ terminal_settings_list_class_init (TerminalSettingsListClass *klass)
  * Returns: (transfer full): the newly created #TerminalSettingsList
  */
 TerminalSettingsList *
-terminal_settings_list_new (GSettingsSchemaSource* schema_source,
+terminal_settings_list_new (GSettingsBackend* backend,
+                            GSettingsSchemaSource* schema_source,
                             const char *path,
                             const char *schema_id,
                             const char *child_schema_id,
                             TerminalSettingsListFlags flags)
 {
+  g_return_val_if_fail (backend == nullptr || G_IS_SETTINGS_BACKEND (backend), nullptr);
   g_return_val_if_fail (schema_source != nullptr, nullptr);
   g_return_val_if_fail (path != nullptr, nullptr);
   g_return_val_if_fail (schema_id != nullptr, nullptr);
@@ -687,6 +702,7 @@ terminal_settings_list_new (GSettingsSchemaSource* schema_source,
   g_return_val_if_fail (g_str_has_suffix (path, ":/"), nullptr);
 
   return reinterpret_cast<TerminalSettingsList*>(g_object_new (TERMINAL_TYPE_SETTINGS_LIST,
+                                                               "backend", backend,
                                                                "schema-source", schema_source,
                                                               "schema-id", schema_id,
                                                               "child-schema-id", child_schema_id,
@@ -728,7 +744,7 @@ terminal_settings_list_dup_default_child (TerminalSettingsList *list)
     return g_strdup (list->default_uuid);
 
   /* Just randomly designate the first child as default, but don't write that
-   * to dconf.
+   * to the settings.
    */
   if (list->uuids == nullptr || list->uuids[0] == nullptr) {
     g_warn_if_fail ((list->flags & TERMINAL_SETTINGS_LIST_FLAG_ALLOW_EMPTY));
diff --git a/src/terminal-settings-list.hh b/src/terminal-settings-list.hh
index f131c2fa..de997665 100644
--- a/src/terminal-settings-list.hh
+++ b/src/terminal-settings-list.hh
@@ -36,7 +36,8 @@ typedef struct _TerminalSettingsListClass TerminalSettingsListClass;
 
 GType terminal_settings_list_get_type (void);
 
-TerminalSettingsList *terminal_settings_list_new (GSettingsSchemaSource* schema_source,
+TerminalSettingsList *terminal_settings_list_new (GSettingsBackend* backend,
+                                                  GSettingsSchemaSource* schema_source,
                                                   const char *path,
                                                   const char *schema_id,
                                                   const char *child_schema_id,
diff --git a/src/terminal-util.cc b/src/terminal-util.cc
index 747737b8..36554315 100644
--- a/src/terminal-util.cc
+++ b/src/terminal-util.cc
@@ -39,6 +39,7 @@
 
 #include "terminal-accels.hh"
 #include "terminal-app.hh"
+#include "terminal-client-utils.hh"
 #include "terminal-intl.hh"
 #include "terminal-util.hh"
 #include "terminal-version.hh"
@@ -1906,9 +1907,22 @@ terminal_g_settings_schema_source_get_default(void)
 {
   GSettingsSchemaSource* default_source = g_settings_schema_source_get_default();
 
+  gs_free auto schema_dir =
+    terminal_client_get_directory_uninstalled(
+#if defined(TERMINAL_SERVER)
+                                              TERM_LIBEXECDIR,
+#elif defined(TERMINAL_PREFERENCES)
+                                              TERM_PKGLIBDIR,
+#else
+#error Need to define installed location
+#endif
+                                              TERM_PKGLIBDIR,
+                                              "gschemas.compiled",
+                                              GFileTest(0));
+
   gs_free_error GError* error = nullptr;
   GSettingsSchemaSource* reference_source =
-    g_settings_schema_source_new_from_directory(TERM_PKGLIBDIR,
+    g_settings_schema_source_new_from_directory(schema_dir,
                                                 nullptr /* parent source */,
                                                 FALSE /* trusted */,
                                                 &error);
diff --git a/src/terminal-window.cc b/src/terminal-window.cc
index 76f18000..78fcbc1a 100644
--- a/src/terminal-window.cc
+++ b/src/terminal-window.cc
@@ -975,7 +975,8 @@ action_edit_preferences_cb (GSimpleAction *action,
 
   terminal_app_edit_preferences (terminal_app_get (),
                                  terminal_screen_get_profile (priv->active_screen),
-                                 nullptr);
+                                 nullptr,
+                                 gtk_get_current_event_time());
 }
 
 static void
diff --git a/src/terminal.cc b/src/terminal.cc
index 27ee91e5..ea1836ee 100644
--- a/src/terminal.cc
+++ b/src/terminal.cc
@@ -318,30 +318,30 @@ factory_proxy_new (TerminalOptions *options,
                                              error);
 }
 
-static void
-handle_show_preferences (TerminalOptions *options,
-                         const char *service_name)
+static bool
+handle_show_preferences_remote (TerminalOptions *options,
+                                const char *service_name)
 {
   gs_free_error GError *error = nullptr;
   gs_unref_object GDBusConnection *bus = nullptr;
   gs_free char *object_path = nullptr;
   GVariantBuilder builder;
 
-  bus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, &error);
-  if (bus == nullptr) {
-    terminal_printerr ("Failed to get session bus: %s\n", error->message);
-    return;
-  }
-
   /* For reasons (!?), the org.gtk.Actions interface's object path
    * is derived from the service name, i.e. for service name
    * "foo.bar.baz" the object path is "/foo/bar/baz".
    * This means that without the name (like when given only the unique name),
    * we cannot activate the action.
    */
-  if (g_dbus_is_unique_name(service_name)) {
-    terminal_printerr ("Cannot call this function from within gnome-terminal.\n");
-    return;
+  if (!service_name ||
+      g_dbus_is_unique_name(service_name)) {
+    return false;
+  }
+
+  bus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, &error);
+  if (bus == nullptr) {
+    terminal_printerr ("Failed to get session bus: %s\n", error->message);
+    return true;
   }
 
   object_path = g_strdelimit (g_strdup_printf (".%s", service_name), ".", '/');
@@ -368,7 +368,31 @@ handle_show_preferences (TerminalOptions *options,
                                     nullptr /* cancelleable */,
                                     &error)) {
     terminal_printerr ("Activate call failed: %s\n", error->message);
+    return true;
+  }
+
+  return true;
+}
+
+static void
+handle_show_preferences(TerminalOptions *options,
+                        const char *service_name)
+{
+  // First try remoting to the specified server
+  if (handle_show_preferences_remote(options, service_name))
     return;
+
+  // If that isn't possible, launch the prefs binary directly
+  auto launcher = g_subprocess_launcher_new(GSubprocessFlags(0));
+  gs_free auto exe = terminal_client_get_file_uninstalled(TERM_BINDIR,
+                                                          TERM_PKGLIBDIR,
+                                                          TERMINAL_PREFERENCES_BINARY_NAME,
+                                                          G_FILE_TEST_IS_EXECUTABLE);
+  char *argv[2] = {exe, nullptr};
+
+  gs_free_error GError* error = nullptr;
+  if (!g_subprocess_launcher_spawnv(launcher, argv, &error)) {
+    terminal_printerr ("Failed to launch preferences: %s\n", error->message);
   }
 }
 
diff --git a/src/terminal.gresource.xml b/src/terminal.gresource.xml
index 8fd50407..dfa8f5c6 100644
--- a/src/terminal.gresource.xml
+++ b/src/terminal.gresource.xml
@@ -23,7 +23,6 @@
     <file alias="ui/menubar-with-mnemonics.ui" compressed="true" 
preprocess="xml-stripblanks">terminal-menubar-with-mnemonics.ui</file>
     <file alias="ui/menubar-without-mnemonics.ui" compressed="true" 
preprocess="xml-stripblanks">terminal-menubar-without-mnemonics.ui</file>
     <file alias="ui/notebook-menu.ui" compressed="true" 
preprocess="xml-stripblanks">terminal-notebook-menu.ui</file>
-    <file alias="ui/preferences.ui" compressed="true" preprocess="xml-stripblanks">preferences.ui</file>
     <file alias="ui/search-popover.ui" compressed="true" 
preprocess="xml-stripblanks">search-popover.ui</file>
     <file alias="ui/terminal.about" compressed="true">terminal.about</file>
     <file alias="ui/window.ui" compressed="true" preprocess="xml-stripblanks">terminal-window.ui</file>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]