[gnome-terminal/gnome-43] prefs: Make preferences dialogue OOP



commit b6837700d03f32d849baaeea6e30e6c10eaacd10
Author: Christian Persch <chpe src gnome org>
Date:   Thu Sep 15 17:53:31 2022 +0200

    prefs: Make preferences dialogue OOP
    
    Backported from master, comprising of commits:
    commit 0ebf9a5a442f6fd60d9cc9cc56dc15659cc31c95
    commit 267e79ee128a956da7769882e5c7d29967cebac6
    commit 24371c711ec61943a89eabc36c1450fe7e999930
    commit d0257f7ea7453d5305a5fd48a4a96f4db0ee7ce6
    commit e895306524f99c7d6fc1cc2ba58a7f572f8fd7fd
    commit bb6c80e6e06400e1720f3fc4a6e115934bbcd128
    commit 08100ecf33f2caf2a4aed36c0d9ed3a551bf10b7
    commit 9a28707a14cd0f37e8dce531adf0facef644b4b6
    commit f13c151ff5decc83a36d87ce60dc2019034b40db

 meson.build                               |   3 +-
 po/POTFILES.in                            |   6 +-
 src/meson.build                           | 217 ++++---
 src/org.gnome.Terminal.SettingsBridge.xml |  77 +++
 src/prefs-main.cc                         | 265 +++++++++
 src/prefs.gresource.xml                   |  23 +
 src/server.cc                             |   3 +-
 src/terminal-app.cc                       | 305 ++++++++--
 src/terminal-app.hh                       |   9 +-
 src/terminal-client-utils.cc              | 125 +++-
 src/terminal-client-utils.hh              |  17 +-
 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             | 513 ++++++++++++++++
 src/terminal-prefs-process.hh             |  53 ++
 src/terminal-prefs.cc                     |   7 +-
 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   | 579 ++++++++++++++++++
 src/terminal-settings-bridge-backend.hh   |  38 ++
 src/terminal-settings-bridge-impl.cc      | 419 +++++++++++++
 src/terminal-settings-bridge-impl.hh      |  38 ++
 src/terminal-settings-list.cc             | 184 +++---
 src/terminal-settings-list.hh             |   3 +-
 src/terminal-settings-utils.cc            | 937 ++++++++++++++++++++++++++++++
 src/terminal-settings-utils.hh            | 111 ++++
 src/terminal-util.cc                      | 365 +-----------
 src/terminal-util.hh                      |   2 -
 src/terminal-window.cc                    |   3 +-
 src/terminal.cc                           |  48 +-
 src/terminal.gresource.xml                |   1 -
 36 files changed, 3728 insertions(+), 665 deletions(-)
---
diff --git a/meson.build b/meson.build
index 2a282712..aea3603a 100644
--- a/meson.build
+++ b/meson.build
@@ -55,7 +55,6 @@ gtk_req_version              = '3.22.27'
 gtk_min_req_version          = '3.18'
 gtk_max_allowed_version      = '3.24'
 
-dconf_req_version            = '0.14.0'
 libnautilus_ext_req_version  = '43'
 pcre2_req_version            = '10.00'
 schemas_req_version          = '0.1.0'
@@ -71,6 +70,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')
@@ -336,7 +336,6 @@ add_project_arguments(global_cxxflags, language: 'cpp')
 
 vte_dep       = dependency(vte_req,        version: '>=' + vte_req_version)
 
-dconf_dep     = dependency('dconf',        version: '>=' + dconf_req_version)
 gio_dep       = dependency('gio-2.0',      version: '>=' + glib_req_version)
 gio_unix_dep  = dependency('gio-unix-2.0', version: '>=' + glib_req_version)
 glib_dep      = dependency('glib-2.0',     version: '>=' + glib_req_version)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 07a1f29b..561f5a9e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,8 +1,11 @@
-data/org.gnome.Terminal.Nautilus.metainfo.xml.in
+# List of source files containing translatable strings.
+# Please keep this file sorted alphabetically.
 data/org.gnome.Terminal.desktop.in
 data/org.gnome.Terminal.metainfo.xml.in
+data/org.gnome.Terminal.Nautilus.metainfo.xml.in
 src/org.gnome.Terminal.gschema.xml
 src/preferences.ui
+src/prefs-main.cc
 src/profile-editor.cc
 src/search-popover.ui
 src/server.cc
@@ -19,6 +22,7 @@ src/terminal-notebook-menu.ui
 src/terminal-notebook.cc
 src/terminal-options.cc
 src/terminal-prefs.cc
+src/terminal-prefs-process.cc
 src/terminal-screen.cc
 src/terminal-search-popover.cc
 src/terminal-tab-label.cc
diff --git a/src/meson.build b/src/meson.build
index 97271278..726c4cdd 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,12 +75,29 @@ 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',
@@ -56,6 +111,21 @@ 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,
+)
+
+settings_utils_sources = files(
+  'terminal-settings-utils.cc',
+  'terminal-settings-utils.hh',
+)
+
 regex_sources = files(
   'terminal-regex.hh',
 )
