summaryrefslogtreecommitdiffstats
path: root/src/terminal-prefs-process.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/terminal-prefs-process.cc')
-rw-r--r--src/terminal-prefs-process.cc513
1 files changed, 513 insertions, 0 deletions
diff --git a/src/terminal-prefs-process.cc b/src/terminal-prefs-process.cc
new file mode 100644
index 0000000..a19386f
--- /dev/null
+++ b/src/terminal-prefs-process.cc
@@ -0,0 +1,513 @@
+/*
+ * Copyright © 2020, 2022 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ *(at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+
+#include <glib.h>
+
+#include "terminal-settings-bridge-impl.hh"
+
+#include "terminal-app.hh"
+#include "terminal-client-utils.hh"
+#include "terminal-debug.hh"
+#include "terminal-defines.hh"
+#include "terminal-intl.hh"
+#include "terminal-libgsystem.hh"
+#include "terminal-prefs-process.hh"
+
+#include "terminal-settings-bridge-generated.h"
+
+struct _TerminalPrefsProcess {
+ GObject parent_instance;
+
+ GSubprocess* subprocess;
+ GCancellable* cancellable;
+ GDBusConnection *connection;
+ TerminalSettingsBridgeImpl* bridge_impl;
+};
+
+struct _TerminalPrefsProcessClass {
+ GObjectClass parent_class;
+
+ // Signals
+ void (*exited)(TerminalPrefsProcess* process,
+ int status);
+};
+
+enum {
+ SIGNAL_EXITED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+// helper functions
+
+template<typename T>
+inline constexpr auto
+IMPL(T* that) noexcept
+{
+ return reinterpret_cast<TerminalPrefsProcess*>(that);
+}
+
+// BEGIN copied from vte/src/libc-glue.hh
+
+static inline int
+fd_get_descriptor_flags(int fd) noexcept
+{
+ auto flags = int{};
+ do {
+ flags = fcntl(fd, F_GETFD);
+ } while (flags == -1 && errno == EINTR);
+
+ return flags;
+}
+
+static inline int
+fd_set_descriptor_flags(int fd,
+ int flags) noexcept
+{
+ auto r = int{};
+ do {
+ r = fcntl(fd, F_SETFD, flags);
+ } while (r == -1 && errno == EINTR);
+
+ return r;
+}
+
+static inline int
+fd_change_descriptor_flags(int fd,
+ int set_flags,
+ int unset_flags) noexcept
+{
+ auto const flags = fd_get_descriptor_flags(fd);
+ if (flags == -1)
+ return -1;
+
+ auto const new_flags = (flags | set_flags) & ~unset_flags;
+ if (new_flags == flags)
+ return 0;
+
+ return fd_set_descriptor_flags(fd, new_flags);
+}
+
+static inline int
+fd_get_status_flags(int fd) noexcept
+{
+ auto flags = int{};
+ do {
+ flags = fcntl(fd, F_GETFL, 0);
+ } while (flags == -1 && errno == EINTR);
+
+ return flags;
+}
+
+static inline int
+fd_set_status_flags(int fd,
+ int flags) noexcept
+{
+ auto r = int{};
+ do {
+ r = fcntl(fd, F_SETFL, flags);
+ } while (r == -1 && errno == EINTR);
+
+ return r;
+}
+
+static inline int
+fd_change_status_flags(int fd,
+ int set_flags,
+ int unset_flags) noexcept
+{
+ auto const flags = fd_get_status_flags(fd);
+ if (flags == -1)
+ return -1;
+
+ auto const new_flags = (flags | set_flags) & ~unset_flags;
+ if (new_flags == flags)
+ return 0;
+
+ return fd_set_status_flags(fd, new_flags);
+}
+
+static inline int
+fd_set_cloexec(int fd) noexcept
+{
+ return fd_change_descriptor_flags(fd, FD_CLOEXEC, 0);
+}
+
+static inline int
+fd_set_nonblock(int fd) noexcept
+{
+ return fd_change_status_flags(fd, O_NONBLOCK, 0);
+}
+
+// END copied from vte
+
+static int
+socketpair_cloexec_nonblock(int domain,
+ int type,
+ int protocol,
+ int sv[2]) noexcept
+{
+ auto r = int{};
+#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
+ r = socketpair(domain,
+ type | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ protocol,
+ sv);
+ if (r != -1)
+ return r;
+
+ // Maybe cloexec and/or nonblock aren't supported by the kernel
+ if (errno != EINVAL && errno != EPROTOTYPE)
+ return r;
+
+ // If so, fall back to applying the flags after the socketpair() call
+#endif /* SOCK_CLOEXEC && SOCK_NONBLOCK */
+
+ r = socketpair(domain, type, protocol, sv);
+ if (r == -1)
+ return r;
+
+ if (fd_set_cloexec(sv[0]) == -1 ||
+ fd_set_nonblock(sv[0]) == -1 ||
+ fd_set_cloexec(sv[1]) == -1 ||
+ fd_set_nonblock(sv[1]) == -1) {
+ close(sv[0]);
+ close(sv[1]);
+ return -1;
+ }
+
+ return r;
+}
+
+static void
+subprocess_wait_cb(GObject* source,
+ GAsyncResult* result,
+ void* user_data)
+{
+ gs_unref_object auto impl = IMPL(user_data); // ref added on g_subprocess_wait_async
+
+ gs_free_error GError* error = nullptr;
+ if (g_subprocess_wait_finish(impl->subprocess, result, &error)) {
+ } // else: @cancellable was cancelled
+
+ auto const status = g_subprocess_get_status(impl->subprocess);
+
+ g_signal_emit(impl, signals[SIGNAL_EXITED], 0, status);
+
+ g_clear_object(&impl->subprocess);
+}
+
+// GInitable implementation
+
+static gboolean
+terminal_prefs_process_initable_init(GInitable* initable,
+ GCancellable* cancellable,
+ GError** error) noexcept
+{
+ auto const impl = IMPL(initable);
+
+ // Create a private D-Bus connection between the server and the preferences
+ // process, over which we proxy the settings (since otherwise there would
+ // be no way to modify the server's settings on backends other than dconf).
+
+ int socket_fds[2]{-1, -1};
+ auto r = socketpair_cloexec_nonblock(AF_UNIX, SOCK_STREAM, 0, socket_fds);
+ if (r != 0) {
+ auto const errsv = errno;
+ g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv),
+ "Failed to create bridge socketpair: %s",
+ g_strerror(errsv));
+ return false;
+ }
+
+ // Launch process
+ auto const launcher = g_subprocess_launcher_new(GSubprocessFlags(0));
+ // or use G_SUBPROCESS_FLAGS_STDOUT_SILENCE | G_SUBPROCESS_FLAGS_STDERR_SILENCE ?
+
+ // Note that g_subprocess_launcher_set_cwd() is not necessary since
+ // the server's cwd is already $HOME.
+ g_subprocess_launcher_set_environ(launcher, nullptr); // inherit server's environment
+ g_subprocess_launcher_unsetenv(launcher, "DBUS_SESSION_BUS_ADDRESS");
+ g_subprocess_launcher_unsetenv(launcher, "DBUS_STARTER_BUS_TYPE");
+ // ? g_subprocess_launcher_setenv(launcher, "GSETTINGS_BACKEND", "bridge", true);
+ g_subprocess_launcher_take_fd(launcher, socket_fds[1], 3);
+ socket_fds[1] = -1;
+
+ gs_free auto exe = terminal_client_get_file_uninstalled(TERM_LIBEXECDIR,
+ TERM_LIBEXECDIR,
+ TERMINAL_PREFERENCES_BINARY_NAME,
+ G_FILE_TEST_IS_EXECUTABLE);
+
+ char *argv[16];
+ auto argc = 0;
+ _TERMINAL_DEBUG_IF(TERMINAL_DEBUG_BRIDGE) {
+ argv[argc++] = (char*)"vte-2.91";
+ argv[argc++] = (char*)"--fd=3";
+ argv[argc++] = (char*)"--";
+ argv[argc++] = (char*)"gdb";
+ argv[argc++] = (char*)"--args";
+ }
+ argv[argc++] = exe;
+ argv[argc++] = (char*)"--bus-fd=3";
+ argv[argc++] = nullptr;
+ g_assert(argc <= int(G_N_ELEMENTS(argv)));
+
+ impl->subprocess = g_subprocess_launcher_spawnv(launcher, // consumed
+ argv,
+ error);
+ if (!impl->subprocess) {
+ close(socket_fds[0]);
+ return false;
+ }
+
+ g_subprocess_wait_async(impl->subprocess,
+ impl->cancellable,
+ GAsyncReadyCallback(subprocess_wait_cb),
+ g_object_ref(initable));
+
+ // Create server end of the D-Bus connection
+ gs_unref_object auto socket = g_socket_new_from_fd(socket_fds[0], error);
+ socket_fds[0] = -1;
+
+ if (!socket) {
+ g_prefix_error(error, "Failed to create bridge GSocket: ");
+ g_subprocess_force_exit(impl->subprocess);
+ return false;
+ }
+
+ gs_unref_object auto sockconn =
+ g_socket_connection_factory_create_connection(socket);
+ if (!G_IS_IO_STREAM(sockconn)) {
+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Bridge socket has incorrect type %s", G_OBJECT_TYPE_NAME(sockconn));
+ g_subprocess_force_exit(impl->subprocess);
+ return false;
+ }
+
+ gs_free auto guid = g_dbus_generate_guid();
+
+ impl->connection =
+ g_dbus_connection_new_sync(G_IO_STREAM(sockconn),
+ guid,
+ GDBusConnectionFlags(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER |
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS),
+ nullptr, // auth observer,
+ cancellable,
+ error);
+ if (!impl->connection) {
+ g_prefix_error(error, "Failed to create bridge D-Bus connection: ");
+ g_subprocess_force_exit(impl->subprocess);
+ return false;
+ }
+
+ g_dbus_connection_set_exit_on_close(impl->connection, false);
+
+ auto const app = terminal_app_get();
+ impl->bridge_impl =
+ terminal_settings_bridge_impl_new(terminal_app_get_settings_backend(app));
+ if (!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(impl->bridge_impl),
+ impl->connection,
+ TERMINAL_SETTINGS_BRIDGE_OBJECT_PATH,
+ error)) {
+ g_prefix_error(error, "Failed to export D-Bus skeleton: ");
+ g_subprocess_force_exit(impl->subprocess);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+terminal_prefs_process_initable_iface_init(GInitableIface* iface) noexcept
+{
+ iface->init = terminal_prefs_process_initable_init;
+}
+
+static void
+terminal_prefs_process_async_initable_iface_init(GAsyncInitableIface* iface) noexcept
+{
+ // Use the default implementation which runs the GInitiable in a thread.
+}
+
+// Class Implementation
+
+G_DEFINE_TYPE_WITH_CODE(TerminalPrefsProcess,
+ terminal_prefs_process,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE,
+ terminal_prefs_process_initable_iface_init)
+ G_IMPLEMENT_INTERFACE(G_TYPE_ASYNC_INITABLE,
+ terminal_prefs_process_async_initable_iface_init))
+
+static void
+terminal_prefs_process_init(TerminalPrefsProcess* process) /* noexcept */
+{
+ g_application_hold(g_application_get_default());
+}
+
+static void
+terminal_prefs_process_finalize(GObject* object) noexcept
+{
+ auto const impl = IMPL(object);
+ g_clear_object(&impl->bridge_impl);
+ g_clear_object(&impl->connection);
+ g_clear_object(&impl->cancellable);
+ g_clear_object(&impl->subprocess);
+
+ G_OBJECT_CLASS(terminal_prefs_process_parent_class)->finalize(object);
+
+ g_application_release(g_application_get_default());
+}
+
+static void
+terminal_prefs_process_class_init(TerminalPrefsProcessClass* klass) /* noexcept */
+{
+ auto const gobject_class = G_OBJECT_CLASS(klass);
+ gobject_class->finalize = terminal_prefs_process_finalize;
+
+ signals[SIGNAL_EXITED] =
+ g_signal_new(I_("exited"),
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET(TerminalPrefsProcessClass, exited),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_INT);
+ g_signal_set_va_marshaller(signals[SIGNAL_EXITED],
+ G_OBJECT_CLASS_TYPE(klass),
+ g_cclosure_marshal_VOID__INTv);
+}
+
+// public API
+
+TerminalPrefsProcess*
+terminal_prefs_process_new_finish(GAsyncResult* result,
+ GError** error)
+{
+ gs_unref_object auto source = G_ASYNC_INITABLE(g_async_result_get_source_object(result));
+ auto const o = g_async_initable_new_finish(source, result, error);
+ return reinterpret_cast<TerminalPrefsProcess*>(o);
+}
+
+void
+terminal_prefs_process_new_async(GCancellable* cancellable,
+ GAsyncReadyCallback callback,
+ void* user_data)
+{
+ g_async_initable_new_async(TERMINAL_TYPE_PREFS_PROCESS,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ // properties,
+ nullptr);
+}
+
+TerminalPrefsProcess*
+terminal_prefs_process_new_sync(GCancellable* cancellable,
+ GError** error)
+{
+ return reinterpret_cast<TerminalPrefsProcess*>
+ (g_initable_new(TERMINAL_TYPE_PREFS_PROCESS,
+ cancellable,
+ error,
+ // properties,
+ nullptr));
+}
+
+void
+terminal_prefs_process_abort(TerminalPrefsProcess* process)
+{
+ g_return_if_fail(TERMINAL_IS_PREFS_PROCESS(process));
+
+ auto const impl = IMPL(process);
+ if (impl->subprocess)
+ g_subprocess_force_exit(impl->subprocess);
+}
+
+#ifdef ENABLE_DEBUG
+
+static void
+show_cb(GObject* source,
+ GAsyncResult* result,
+ void* user_data)
+{
+ gs_unref_object auto process = IMPL(user_data); // added on g_dbus_connection_call
+
+ gs_free_error GError *error = nullptr;
+ gs_unref_variant auto rv = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source),
+ result,
+ &error);
+
+ if (error)
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, "terminal_prefs_process_show failed: %s\n", error->message);
+}
+
+#endif /* ENABLE_DEBUG */
+
+void
+terminal_prefs_process_show(TerminalPrefsProcess* process,
+ char const* profile_uuid,
+ char const* hint,
+ unsigned timestamp)
+{
+ auto const impl = IMPL(process);
+
+ auto builder = GVariantBuilder{};
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("(sava{sv})"));
+ g_variant_builder_add(&builder, "s", "preferences");
+ g_variant_builder_open(&builder, G_VARIANT_TYPE("av")); // parameter
+ g_variant_builder_open(&builder, G_VARIANT_TYPE("v"));
+ g_variant_builder_open(&builder, G_VARIANT_TYPE("a{sv}"));
+ if (profile_uuid)
+ g_variant_builder_add(&builder, "{sv}", "profile", g_variant_new_string(profile_uuid));
+ if (hint)
+ g_variant_builder_add(&builder, "{sv}", "hint", g_variant_new_string(hint));
+ g_variant_builder_add(&builder, "{sv}", "timestamp", g_variant_new_uint32(timestamp));
+ g_variant_builder_close(&builder); // a{sv}
+ g_variant_builder_close(&builder); // v
+ g_variant_builder_close(&builder); // av
+ g_variant_builder_open(&builder, G_VARIANT_TYPE("a{sv}")); // platform data
+ g_variant_builder_close(&builder); // a{sv}
+
+ g_dbus_connection_call(impl->connection,
+ nullptr, // since not on a message bus
+ TERMINAL_PREFERENCES_OBJECT_PATH,
+ "org.gtk.Actions",
+ "Activate",
+ g_variant_builder_end(&builder),
+ G_VARIANT_TYPE("()"),
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ 30 * 1000, // ms timeout
+ impl->cancellable, // cancelleable
+#ifdef ENABLE_DEBUG
+ show_cb, // callback
+ g_object_ref(process) // callback data
+#else
+ nullptr, nullptr
+#endif
+ );
+}