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