[gnome-terminal/gnome-43] prefs: Make preferences dialogue OOP
- From: Christian Persch <chpe src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-terminal/gnome-43] prefs: Make preferences dialogue OOP
- Date: Thu, 15 Sep 2022 15:55:03 +0000 (UTC)
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]