From d1b3de5fc00ac47e8c301099051f10547355c747 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 28 Oct 2022 16:01:43 +0200 Subject: [PATCH] Connect to the session message bus using libdbus-1 This is the first step of migrating the desktop notifications over to pure DBus. --- meson.build | 4 ++ src/chainloader/meson.build | 1 + src/common/notifications.cpp | 103 ++++++++++++++++++++++++++++++++++- src/plugin/meson.build | 3 + src/wine-host/meson.build | 2 + 5 files changed, 112 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index b7ae8d1c..cd09e821 100644 --- a/meson.build +++ b/meson.build @@ -240,6 +240,10 @@ else # version now. bitsery_dep = dependency('bitsery', version : '>=5.2.0') endif + +# The DBus headers are also only accessed through the include path. We don't +# link to libdbus-1 to make soname changes don't completely break yabridge. +dbus_dep = dependency('dbus-1').partial_dependency(compile_args : true, includes : true) function2_dep = dependency('function2', version : '>=4.0.0') ghc_filesystem_dep = dependency('ghc_filesystem', modules : 'ghcFilesystem::ghc_filesystem', version : '>=1.5.0') threads_dep = dependency('threads') diff --git a/src/chainloader/meson.build b/src/chainloader/meson.build index 2b9bfff6..4980a112 100644 --- a/src/chainloader/meson.build +++ b/src/chainloader/meson.build @@ -4,6 +4,7 @@ chainloader_deps = [ configuration_dep, + dbus_dep, dl_dep, ghc_filesystem_dep, rt_dep, diff --git a/src/common/notifications.cpp b/src/common/notifications.cpp index da27a1bf..9c85482c 100644 --- a/src/common/notifications.cpp +++ b/src/common/notifications.cpp @@ -16,14 +16,115 @@ #include "notifications.h" -#include +#include +#include +#include +#include +#include + +#include "logging/common.h" #include "process.h" #include "utils.h" +constexpr char libdbus_library_name[] = "libdbus-1.so"; + +std::atomic libdbus_handle = nullptr; +std::mutex libdbus_mutex; + +// We'll fetch all of these functions at runtime when send a first notification +decltype(dbus_connection_unref)* libdbus_connection_unref = nullptr; +decltype(dbus_bus_get)* libdbus_bus_get = nullptr; +decltype(dbus_error_free)* libdbus_error_free = nullptr; +decltype(dbus_error_init)* libdbus_error_init = nullptr; +decltype(dbus_error_is_set)* libdbus_error_is_set = nullptr; +decltype(dbus_connection_set_exit_on_disconnect)* + libdbus_connection_set_exit_on_disconnect = nullptr; + +std::unique_ptr libdbus_connection( + nullptr, + libdbus_connection_unref); + +/** + * Try to set up DBus. Returns `false` if a function could not be resolved or if + * we could not connect to the DBus session. + */ +bool setup_libdbus() { + // If this function is called from two threads at the same time, then we can + // skip this. `libdbus_handle` is only set at the very end of this function + // once every function pointer has been resolved + std::lock_guard lock(libdbus_mutex); + if (libdbus_handle) { + return true; + } + + Logger logger = Logger::create_exception_logger(); + + void* handle = dlopen(libdbus_library_name, RTLD_LAZY | RTLD_LOCAL); + if (!handle) { + logger.log("Could not load '" + std::string(libdbus_library_name) + + "', not sending desktop notifications"); + return false; + } + +#define LOAD_FUNCTION(name) \ + do { \ + lib##name = \ + reinterpret_cast(dlsym(handle, #name)); \ + if (!(name)) { \ + logger.log("Could not find '" + std::string(#name) + "' in '" + \ + std::string(libdbus_library_name) + \ + "', not sending desktop notifications"); \ + return false; \ + } \ + } while (false) + + LOAD_FUNCTION(dbus_connection_unref); + LOAD_FUNCTION(dbus_bus_get); + LOAD_FUNCTION(dbus_error_free); + LOAD_FUNCTION(dbus_error_init); + LOAD_FUNCTION(dbus_error_is_set); + LOAD_FUNCTION(dbus_connection_set_exit_on_disconnect); + +#undef LOAD_FUNCTION + + // With every function ready, we can try connecting to the DBus interface + DBusError error; + libdbus_error_init(&error); + + libdbus_connection.reset( + libdbus_bus_get(DBusBusType::DBUS_BUS_SESSION, &error)); + if (libdbus_error_is_set(&error)) { + assert(error.message); + logger.log("Could not connect to DBus session bus: " + + std::string(error.message)); + libdbus_error_free(&error); + + return false; + } + assert(libdbus_connection); + + // While the connection should not be closed while this plugin is alive, + // this does sound extremely dangerous and why is it enabled by default? + libdbus_connection_set_exit_on_disconnect(libdbus_connection.get(), false); + + // This is only set at the very end since this indicates that everything + // has been initialized properly + libdbus_handle.store(handle); + + return true; +} + bool send_notification(const std::string& title, const std::string body, std::optional origin) { + // The first time this function is called we'll need to set up the DBus + // interface. Previously yabridge relied on notify-send, but some distros + // don't install that by default. + if (!libdbus_handle && !setup_libdbus()) { + return false; + } + // I think there's a zero chance that we're going to call this function with // anything that even somewhat resembles HTML, but we should still do a // basic XML escape anyways. diff --git a/src/plugin/meson.build b/src/plugin/meson.build index d53e45bd..724fe59d 100644 --- a/src/plugin/meson.build +++ b/src/plugin/meson.build @@ -7,6 +7,7 @@ vst2_plugin_deps = [ asio_dep, bitsery_dep, + dbus_dep, dl_dep, ghc_filesystem_dep, rt_dep, @@ -21,6 +22,7 @@ if with_clap asio_dep, bitsery_dep, clap_dep, + dbus_dep, dl_dep, function2_dep, ghc_filesystem_dep, @@ -36,6 +38,7 @@ if with_vst3 asio_dep, bitsery_dep, + dbus_dep, dl_dep, function2_dep, ghc_filesystem_dep, diff --git a/src/wine-host/meson.build b/src/wine-host/meson.build index 20273123..91db1d1c 100644 --- a/src/wine-host/meson.build +++ b/src/wine-host/meson.build @@ -8,6 +8,7 @@ if is_64bit_system asio_dep, bitsery_dep, + dbus_dep, function2_dep, ghc_filesystem_dep, rt_dep, @@ -37,6 +38,7 @@ if with_bitbridge asio_dep, ghc_filesystem_dep, bitsery_dep, + dbus_dep, function2_dep, rt_dep, tomlplusplus_dep,