@@ -85,47 +155,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 + settings_utils_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 +204,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,15 +243,11 @@ 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 = [
-  dconf_dep,
   gio_dep,
   gio_unix_dep,
   glib_dep,
@@ -270,9 +305,45 @@ 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 + settings_utils_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(
+client_sources = client_util_sources + debug_sources + dbus_sources + i18n_sources + profiles_sources + 
settings_utils_sources + types_sources + files(
   'terminal-options.cc',
   'terminal-options.hh',
   'terminal.cc',
@@ -288,17 +359,11 @@ 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 = [
-  dconf_dep,
   gio_dep,
   gio_unix_dep,
   glib_dep,
diff --git a/src/org.gnome.Terminal.SettingsBridge.xml b/src/org.gnome.Terminal.SettingsBridge.xml
new file mode 100644
index 00000000..4dac6d78
--- /dev/null
+++ b/src/org.gnome.Terminal.SettingsBridge.xml
@@ -0,0 +1,77 @@
+<?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="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..07857916
--- /dev/null
+++ b/src/prefs-main.cc
@@ -0,0 +1,265 @@
+/*
+ * 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(_("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..59a4db15 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,29 @@
 #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-settings-utils.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 +86,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 +110,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 +120,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 +142,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 +364,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 +374,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 +400,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 +722,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 +796,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 +830,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 +847,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 +854,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 +888,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 +934,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 +979,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 +992,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 +1009,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 +1021,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 +1030,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 +1041,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 +1145,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 +1185,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 +1321,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 +1362,8 @@ terminal_app_get_profiles_list (TerminalApp *app)
   return app->profiles_list;
 }
 
+#ifdef TERMINAL_SERVER
+
 /**
  * terminal_app_get_menubar:
  * @app: a #TerminalApp
@@ -1222,6 +1418,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 +1520,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 +1529,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 9158d65c..fbe85d2f 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>
@@ -36,6 +42,102 @@
 #endif
 #endif
 
+#ifdef ENABLE_DEBUG
+
+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));
+}
+
+#endif /* ENABLE_DEBUG */
+
+/**
+ * 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}"
@@ -338,26 +440,3 @@ out:
 }
 
 #endif /* !TERMINAL_NAUTILUS */
-
-GSettings*
-terminal_g_settings_new_with_path (GSettingsSchemaSource* source,
-                                   char const* schema_id,
-                                   char const* path)
-{
-  gs_unref_settings_schema GSettingsSchema* schema =
-    g_settings_schema_source_lookup(source,
-                                    schema_id,
-                                    TRUE /* recursive */);
-  g_assert_nonnull(schema);
-
-  return g_settings_new_full(schema,
-                             nullptr /* default backend */,
-                             path);
-}
-
-GSettings*
-terminal_g_settings_new(GSettingsSchemaSource* source,
-                        char const* schema_id)
-{
-  return terminal_g_settings_new_with_path(source, schema_id, nullptr);
-}
diff --git a/src/terminal-client-utils.hh b/src/terminal-client-utils.hh
index 88a6fd56..33e5c909 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,13 +65,6 @@ 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,
-                                    char const* schema_id);
-
-GSettings* terminal_g_settings_new_with_path (GSettingsSchemaSource* source,
-                                              char const* schema_id,
-                                              char const* path);
-
 G_END_DECLS
 
 #endif /* TERMINAL_UTIL_UTILS_H */
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..e06b17fa
--- /dev/null
+++ b/src/terminal-prefs-process.cc
@@ -0,0 +1,513 @@
+/*
+ * 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 */
+{
+  g_application_hold(g_application_get_default());
+}
+
+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);
+
+  g_application_release(g_application_get_default());
+}
+
+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)
+{
+  gs_unref_object auto source = G_ASYNC_INITABLE(g_async_result_get_source_object(result));
+  auto const o = g_async_initable_new_finish(source, result, error);
+  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..1163f739 100644
--- a/src/terminal-prefs.cc
+++ b/src/terminal-prefs.cc
@@ -21,7 +21,6 @@
 #include <string.h>
 
 #include <uuid.h>
-#include <dconf.h>
 
 #include <glib.h>
 #include <glib/gi18n.h>
@@ -716,7 +715,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 +921,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 d39db301..9e3ad0e7 100644
--- a/src/terminal-screen.cc
+++ b/src/terminal-screen.cc
@@ -1516,7 +1516,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..2a6f2a13
--- /dev/null
+++ b/src/terminal-settings-bridge-backend.cc
@@ -0,0 +1,579 @@
+/*
+ *  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));
+}
diff --git a/src/terminal-settings-bridge-backend.hh b/src/terminal-settings-bridge-backend.hh
new file mode 100644
index 00000000..2b7c1887
--- /dev/null
+++ b/src/terminal-settings-bridge-backend.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 "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);
+
+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..ec387fd1
--- /dev/null
+++ b/src/terminal-settings-bridge-impl.cc
@@ -0,0 +1,419 @@
+/*
+ *  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-debug.hh"
+#include "terminal-libgsystem.hh"
+#include "terminal-settings-utils.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;
+  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 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_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 = terminal_g_settings_backend_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 = terminal_g_settings_backend_read(impl->backend,
+                                                             key,
+                                                             vtype,
+                                                             default_value);
+  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 = terminal_g_settings_backend_read_user_value(impl->backend,
+                                                                        key,
+                                                                        vtype);
+  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);
+  terminal_g_settings_backend_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);
+  terminal_g_settings_backend_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);
+  terminal_g_settings_backend_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);
+  terminal_g_settings_backend_unsubscribe(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)");
+
+  auto const r = terminal_g_settings_backend_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 = terminal_g_settings_backend_create_tree();
+
+  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 = terminal_g_settings_backend_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_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);
+}
+
+static void
+terminal_settings_bridge_impl_finalize(GObject* object) noexcept
+{
+  auto const impl = IMPL(object);
+  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..40b9a985 100644
--- a/src/terminal-settings-list.cc
+++ b/src/terminal-settings-list.cc
@@ -23,15 +23,11 @@
 #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-settings-utils.hh"
 #include "terminal-schemas.hh"
 #include "terminal-debug.hh"
 #include "terminal-libgsystem.hh"
@@ -39,6 +35,7 @@ extern "C" {
 struct _TerminalSettingsList {
   GSettings parent;
 
+  GSettingsBackend* settings_backend;
   GSettingsSchemaSource* schema_source;
   char *path;
   char *child_schema_id;
@@ -129,6 +126,8 @@ strv_find (char **strv,
   return -1;
 }
 
+#if defined(TERMINAL_SERVER) || defined(TERMINAL_PREFERENCES)
+
 static char **
 strv_dupv_insert (char **strv,
                   const char *str)
@@ -176,6 +175,8 @@ strv_dupv_remove (char **strv,
   return nstrv;
 }
 
+#endif /* TERMINAL_SERVER || TERMINAL_PREFERENCES */
+
 gboolean
 terminal_settings_list_valid_uuid (const char *str)
 {
@@ -187,15 +188,7 @@ 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");
-}
+#if defined(TERMINAL_SERVER) || defined(TERMINAL_PREFERENCES)
 
 static char *
 new_list_entry (void)
@@ -209,6 +202,8 @@ new_list_entry (void)
   return g_strdup (name);
 }
 
+#endif /* TERMINAL_SERVER || TERMINAL_PREFERENCES */
+
 static gboolean
 validate_list (TerminalSettingsList *list,
                char **entries)
@@ -245,9 +240,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 +265,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 */);
@@ -279,104 +275,48 @@ terminal_settings_list_ref_child_internal (TerminalSettingsList *list,
   return (GSettings*)g_object_ref(child);
 }
 
-static char *
-new_child (TerminalSettingsList *list,
-           const char *name)
-{
-  char *new_uuid = new_list_entry ();
-
-  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,
-                                        list->child_schema_id,
-                                        new_path);
-    g_settings_set_string (child, TERMINAL_PROFILE_VISIBLE_NAME_KEY, name);
-  }
-
-  return new_uuid;
-}
+#if defined(TERMINAL_SERVER) || defined(TERMINAL_PREFERENCES)
 
 static char *
-clone_child (TerminalSettingsList *list,
-             const char *uuid,
-             const char *name)
+terminal_settings_list_add_child_internal (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 ();
 
+  auto 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);
+                         "%s NEW UUID %s\n", G_STRFUNC, 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);
+
+  auto tree = terminal_g_settings_backend_create_tree();
+  terminal_g_settings_backend_clone_schema(list->settings_backend,
+                                           list->schema_source,
+                                           list->child_schema_id,
+                                           path,
+                                           new_path,
+                                           tree);
+  if (name) {
+    g_tree_insert(tree,
+                  g_strconcat(new_path, TERMINAL_PROFILE_VISIBLE_NAME_KEY, nullptr), // transfer
+                  g_variant_take_ref(g_variant_new_string(name))); // transfer
   }
 
-  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);
-    }
+#ifdef ENABLE_DEBUG
+  _TERMINAL_DEBUG_IF(TERMINAL_DEBUG_SETTINGS_LIST) {
+    g_printerr("Cloning schema %s from %s -> %s\n", list->child_schema_id, path, new_path);
+    terminal_g_settings_backend_print_tree(tree);
   }
+#endif
 
-  dconf_client_change_sync (client, changeset, nullptr, nullptr, nullptr);
-  dconf_changeset_unref (changeset);
-
-  return new_uuid;
-}
-
-static char *
-terminal_settings_list_add_child_internal (TerminalSettingsList *list,
-                                           const char *uuid,
-                                           const char *name)
-{
-  char *new_uuid;
-  gs_strfreev char **new_uuids;
+  auto const tag = &list;
+  (void)terminal_g_settings_backend_write_tree(list->settings_backend, tree, tag);
+  g_tree_unref(tree);
 
-  if (uuid && settings_backend_is_dconf ())
-    new_uuid = clone_child (list, uuid, name);
-  else
-    new_uuid = new_child (list, name);
-
-  _terminal_debug_print (TERMINAL_DEBUG_SETTINGS_LIST,
-                         "%s NEW UUID %s\n", G_STRFUNC, new_uuid);
-
-  new_uuids = strv_dupv_insert (list->uuids, new_uuid);
-  g_settings_set_strv (&list->parent, TERMINAL_SETTINGS_LIST_LIST_KEY,
-                       (const char * const *) new_uuids);
+  gs_strfreev auto new_uuids = strv_dupv_insert(list->uuids, new_uuid);
+  g_settings_set_strv(&list->parent, TERMINAL_SETTINGS_LIST_LIST_KEY,
+                      (char const* const*)new_uuids);
 
   return new_uuid;
 }
