diff options
author | Christian Persch <chpe@src.gnome.org> | 2022-08-26 22:10:31 +0200 |
---|---|---|
committer | Christian Persch <chpe@src.gnome.org> | 2022-08-26 22:10:31 +0200 |
commit | 0ebf9a5a442f6fd60d9cc9cc56dc15659cc31c95 (patch) | |
tree | fa2f7cbc6de6d6aeccd8b6058cfe351445afd705 | |
parent | 4e3016e0493538ccb399e613e90d8d0cd2ae874c (diff) | |
download | gnome-terminal-0ebf9a5a442f6fd60d9cc9cc56dc15659cc31c95.tar.gz |
prefs: Make preferences dialogue OOP
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> |