/*
* 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 .
*/
#include "config.h"
#include
#include
#include
#include
#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
inline constexpr auto
IMPL(T* that) noexcept
{
return reinterpret_cast(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(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
(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
);
}