@@ -403,16 +343,15 @@ 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);
-  }
+  gs_free auto path = path_new(list, uuid);
+  terminal_g_settings_backend_erase_path(list->settings_backend,
+                                         list->schema_source,
+                                         list->child_schema_id,
+                                         path);
 }
 
+#endif /* TERMINAL_SERVER || TERMINAL_PREFERENCES */
+
 static void
 terminal_settings_list_update_list (TerminalSettingsList *list)
 {
@@ -500,7 +439,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 +466,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 +495,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 +611,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 +621,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 +636,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 +678,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));
@@ -824,6 +774,8 @@ terminal_settings_list_ref_default_child (TerminalSettingsList *list)
   return terminal_settings_list_ref_child_internal (list, uuid);
 }
 
+#if defined(TERMINAL_SERVER) || defined(TERMINAL_PREFERENCES)
+
 /**
  * terminal_settings_list_add_child:
  * @list: a #TerminalSettingsList
@@ -881,6 +833,8 @@ terminal_settings_list_remove_child (TerminalSettingsList *list,
   terminal_settings_list_remove_child_internal (list, uuid);
 }
 
+#endif /* TERMINAL_SERVER || TERMINAL_PREFERENCES */
+
 /**
  * terminal_settings_list_dup_uuid_from_child:
  * @list: a #TerminalSettingsList
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-settings-utils.cc b/src/terminal-settings-utils.cc
new file mode 100644
index 00000000..f64d9cc6
--- /dev/null
+++ b/src/terminal-settings-utils.cc
@@ -0,0 +1,937 @@
+/*
+ *  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 "terminal-settings-utils.hh"
+#include "terminal-client-utils.hh"
+#include "terminal-debug.hh"
+#include "terminal-libgsystem.hh"
+
+#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 (GSettingsBackend* backend,
+                                   GSettingsSchemaSource* source,
+                                   char const* schema_id,
+                                   char const* path)
+{
+  gs_unref_settings_schema GSettingsSchema* schema =
+    g_settings_schema_source_lookup(source,
+                                    schema_id,
+                                    TRUE /* recursive */);
+  g_assert_nonnull(schema);
+
+  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(GSettingsBackend* backend,
+                        GSettingsSchemaSource* source,
+                        char const* schema_id)
+{
+  return terminal_g_settings_new_with_path(backend, source, schema_id, nullptr);
+}
+
+#if defined(TERMINAL_SERVER) || defined(TERMINAL_PREFERENCES)
+
+void
+terminal_g_settings_backend_clone_schema(GSettingsBackend* backend,
+                                         GSettingsSchemaSource* schema_source,
+                                         char const* schema_id,
+                                         char const* path,
+                                         char const* new_path,
+                                         GTree* tree)
+{
+  gs_unref_settings_schema auto schema =
+    g_settings_schema_source_lookup(schema_source, schema_id, true);
+  if (schema == nullptr) [[unlikely]] // This shouldn't really happen ever
+    return;
+
+  gs_strfreev auto keys = g_settings_schema_list_keys(schema);
+
+  for (auto i = 0; keys[i]; ++i) {
+    gs_unref_settings_schema_key auto schema_key =
+      g_settings_schema_get_key(schema, keys[i]);
+
+    gs_free auto rkey = g_strconcat(path, keys[i], nullptr);
+    auto const value =
+      terminal_g_settings_backend_read(backend,
+                                       rkey,
+                                       g_settings_schema_key_get_value_type(schema_key),
+                                       false);
+
+    if (value) {
+      g_tree_insert(tree,
+                    g_strconcat(new_path, keys[i], nullptr), // transfer
+                    value); // transfer
+    }
+  }
+}
+
+gboolean
+terminal_g_settings_backend_erase_path(GSettingsBackend* backend,
+                                       GSettingsSchemaSource* schema_source,
+                                       char const* schema_id,
+                                       char const* path)
+
+{
+  // We want to erase all keys below @path, not just keys we wrote ourself
+  // or that are (currently) in a known schema.  DConf supports this kind of
+  // 'directory reset' by writing a NULL value for the non-key @path (i.e.
+  // which ends in a slash). However, neither g_settings_backend_reset() nor
+  // g_settings_backend_write() accept a non-key path, and the latter
+  // doesn't accept NULL values anyway. g_settings_backend_write_tree()
+  // does allow NULL values, and the DConf backend works fine with this and
+  // performs the directory reset, however it also (as is a documented
+  // requirement) calls g_settings_backend_changed_tree() which chokes on
+  // such a tree containing a non-key path.
+  //
+  // We could:
+  // 1. Just do nothing, i.e. leave the deleted settings lying around.
+  // 2. Fix glib. However, getting any improvements to gsettings into glib
+  //    seems almost impossible at this point.
+  // 3. Interpose a fixed g_settings_backend_changed_tree() that works
+  //    with these non-key paths. This will work with out-of-tree
+  //    settings backends like DConf. However, this will *not* work with
+  //    the settings backends inside libgio, like the memory and keyfile
+  //    backends, due to -Bsymbolic_functions.
+  // 4. At least reset those keys we know might exists, i.e. those in
+  //    the schema.
+  //
+  // Since I don't like 1, 2 is impossible, and 3 is too hacky, let's at least
+  // do 4.
+
+#if 0
+  // This is how this function would ideally work if glib was fixed (option 2 above)
+  auto tree = terminal_g_settings_backend_create_tree();
+  g_tree_insert(tree, g_strdup(path), nullptr);
+  auto const tag = &backend;
+  auto const r = terminal_g_settings_backend_write_tree(backend, tree, tag);
+  g_tree_unref(tree);
+#endif
+
+  gs_unref_settings_schema auto schema =
+    g_settings_schema_source_lookup(schema_source, schema_id, true);
+  if (schema == nullptr) [[unlikely]] // This shouldn't really happen ever
+    return false;
+
+  auto tree = terminal_g_settings_backend_create_tree();
+  gs_strfreev auto keys = g_settings_schema_list_keys(schema);
+
+  for (auto i = 0; keys[i]; ++i) {
+    g_tree_insert(tree,
+                  g_strconcat(path, keys[i], nullptr), // transfer
+                  nullptr); // reset key
+  }
+
+  auto const tag = &backend;
+  auto const r = terminal_g_settings_backend_write_tree(backend, tree, tag);
+  g_tree_unref(tree);
+  return r;
+}
+
+#define TERMINAL_SCHEMA_VERIFIER_ERROR (g_quark_from_static_string("TerminalSchemaVerifier"))
+
+typedef enum {
+  TERMINAL_SCHEMA_VERIFIER_SCHEMA_MISSING,
+  TERMINAL_SCHEMA_VERIFIER_SCHEMA_PATH,
+  TERMINAL_SCHEMA_VERIFIER_KEY_MISSING,
+  TERMINAL_SCHEMA_VERIFIER_KEY_TYPE,
+  TERMINAL_SCHEMA_VERIFIER_KEY_DEFAULT,
+  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE,
+  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE,
+  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_UNKNOWN,
+  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_MISMATCH,
+  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_ENUM_VALUE,
+  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_INTERVAL,
+  TERMINAL_SCHEMA_VERIFIER_CHILD_MISSING,
+} TerminalSchemaVerifierError;
+
+static gboolean
+strv_contains(char const* const* strv,
+              char const* str)
+{
+  if (strv == nullptr)
+    return FALSE;
+
+  for (size_t i = 0; strv[i]; i++) {
+    if (g_str_equal (strv[i], str))
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+static gboolean
+schema_key_range_compatible(GSettingsSchema* source_schema,
+                            GSettingsSchemaKey* source_key,
+                            char const* key,
+                            GSettingsSchemaKey* reference_key,
+                            GError** error)
+{
+  gs_unref_variant GVariant* source_range =
+    g_settings_schema_key_get_range(source_key);
+  gs_unref_variant GVariant* reference_range =
+    g_settings_schema_key_get_range(reference_key);
+
+  char const* source_type = nullptr;
+  gs_unref_variant GVariant* source_data = nullptr;
+  g_variant_get(source_range, "(&sv)", &source_type, &source_data);
+
+  char const* reference_type = nullptr;
+  gs_unref_variant GVariant* reference_data = nullptr;
+  g_variant_get(reference_range, "(&sv)", &reference_type, &reference_data);
+
+  if (!g_str_equal(source_type, reference_type)) {
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE,
+                "Schema \"%s\" key \"%s\" has range type \"%s\" but reference range type is \"%s\"",
+                g_settings_schema_get_id(source_schema),
+                key, source_type, reference_type);
+    return FALSE;
+  }
+
+  if (g_str_equal(reference_type, "type"))
+    ; /* no constraints; this is fine */
+  else if (g_str_equal(reference_type, "enum")) {
+    size_t source_values_len = 0;
+    gs_free char const** source_values = g_variant_get_strv(source_data, &source_values_len);
+
+    size_t reference_values_len = 0;
+    gs_free char const** reference_values = g_variant_get_strv(reference_data, &reference_values_len);
+
+    /* Check that every enum value in source is valid according to the reference */
+    for (size_t i = 0; i < source_values_len; ++i) {
+      if (!strv_contains(reference_values, source_values[i])) {
+        g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                    TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_ENUM_VALUE,
+                    "Schema \"%s\" key \"%s\" has enum value \"%s\" not in reference schema",
+                    g_settings_schema_get_id(source_schema),
+                    key, source_values[i]);
+        return FALSE;
+      }
+    }
+  } else if (g_str_equal(reference_type, "flags")) {
+    /* Our schemas don't use flags. If that changes, need to implement this! */
+    g_assert_not_reached();
+  } else if (g_str_equal(reference_type, "range")) {
+    if (!g_variant_is_of_type(source_data,
+                              g_variant_get_type(reference_data))) {
+      char const* source_type_str = g_variant_get_type_string(source_data);
+      char const* reference_type_str = g_variant_get_type_string(reference_data);
+      g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_MISMATCH,
+                  "Schema \"%s\" key \"%s\" has range type \"%s\" but reference range type is \"%s\"",
+                  g_settings_schema_get_id(source_schema),
+                  key, source_type_str, reference_type_str);
+      return FALSE;
+    }
+
+    gs_unref_variant GVariant* reference_min = nullptr;
+    gs_unref_variant GVariant* reference_max = nullptr;
+    g_variant_get(reference_data, "(**)", &reference_min, &reference_max);
+
+    gs_unref_variant GVariant* source_min = nullptr;
+    gs_unref_variant GVariant* source_max = nullptr;
+    g_variant_get(source_data, "(**)", &source_min, &source_max);
+
+    /* The source interval must be contained within the reference interval */
+    if (g_variant_compare(source_min, reference_min) < 0 ||
+        g_variant_compare(source_max, reference_max) > 0) {
+      g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_INTERVAL,
+                  "Schema \"%s\" key \"%s\" has range interval not contained in reference range interval",
+                  g_settings_schema_get_id(source_schema), key);
+        return FALSE;
+    }
+  } else {
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_UNKNOWN,
+                "Schema \"%s\" key \"%s\" has unknown range type \"%s\"",
+                g_settings_schema_get_id(source_schema),
+                key, reference_type);
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+schema_verify_key(GSettingsSchema* source_schema,
+                  char const* key,
+                  GSettingsSchema* reference_schema,
+                  GError** error)
+{
+  if (!g_settings_schema_has_key(source_schema, key)) {
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_KEY_MISSING,
+                "Schema \"%s\" has missing key \"%s\"",
+                g_settings_schema_get_id(source_schema), key);
+    return FALSE;
+  }
+
+  gs_unref_settings_schema_key GSettingsSchemaKey* source_key =
+    g_settings_schema_get_key(source_schema, key);
+  g_assert_nonnull(source_key);
+
+  gs_unref_settings_schema_key GSettingsSchemaKey* reference_key =
+    g_settings_schema_get_key(reference_schema, key);
+  g_assert_nonnull(reference_key);
+
+  GVariantType const* source_type = g_settings_schema_key_get_value_type(source_key);
+  GVariantType const* reference_type = g_settings_schema_key_get_value_type(reference_key);
+  if (!g_variant_type_equal(source_type, reference_type)) {
+    gs_free char* source_type_str = g_variant_type_dup_string(source_type);
+    gs_free char* reference_type_str = g_variant_type_dup_string(reference_type);
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_KEY_TYPE,
+                "Schema \"%s\" has type \"%s\" but reference type is \"%s\"",
+                g_settings_schema_get_id(source_schema),
+                source_type_str, reference_type_str);
+    return FALSE;
+  }
+
+  gs_unref_variant GVariant* source_default = g_settings_schema_key_get_default_value(source_key);
+  if (!g_settings_schema_key_range_check(reference_key, source_default)) {
+    gs_free char* source_value_str = g_variant_print(source_default, TRUE);
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_KEY_DEFAULT,
+                "Schema \"%s\" default value \"%s\" does not conform to reference schema",
+                g_settings_schema_get_id(source_schema), source_value_str);
+    return FALSE;
+  }
+
+  if (!schema_key_range_compatible(source_schema,
+                                   source_key,
+                                   key,
+                                   reference_key,
+                                   error))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+schema_verify_child(GSettingsSchema* source_schema,
+                    char const* child_name,
+                    GSettingsSchema* reference_schema,
+                    GError** error)
+{
+  /* Should verify the child's schema ID is as expected and exists in
+   * the source, but there appears to be no API to get the schema ID of
+   * the child.
+   *
+   * We work around this missing verification by never calling
+   * g_settings_get_child() and instead always constructing the child
+   * GSettings directly; and the existence and correctness of that
+   * schema is verified by the per-schema checks.
+   */
+
+  return TRUE;
+}
+
+static gboolean
+schema_verify(GSettingsSchema* source_schema,
+              GSettingsSchema* reference_schema,
+              GError** error)
+{
+  /* Verify path */
+  char const* source_path = g_settings_schema_get_path(source_schema);
+  char const* reference_path = g_settings_schema_get_path(reference_schema);
+  if (g_strcmp0(source_path, reference_path) != 0) {
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_SCHEMA_PATH,
+                "Schema \"%s\" has path \"%s\" but reference path is \"%s\"",
+                g_settings_schema_get_id(source_schema),
+                source_path ? source_path : "(null)",
+                reference_path ? reference_path : "(null)");
+    return FALSE;
+  }
+
+  /* Verify keys */
+  gs_strfreev char** keys = g_settings_schema_list_keys(reference_schema);
+  if (keys) {
+    for (int i = 0; keys[i]; ++i) {
+      if (!schema_verify_key(source_schema,
+                             keys[i],
+                             reference_schema,
+                             error))
+        return FALSE;
+    }
+  }
+
+  /* Verify child schemas */
+  gs_strfreev char** source_children = g_settings_schema_list_children(source_schema);
+  gs_strfreev char** reference_children = g_settings_schema_list_children(reference_schema);
+  if (reference_children) {
+    for (size_t i = 0; reference_children[i]; ++i) {
+      if (!strv_contains((char const* const*)source_children, reference_children[i])) {
+        g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                    TERMINAL_SCHEMA_VERIFIER_CHILD_MISSING,
+                    "Schema \"%s\" has missing child \"%s\"",
+                    g_settings_schema_get_id(source_schema),
+                    reference_children[i]);
+        return FALSE;
+      }
+
+      if (!schema_verify_child(source_schema,
+                               reference_children[i],
+                               reference_schema,
+                               error))
+          return FALSE;
+    }
+  }
+
+  return TRUE;
+}
+
+static gboolean
+schemas_source_verify_schema_by_name(GSettingsSchemaSource* source,
+                                     char const* schema_name,
+                                     GSettingsSchemaSource* reference_source,
+                                     GError** error)
+{
+  gs_unref_settings_schema GSettingsSchema* source_schema =
+    g_settings_schema_source_lookup(source, schema_name, TRUE /* recursive */);
+
+  if (!source_schema) {
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_SCHEMA_MISSING,
+                "Schema \"%s\" is missing", schema_name);
+    return FALSE;
+  }
+
+  gs_unref_settings_schema GSettingsSchema* reference_schema =
+    g_settings_schema_source_lookup(reference_source,
+                                    schema_name,
+                                    FALSE /* recursive */);
+  g_assert_nonnull(reference_schema);
+
+  return schema_verify(source_schema,
+                       reference_schema,
+                       error);
+}
+
+static gboolean
+schemas_source_verify_schemas(GSettingsSchemaSource* source,
+                              char const* const* schemas,
+                              GSettingsSchemaSource* reference_source,
+                              GError** error)
+{
+  if (!schemas)
+    return TRUE;
+
+  for (int i = 0; schemas[i]; ++i) {
+    if (!schemas_source_verify_schema_by_name(source,
+                                              schemas[i],
+                                              reference_source,
+                                              error))
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+schemas_source_verify(GSettingsSchemaSource* source,
+                      GSettingsSchemaSource* reference_source,
+                      GError** error)
+{
+  gs_strfreev char** reloc_schemas = nullptr;
+  gs_strfreev char** nonreloc_schemas = nullptr;
+
+  g_settings_schema_source_list_schemas(reference_source,
+                                        FALSE /* recursive */,
+                                        &reloc_schemas,
+                                        &nonreloc_schemas);
+
+  if (!schemas_source_verify_schemas(source,
+                                     (char const* const*)reloc_schemas,
+                                     reference_source,
+                                     error))
+    return FALSE;
+
+  if (!schemas_source_verify_schemas(source,
+                                     (char const* const*)nonreloc_schemas,
+                                     reference_source,
+                                     error))
+    return FALSE;
+
+  return TRUE;
+}
+
+GSettingsSchemaSource*
+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(schema_dir,
+                                                nullptr /* parent source */,
+                                                FALSE /* trusted */,
+                                                &error);
+  if (!reference_source)  {
+    /* Can only use the installed schemas, or abort here. */
+    g_printerr("Failed to load reference schemas: %s\n"
+               "Using unverified installed schemas.\n",
+               error->message);
+
+    return g_settings_schema_source_ref(default_source);
+  }
+
+  if (!schemas_source_verify(default_source, reference_source, &error)) {
+    g_printerr("Installed schemas failed verification: %s\n"
+               "Falling back to built-in reference schemas.\n",
+               error->message);
+
+    return reference_source; /* transfer */
+  }
+
+  /* Installed schemas verified; use them. */
+  g_settings_schema_source_unref(reference_source);
+  return g_settings_schema_source_ref(default_source);
+}
+
+#endif /* TERMINAL_SERVER || TERMINAL_PREFERENCES */
+
+// BEGIN copied from glib/gio/gsettingsbackend.c
+
+/*
+ * Copyright © 2009, 2010 Codethink Limited
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt desrt ca>
+ *          Matthias Clasen <mclasen redhat com>
+ */
+
+static void
+variant_unref0(void* data)
+{
+  if (data)
+    g_variant_unref(reinterpret_cast<GVariant*>(data));
+}
+
+static int
+compare_string(void const* a,
+               void const* b,
+               void* closure)
+{
+  return strcmp(reinterpret_cast<char const*>(a),
+                reinterpret_cast<char const*>(b));
+}
+
+/*
+ * terminal_g_settings_backend_create_tree:
+ *
+ * This is a convenience function for creating a tree that is compatible
+ * with terminal_g_settings_backend_write().  It merely calls g_tree_new_full()
+ * with strcmp(), g_free() and g_variant_unref().
+ *
+ * Returns: (transfer full): a new #GTree
+ */
+GTree*
+terminal_g_settings_backend_create_tree(void)
+{
+  return g_tree_new_full(compare_string, nullptr,
+                         g_free,
+                         variant_unref0);
+}
+
+#ifdef ENABLE_DEBUG
+
+static gboolean
+print_tree(void* key,
+           void* value,
+           void* closure)
+{
+  g_printerr("  %s => %s\n",
+             reinterpret_cast<char const*>(key),
+             value ? g_variant_print(reinterpret_cast<GVariant*>(value), true): "(null)");
+
+  return false; // continue
+}
+
+void
+terminal_g_settings_backend_print_tree(GTree* tree)
+{
+  g_printerr("Settings tree: [\n");
+  g_tree_foreach(tree, print_tree, nullptr);
+  g_printerr("]\n");
+}
+
+#endif /* ENABLE_DEBUG */
+
+#if defined(TERMINAL_SERVER) || defined(TERMINAL_PREFERENCES)
+
+/*
+ * g_settings_backend_read:
+ * @backend: a #GSettingsBackend implementation
+ * @key: the key to read
+ * @expected_type: a #GVariantType
+ * @default_value: if the default value should be returned
+ *
+ * Reads a key. This call will never block.
+ *
+ * If the key exists, the value associated with it will be returned.
+ * If the key does not exist, %nullptr will be returned.
+ *
+ * The returned value will be of the type given in @expected_type.  If
+ * the backend stored a value of a different type then %nullptr will be
+ * returned.
+ *
+ * If @default_value is %TRUE then this gets the default value from the
+ * backend (ie: the one that the backend would contain if
+ * g_settings_reset() were called).
+ *
+ * Returns: (nullable) (transfer full): the value that was read, or %nullptr
+ */
+GVariant*
+terminal_g_settings_backend_read(GSettingsBackend* backend,
+                                 char const* key,
+                                 GVariantType const* expected_type,
+                                 gboolean default_value)
+{
+  auto value = G_SETTINGS_BACKEND_GET_CLASS(backend)->read (backend,
+                                                            key,
+                                                            expected_type,
+                                                            default_value);
+
+  if (value)
+    value = g_variant_take_ref(value);
+
+  if (value && !g_variant_is_of_type(value, expected_type)) [[unlikely]] {
+    g_clear_pointer(&value, g_variant_unref);
+  }
+
+  return value;
+}
+
+/*
+ * terminal_g_settings_backend_read_user_value:
+ * @backend: a #GSettingsBackend implementation
+ * @key: the key to read
+ * @expected_type: a #GVariantType
+ *
+ * Reads the 'user value' of a key.
+ *
+ * This is the value of the key that the user has control over and has
+ * set for themselves.  Put another way: if the user did not set the
+ * value for themselves, then this will return %nullptr(even if the
+ * sysadmin has provided a default value).
+ *
+ * Returns:(nullable)(transfer full): the value that was read, or %nullptr
+ */
+GVariant*
+terminal_g_settings_backend_read_user_value(GSettingsBackend* backend,
+                                            char const*key,
+                                            GVariantType const* expected_type)
+{
+  auto value = G_SETTINGS_BACKEND_GET_CLASS(backend)->read_user_value(backend,
+                                                                      key,
+                                                                      expected_type);
+
+  if (value)
+    value = g_variant_take_ref(value);
+
+  if (value && !g_variant_is_of_type(value, expected_type)) [[unlikely]] {
+    g_clear_pointer(&value, g_variant_unref);
+  }
+
+  return value;
+}
+
+/*
+ * terminal_g_settings_backend_write:
+ * @backend: a #GSettingsBackend implementation
+ * @key: the name of the key
+ * @value: a #GVariant value to write to this key
+ * @origin_tag: the origin tag
+ *
+ * Writes exactly one key.
+ *
+ * This call does not fail.  During this call a
+ * #GSettingsBackend::changed signal will be emitted if the value of the
+ * key has changed.  The updated key value will be visible to any signal
+ * callbacks.
+ *
+ * One possible method that an implementation might deal with failures is
+ * to emit a second "changed" signal(either during this call, or later)
+ * to indicate that the affected keys have suddenly "changed back" to their
+ * old values.
+ *
+ * If @value has a floating reference, it will be sunk.
+ *
+ * Returns: %TRUE if the write succeeded, %FALSE if the key was not writable
+ */
+gboolean
+terminal_g_settings_backend_write(GSettingsBackend* backend,
+                                  char const* key,
+                                  GVariant* value,
+                                  void* origin_tag)
+{
+  g_variant_ref_sink(value);
+  auto const success = G_SETTINGS_BACKEND_GET_CLASS(backend)->write(backend,
+                                                                    key,
+                                                                    value,
+                                                                    origin_tag);
+  g_variant_unref(value);
+
+  return success;
+}
+
+/*
+ * terminal_g_settings_backend_write_tree:
+ * @backend: a #GSettingsBackend implementation
+ * @tree: a #GTree containing key-value pairs to write
+ * @origin_tag: the origin tag
+ *
+ * Writes one or more keys.  This call will never block.
+ *
+ * The key of each item in the tree is the key name to write to and the
+ * value is a #GVariant to write.  The proper type of #GTree for this
+ * call can be created with terminal_g_settings_backend_create_tree().  This call
+ * might take a reference to the tree; you must not modified the #GTree
+ * after passing it to this call.
+ *
+ * This call does not fail.  During this call a #GSettingsBackend::changed
+ * signal will be emitted if any keys have been changed.  The new values of
+ * all updated keys will be visible to any signal callbacks.
+ *
+ * One possible method that an implementation might deal with failures is
+ * to emit a second "changed" signal(either during this call, or later)
+ * to indicate that the affected keys have suddenly "changed back" to their
+ * old values.
+ */
+gboolean
+terminal_g_settings_backend_write_tree(GSettingsBackend* backend,
+                                       GTree* tree,
+                                       void* origin_tag)
+{
+  return G_SETTINGS_BACKEND_GET_CLASS(backend)->write_tree(backend,
+                                                           tree,
+                                                           origin_tag);
+}
+
+/*
+ * terminal_g_settings_backend_reset:
+ * @backend: a #GSettingsBackend implementation
+ * @key: the name of a key
+ * @origin_tag: the origin tag
+ *
+ * "Resets" the named key to its "default" value(ie: after system-wide
+ * defaults, mandatory keys, etc. have been taken into account) or possibly
+ * unsets it.
+ */
+void
+terminal_g_settings_backend_reset(GSettingsBackend*backend,
+                                  char const* key,
+                                  void* origin_tag)
+{
+  G_SETTINGS_BACKEND_GET_CLASS(backend)->reset(backend, key, origin_tag);
+}
+
+/*
+ * terminal_g_settings_backend_get_writable:
+ * @backend: a #GSettingsBackend implementation
+ * @key: the name of a key
+ *
+ * Finds out if a key is available for writing to.  This is the
+ * interface through which 'lockdown' is implemented.  Locked down
+ * keys will have %FALSE returned by this call.
+ *
+ * You should not write to locked-down keys, but if you do, the
+ * implementation will deal with it.
+ *
+ * Returns: %TRUE if the key is writable
+ */
+gboolean
+terminal_g_settings_backend_get_writable(GSettingsBackend* backend,
+                                         char const* key)
+{
+  return G_SETTINGS_BACKEND_GET_CLASS(backend)->get_writable(backend, key);
+}
+
+/*
+ * terminal_g_settings_backend_unsubscribe:
+ * @backend: a #GSettingsBackend
+ * @name: a key or path to subscribe to
+ *
+ * Reverses the effect of a previous call to
+ * terminal_g_settings_backend_subscribe().
+ */
+void
+terminal_g_settings_backend_unsubscribe(GSettingsBackend* backend,
+                                        const char* name)
+{
+  G_SETTINGS_BACKEND_GET_CLASS(backend)->unsubscribe(backend, name);
+}
+
+/*
+ * terminal_g_settings_backend_subscribe:
+ * @backend: a #GSettingsBackend
+ * @name: a key or path to subscribe to
+ *
+ * Requests that change signals be emitted for events on @name.
+ */
+void
+terminal_g_settings_backend_subscribe(GSettingsBackend* backend,
+                                      char const* name)
+{
+  G_SETTINGS_BACKEND_GET_CLASS(backend)->subscribe(backend, name);
+}
+
+/*
+ * terminal_g_settings_backend_get_permission:
+ * @backend: a #GSettingsBackend
+ * @path: a path
+ *
+ * Gets the permission object associated with writing to keys below
+ * @path on @backend.
+ *
+ * If this is not implemented in the backend, then a %TRUE
+ * #GSimplePermission is returned.
+ *
+ * Returns:(not nullable)(transfer full): a non-%nullptr #GPermission.
+ *     Free with g_object_unref()
+ */
+GPermission*
+terminal_g_settings_backend_get_permission(GSettingsBackend* backend,
+                                           char const* path)
+{
+  auto const klass = G_SETTINGS_BACKEND_GET_CLASS(backend);
+  if (klass->get_permission)
+    return klass->get_permission(backend, path);
+
+  return g_simple_permission_new(TRUE);
+}
+
+/*
+ * terminal_g_settings_backend_sync_default:
+ *
+ * Syncs.
+ */
+void
+terminal_g_settings_backend_sync(GSettingsBackend* backend)
+{
+  auto const klass = G_SETTINGS_BACKEND_GET_CLASS(backend);
+  if (klass->sync)
+    klass->sync(backend);
+}
+
+// END copied from glib
+
+#endif /* TERMINAL_SERVER || TERMINAL_PREFERENCES */
diff --git a/src/terminal-settings-utils.hh b/src/terminal-settings-utils.hh
new file mode 100644
index 00000000..9079485e
--- /dev/null
+++ b/src/terminal-settings-utils.hh
@@ -0,0 +1,111 @@
+/*
+ * 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>
+#include <gio/gsettingsbackend.h>
+
+GSettings* terminal_g_settings_new (GSettingsBackend* backend,
+                                    GSettingsSchemaSource* source,
+                                    char const* schema_id);
+
+GSettings* terminal_g_settings_new_with_path (GSettingsBackend* backend,
+                                              GSettingsSchemaSource* source,
+                                              char const* schema_id,
+                                              char const* path);
+
+void terminal_g_settings_backend_clone_schema(GSettingsBackend* backend,
+                                              GSettingsSchemaSource*schema_source,
+                                              char const* schema_id,
+                                              char const* path,
+                                              char const* new_path,
+                                              GTree* tree);
+
+gboolean terminal_g_settings_backend_erase_path(GSettingsBackend* backend,
+                                                GSettingsSchemaSource* schema_source,
+                                                char const* schema_id,
+                                                char const* path);
+
+GTree* terminal_g_settings_backend_create_tree(void);
+
+void terminal_g_settings_backend_print_tree(GTree* tree);
+
+GSettingsSchemaSource* terminal_g_settings_schema_source_get_default(void);
+
+GTree* terminal_g_settings_backend_create_tree(void);
+
+// BEGIN copied from glib/gio/gsettingsbackendinternal.h
+
+/*
+ * Copyright © 2009, 2010 Codethink Limited
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt desrt ca>
+ *          Matthias Clasen <mclasen redhat com>
+ */
+
+GPermission* terminal_g_settings_backend_get_permission(GSettingsBackend* backend,
+                                                        char const*path);
+
+gboolean terminal_g_settings_backend_get_writable(GSettingsBackend* backend,
+                                                  const char* key);
+
+GVariant* terminal_g_settings_backend_read(GSettingsBackend* backend,
+                                           char const* key,
+                                           GVariantType const* expected_type,
+                                           gboolean default_value);
+
+GVariant* terminal_g_settings_backend_read_user_value(GSettingsBackend* backend,
+                                                      char const* key,
+                                                      GVariantType const* expected_type);
+
+void terminal_g_settings_backend_reset(GSettingsBackend* backend,
+                                       char const* key,
+                                       void* origin_tag);
+
+void terminal_g_settings_backend_subscribe(GSettingsBackend* backend,
+                                           const char* name);
+
+void terminal_g_settings_backend_sync(GSettingsBackend* backend);
+
+void terminal_g_settings_backend_unsubscribe(GSettingsBackend* backend,
+                                             const char* name);
+
+gboolean terminal_g_settings_backend_write(GSettingsBackend* backend,
+                                           char const* key,
+                                           GVariant* value,
+                                           void* origin_tag);
+
+gboolean terminal_g_settings_backend_write_tree(GSettingsBackend* backend,
+                                                GTree* tree,
+                                                void* origin_tag);
+
+// END copied from glib
diff --git a/src/terminal-util.cc b/src/terminal-util.cc
index 747737b8..b430efca 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"
@@ -1569,367 +1570,3 @@ terminal_util_check_envv(char const* const* strv)
 
   return TRUE;
 }
-
-#define TERMINAL_SCHEMA_VERIFIER_ERROR (g_quark_from_static_string("TerminalSchemaVerifier"))
-
-typedef enum {
-  TERMINAL_SCHEMA_VERIFIER_SCHEMA_MISSING,
-  TERMINAL_SCHEMA_VERIFIER_SCHEMA_PATH,
-  TERMINAL_SCHEMA_VERIFIER_KEY_MISSING,
-  TERMINAL_SCHEMA_VERIFIER_KEY_TYPE,
-  TERMINAL_SCHEMA_VERIFIER_KEY_DEFAULT,
-  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE,
-  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE,
-  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_UNKNOWN,
-  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_MISMATCH,
-  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_ENUM_VALUE,
-  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_INTERVAL,
-  TERMINAL_SCHEMA_VERIFIER_CHILD_MISSING,
-} TerminalSchemaVerifierError;
-
-static gboolean
-strv_contains(char const* const* strv,
-              char const* str)
-{
-  if (strv == nullptr)
-    return FALSE;
-
-  for (size_t i = 0; strv[i]; i++) {
-    if (g_str_equal (strv[i], str))
-      return TRUE;
-  }
-
-  return FALSE;
-}
-
-static gboolean
-schema_key_range_compatible(GSettingsSchema* source_schema,
-                            GSettingsSchemaKey* source_key,
-                            char const* key,
-                            GSettingsSchemaKey* reference_key,
-                            GError** error)
-{
-  gs_unref_variant GVariant* source_range =
-    g_settings_schema_key_get_range(source_key);
-  gs_unref_variant GVariant* reference_range =
-    g_settings_schema_key_get_range(reference_key);
-
-  char const* source_type = nullptr;
-  gs_unref_variant GVariant* source_data = nullptr;
-  g_variant_get(source_range, "(&sv)", &source_type, &source_data);
-
-  char const* reference_type = nullptr;
-  gs_unref_variant GVariant* reference_data = nullptr;
-  g_variant_get(reference_range, "(&sv)", &reference_type, &reference_data);
-
-  if (!g_str_equal(source_type, reference_type)) {
-    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
-                TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE,
-                "Schema \"%s\" key \"%s\" has range type \"%s\" but reference range type is \"%s\"",
-                g_settings_schema_get_id(source_schema),
-                key, source_type, reference_type);
-    return FALSE;
-  }
-
-  if (g_str_equal(reference_type, "type"))
-    ; /* no constraints; this is fine */
-  else if (g_str_equal(reference_type, "enum")) {
-    size_t source_values_len = 0;
-    gs_free char const** source_values = g_variant_get_strv(source_data, &source_values_len);
-
-    size_t reference_values_len = 0;
-    gs_free char const** reference_values = g_variant_get_strv(reference_data, &reference_values_len);
-
-    /* Check that every enum value in source is valid according to the reference */
-    for (size_t i = 0; i < source_values_len; ++i) {
-      if (!strv_contains(reference_values, source_values[i])) {
-        g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
-                    TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_ENUM_VALUE,
-                    "Schema \"%s\" key \"%s\" has enum value \"%s\" not in reference schema",
-                    g_settings_schema_get_id(source_schema),
-                    key, source_values[i]);
-        return FALSE;
-      }
-    }
-  } else if (g_str_equal(reference_type, "flags")) {
-    /* Our schemas don't use flags. If that changes, need to implement this! */
-    g_assert_not_reached();
-  } else if (g_str_equal(reference_type, "range")) {
-    if (!g_variant_is_of_type(source_data,
-                              g_variant_get_type(reference_data))) {
-      char const* source_type_str = g_variant_get_type_string(source_data);
-      char const* reference_type_str = g_variant_get_type_string(reference_data);
-      g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
-                  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_MISMATCH,
-                  "Schema \"%s\" key \"%s\" has range type \"%s\" but reference range type is \"%s\"",
-                  g_settings_schema_get_id(source_schema),
-                  key, source_type_str, reference_type_str);
-      return FALSE;
-    }
-
-    gs_unref_variant GVariant* reference_min = nullptr;
-    gs_unref_variant GVariant* reference_max = nullptr;
-    g_variant_get(reference_data, "(**)", &reference_min, &reference_max);
-
-    gs_unref_variant GVariant* source_min = nullptr;
-    gs_unref_variant GVariant* source_max = nullptr;
-    g_variant_get(source_data, "(**)", &source_min, &source_max);
-
-    /* The source interval must be contained within the reference interval */
-    if (g_variant_compare(source_min, reference_min) < 0 ||
-        g_variant_compare(source_max, reference_max) > 0) {
-      g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
-                  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_INTERVAL,
-                  "Schema \"%s\" key \"%s\" has range interval not contained in reference range interval",
-                  g_settings_schema_get_id(source_schema), key);
-        return FALSE;
-    }
-  } else {
-    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
-                TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_UNKNOWN,
-                "Schema \"%s\" key \"%s\" has unknown range type \"%s\"",
-                g_settings_schema_get_id(source_schema),
-                key, reference_type);
-    return FALSE;
-  }
-
-  return TRUE;
-}
-
-static gboolean
-schema_verify_key(GSettingsSchema* source_schema,
-                  char const* key,
-                  GSettingsSchema* reference_schema,
-                  GError** error)
-{
-  if (!g_settings_schema_has_key(source_schema, key)) {
-    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
-                TERMINAL_SCHEMA_VERIFIER_KEY_MISSING,
-                "Schema \"%s\" has missing key \"%s\"",
-                g_settings_schema_get_id(source_schema), key);
-    return FALSE;
-  }
-
-  gs_unref_settings_schema_key GSettingsSchemaKey* source_key =
-    g_settings_schema_get_key(source_schema, key);
-  g_assert_nonnull(source_key);
-
-  gs_unref_settings_schema_key GSettingsSchemaKey* reference_key =
-    g_settings_schema_get_key(reference_schema, key);
-  g_assert_nonnull(reference_key);
-
-  GVariantType const* source_type = g_settings_schema_key_get_value_type(source_key);
-  GVariantType const* reference_type = g_settings_schema_key_get_value_type(reference_key);
-  if (!g_variant_type_equal(source_type, reference_type)) {
-    gs_free char* source_type_str = g_variant_type_dup_string(source_type);
-    gs_free char* reference_type_str = g_variant_type_dup_string(reference_type);
-    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
-                TERMINAL_SCHEMA_VERIFIER_KEY_TYPE,
-                "Schema \"%s\" has type \"%s\" but reference type is \"%s\"",
-                g_settings_schema_get_id(source_schema),
-                source_type_str, reference_type_str);
-    return FALSE;
-  }
-
-  gs_unref_variant GVariant* source_default = g_settings_schema_key_get_default_value(source_key);
-  if (!g_settings_schema_key_range_check(reference_key, source_default)) {
-    gs_free char* source_value_str = g_variant_print(source_default, TRUE);
-    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
-                TERMINAL_SCHEMA_VERIFIER_KEY_DEFAULT,
-                "Schema \"%s\" default value \"%s\" does not conform to reference schema",
-                g_settings_schema_get_id(source_schema), source_value_str);
-    return FALSE;
-  }
-
-  if (!schema_key_range_compatible(source_schema,
-                                   source_key,
-                                   key,
-                                   reference_key,
-                                   error))
-    return FALSE;
-
-  return TRUE;
-}
-
-static gboolean
-schema_verify_child(GSettingsSchema* source_schema,
-                    char const* child_name,
-                    GSettingsSchema* reference_schema,
-                    GError** error)
-{
-  /* Should verify the child's schema ID is as expected and exists in
-   * the source, but there appears to be no API to get the schema ID of
-   * the child.
-   *
-   * We work around this missing verification by never calling
-   * g_settings_get_child() and instead always constructing the child
-   * GSettings directly; and the existence and correctness of that
-   * schema is verified by the per-schema checks.
-   */
-
-  return TRUE;
-}
-
-static gboolean
-schema_verify(GSettingsSchema* source_schema,
-              GSettingsSchema* reference_schema,
-              GError** error)
-{
-  /* Verify path */
-  char const* source_path = g_settings_schema_get_path(source_schema);
-  char const* reference_path = g_settings_schema_get_path(reference_schema);
-  if (g_strcmp0(source_path, reference_path) != 0) {
-    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
-                TERMINAL_SCHEMA_VERIFIER_SCHEMA_PATH,
-                "Schema \"%s\" has path \"%s\" but reference path is \"%s\"",
-                g_settings_schema_get_id(source_schema),
-                source_path ? source_path : "(null)",
-                reference_path ? reference_path : "(null)");
-    return FALSE;
-  }
-
-  /* Verify keys */
-  gs_strfreev char** keys = g_settings_schema_list_keys(reference_schema);
-  if (keys) {
-    for (int i = 0; keys[i]; ++i) {
-      if (!schema_verify_key(source_schema,
-                             keys[i],
-                             reference_schema,
-                             error))
-        return FALSE;
-    }
-  }
-
-  /* Verify child schemas */
-  gs_strfreev char** source_children = g_settings_schema_list_children(source_schema);
-  gs_strfreev char** reference_children = g_settings_schema_list_children(reference_schema);
-  if (reference_children) {
-    for (size_t i = 0; reference_children[i]; ++i) {
-      if (!strv_contains((char const* const*)source_children, reference_children[i])) {
-        g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
-                    TERMINAL_SCHEMA_VERIFIER_CHILD_MISSING,
-                    "Schema \"%s\" has missing child \"%s\"",
-                    g_settings_schema_get_id(source_schema),
-                    reference_children[i]);
-        return FALSE;
-      }
-
-      if (!schema_verify_child(source_schema,
-                               reference_children[i],
-                               reference_schema,
-                               error))
-          return FALSE;
-    }
-  }
-
-  return TRUE;
-}
-
-static gboolean
-schemas_source_verify_schema_by_name(GSettingsSchemaSource* source,
-                                     char const* schema_name,
-                                     GSettingsSchemaSource* reference_source,
-                                     GError** error)
-{
-  gs_unref_settings_schema GSettingsSchema* source_schema =
-    g_settings_schema_source_lookup(source, schema_name, TRUE /* recursive */);
-
-  if (!source_schema) {
-    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
-                TERMINAL_SCHEMA_VERIFIER_SCHEMA_MISSING,
-                "Schema \"%s\" is missing", schema_name);
-    return FALSE;
-  }
-
-  gs_unref_settings_schema GSettingsSchema* reference_schema =
-    g_settings_schema_source_lookup(reference_source,
-                                    schema_name,
-                                    FALSE /* recursive */);
-  g_assert_nonnull(reference_schema);
-
-  return schema_verify(source_schema,
-                       reference_schema,
-                       error);
-}
-
-static gboolean
-schemas_source_verify_schemas(GSettingsSchemaSource* source,
-                              char const* const* schemas,
-                              GSettingsSchemaSource* reference_source,
-                              GError** error)
-{
-  if (!schemas)
-    return TRUE;
-
-  for (int i = 0; schemas[i]; ++i) {
-    if (!schemas_source_verify_schema_by_name(source,
-                                              schemas[i],
-                                              reference_source,
-                                              error))
-      return FALSE;
-  }
-
-  return TRUE;
-}
-
-static gboolean
-schemas_source_verify(GSettingsSchemaSource* source,
-                      GSettingsSchemaSource* reference_source,
-                      GError** error)
-{
-  gs_strfreev char** reloc_schemas = nullptr;
-  gs_strfreev char** nonreloc_schemas = nullptr;
-
-  g_settings_schema_source_list_schemas(reference_source,
-                                        FALSE /* recursive */,
-                                        &reloc_schemas,
-                                        &nonreloc_schemas);
-
-  if (!schemas_source_verify_schemas(source,
-                                     (char const* const*)reloc_schemas,
-                                     reference_source,
-                                     error))
-    return FALSE;
-
-  if (!schemas_source_verify_schemas(source,
-                                     (char const* const*)nonreloc_schemas,
-                                     reference_source,
-                                     error))
-    return FALSE;
-
-  return TRUE;
-}
-
-
-GSettingsSchemaSource*
-terminal_g_settings_schema_source_get_default(void)
-{
-  GSettingsSchemaSource* default_source = g_settings_schema_source_get_default();
-
-  gs_free_error GError* error = nullptr;
-  GSettingsSchemaSource* reference_source =
-    g_settings_schema_source_new_from_directory(TERM_PKGLIBDIR,
-                                                nullptr /* parent source */,
-                                                FALSE /* trusted */,
-                                                &error);
-  if (!reference_source)  {
-    /* Can only use the installed schemas, or abort here. */
-    g_printerr("Failed to load reference schemas: %s\n"
-               "Using unverified installed schemas.\n",
-               error->message);
-
-    return g_settings_schema_source_ref(default_source);
-  }
-
-  if (!schemas_source_verify(default_source, reference_source, &error)) {
-    g_printerr("Installed schemas failed verification: %s\n"
-               "Falling back to built-in reference schemas.\n",
-               error->message);
-
-    return reference_source; /* transfer */
-  }
-
-  /* Installed schemas verified; use them. */
-  g_settings_schema_source_unref(reference_source);
-  return g_settings_schema_source_ref(default_source);
-}
diff --git a/src/terminal-util.hh b/src/terminal-util.hh
index f4b7292b..d980725e 100644
--- a/src/terminal-util.hh
+++ b/src/terminal-util.hh
@@ -71,8 +71,6 @@ char **terminal_util_get_etc_shells (void);
 
 gboolean terminal_util_get_is_shell (const char *command);
 
-GSettingsSchemaSource* terminal_g_settings_schema_source_get_default(void);
-
 const GdkRGBA *terminal_g_settings_get_rgba (GSettings  *settings,
                                              const char *key,
                                              GdkRGBA    *rgba);
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]