Drop notify-send and use libdbus-1 directly

This is mostly useful for more obscure distros that don't ship
notify-send together with libnotify.
This commit is contained in:
Robbert van der Helm
2022-10-28 19:43:22 +02:00
parent b58eca9ed1
commit af0f38c00b
5 changed files with 103 additions and 51 deletions
+8
View File
@@ -24,6 +24,10 @@ Versioning](https://semver.org/spec/v2.0.0.html).
aside from the audio thread pool extension. Support for the extension will be aside from the audio thread pool extension. Support for the extension will be
added in a future yabridge release as Windows-only plugins that rely on the added in a future yabridge release as Windows-only plugins that rely on the
feature get released. feature get released.
- Notifications are now sent by directly talking to D-Bus instead of using the
`notify-send` command line tool. This ensures that you'll always see
yabridge's notifications, even when using more niche distros where you may not
have `notify-send` installed by default.
- The new `editor_disable_host_scaling` compatibility prevents hosts from - The new `editor_disable_host_scaling` compatibility prevents hosts from
setting an explicit DPI scaling factor for the editor. In some cases this can setting an explicit DPI scaling factor for the editor. In some cases this can
help with inconsistent scaling when using HiDPI scaling. This option affects help with inconsistent scaling when using HiDPI scaling. This option affects
@@ -61,6 +65,10 @@ Versioning](https://semver.org/spec/v2.0.0.html).
(<https://github.com/free-audio/clap/issues/153>, (<https://github.com/free-audio/clap/issues/153>,
<https://github.com/free-audio/clap/pull/154>). Building against older <https://github.com/free-audio/clap/pull/154>). Building against older
versions will result in memory errors. versions will result in memory errors.
- The Meson build now requires the `libdbus-1` package to be installed.
Yabridge's binaries will not link against the shared library, but it does use
the definitions from the headers to dynamically link against D-Bus at runtime
when it needs to send a desktop notification.
## [4.0.2] - 2022-06-27 ## [4.0.2] - 2022-06-27
-6
View File
@@ -16,12 +16,6 @@ page lists some of those.
- An easier [updater](https://github.com/robbert-vdh/yabridge/issues/51) through - An easier [updater](https://github.com/robbert-vdh/yabridge/issues/51) through
a new `yabridgectl update` command for distros that don't package yabridge. a new `yabridgectl update` command for distros that don't package yabridge.
# For a major release
- Replace the use of `notify-send` for notifications with using `libdbus`
directly. Most systems will have both available by default, but some less
common distros split `notify-send` from the rest of the `libnotify` package.
# Somewhere in the future, possibly # Somewhere in the future, possibly
- CLAP audio thread pool support. Implementing this efficiently is less than - CLAP audio thread pool support. Implementing this efficiently is less than
+1 -1
View File
@@ -241,7 +241,7 @@ else
bitsery_dep = dependency('bitsery', version : '>=5.2.0') bitsery_dep = dependency('bitsery', version : '>=5.2.0')
endif endif
# The DBus headers are also only accessed through the include path. We don't # The D-Bus 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. # 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) dbus_dep = dependency('dbus-1').partial_dependency(compile_args : true, includes : true)
function2_dep = dependency('function2', version : '>=4.0.0') function2_dep = dependency('function2', version : '>=4.0.0')
+89 -36
View File
@@ -34,21 +34,34 @@ std::atomic<void*> libdbus_handle = nullptr;
std::mutex libdbus_mutex; std::mutex libdbus_mutex;
// We'll fetch all of these functions at runtime when send a first notification // We'll fetch all of these functions at runtime when send a first notification
decltype(dbus_connection_unref)* libdbus_connection_unref = nullptr; #define LIBDBUS_FUNCTIONS \
decltype(dbus_bus_get)* libdbus_bus_get = nullptr; X(dbus_bus_get) \
decltype(dbus_error_free)* libdbus_error_free = nullptr; X(dbus_connection_flush) \
decltype(dbus_error_init)* libdbus_error_init = nullptr; X(dbus_connection_send) \
decltype(dbus_error_is_set)* libdbus_error_is_set = nullptr; X(dbus_connection_set_exit_on_disconnect) \
decltype(dbus_connection_set_exit_on_disconnect)* X(dbus_connection_unref) \
libdbus_connection_set_exit_on_disconnect = nullptr; X(dbus_error_free) \
X(dbus_error_init) \
X(dbus_error_is_set) \
X(dbus_message_get_serial) \
X(dbus_message_iter_append_basic) \
X(dbus_message_iter_close_container) \
X(dbus_message_iter_init_append) \
X(dbus_message_iter_open_container) \
X(dbus_message_new_method_call) \
X(dbus_message_unref)
#define X(name) decltype(name)* lib##name = nullptr;
LIBDBUS_FUNCTIONS
#undef X
std::unique_ptr<DBusConnection, void (*)(DBusConnection*)> libdbus_connection( std::unique_ptr<DBusConnection, void (*)(DBusConnection*)> libdbus_connection(
nullptr, nullptr,
libdbus_connection_unref); libdbus_connection_unref);
/** /**
* Try to set up DBus. Returns `false` if a function could not be resolved or if * Try to set up D-Bus. Returns `false` if a function could not be resolved or
* we could not connect to the DBus session. * if we could not connect to the D-Bus session.
*/ */
bool setup_libdbus() { bool setup_libdbus() {
// If this function is called from two threads at the same time, then we can // If this function is called from two threads at the same time, then we can
@@ -68,7 +81,7 @@ bool setup_libdbus() {
return false; return false;
} }
#define LOAD_FUNCTION(name) \ #define X(name) \
do { \ do { \
lib##name = \ lib##name = \
reinterpret_cast<decltype(lib##name)>(dlsym(handle, #name)); \ reinterpret_cast<decltype(lib##name)>(dlsym(handle, #name)); \
@@ -78,18 +91,13 @@ bool setup_libdbus() {
"', not sending desktop notifications"); \ "', not sending desktop notifications"); \
return false; \ return false; \
} \ } \
} while (false) } while (false);
LOAD_FUNCTION(dbus_connection_unref); LIBDBUS_FUNCTIONS
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 #undef X
// With every function ready, we can try connecting to the DBus interface // With every function ready, we can try connecting to the D-Bus interface
DBusError error; DBusError error;
libdbus_error_init(&error); libdbus_error_init(&error);
@@ -97,7 +105,7 @@ bool setup_libdbus() {
libdbus_bus_get(DBusBusType::DBUS_BUS_SESSION, &error)); libdbus_bus_get(DBusBusType::DBUS_BUS_SESSION, &error));
if (libdbus_error_is_set(&error)) { if (libdbus_error_is_set(&error)) {
assert(error.message); assert(error.message);
logger.log("Could not connect to DBus session bus: " + logger.log("Could not connect to D-Bus session bus: " +
std::string(error.message)); std::string(error.message));
libdbus_error_free(&error); libdbus_error_free(&error);
@@ -119,7 +127,7 @@ bool setup_libdbus() {
bool send_notification(const std::string& title, bool send_notification(const std::string& title,
const std::string body, const std::string body,
std::optional<ghc::filesystem::path> origin) { std::optional<ghc::filesystem::path> origin) {
// The first time this function is called we'll need to set up the DBus // The first time this function is called we'll need to set up the D-Bus
// interface. Previously yabridge relied on notify-send, but some distros // interface. Previously yabridge relied on notify-send, but some distros
// don't install that by default. // don't install that by default.
if (!libdbus_handle && !setup_libdbus()) { if (!libdbus_handle && !setup_libdbus()) {
@@ -151,20 +159,65 @@ bool send_notification(const std::string& title,
} }
} }
Process process("notify-send"); // Actually sending the notification is done directly using the D-Bus API.
process.arg("--urgency=normal"); std::unique_ptr<DBusMessage, void (*)(DBusMessage*)> message(
process.arg("--app-name=yabridge"); libdbus_message_new_method_call(
process.arg(title); "org.freedesktop.Notifications", "/org/freedesktop/Notifications",
process.arg(formatted_body.str()); "org.freedesktop.Notifications", "Notify"),
libdbus_message_unref);
assert(message);
// We will have printed the message to the terminal anyways, so if the user // Can't use `dbus_message_append_args` because that doesn't support
// doesn't have libnotify installed we'll just fail silently // dictionaries
const auto result = process.spawn_get_status(); DBusMessageIter iter;
return std::visit( libdbus_message_iter_init_append(message.get(), &iter);
overload{
[](int status) -> bool { return status == EXIT_SUCCESS; }, const char* app_name = "yabridge";
[](const Process::CommandNotFound&) -> bool { return false; }, assert(
[](const std::error_code&) -> bool { return false; }, libdbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &app_name));
},
result); // TODO: Add the replacing thing, see if that helps prevent notifications
// from stacking up
const dbus_uint32_t replaces_id = 0;
assert(libdbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32,
&replaces_id));
const char* app_icon = "";
assert(
libdbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &app_icon));
const char* title_cstr = title.c_str();
assert(libdbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
&title_cstr));
const std::string formatted_body_str = formatted_body.str();
const char* formatted_body_cstr = formatted_body_str.c_str();
assert(libdbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
&formatted_body_cstr));
// Our actions array is empty
DBusMessageIter array_iter;
assert(libdbus_message_iter_open_container(
&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &array_iter));
assert(libdbus_message_iter_close_container(&iter, &array_iter));
// We also don't have any hints, but we can't use the simple
// `libdbus_message_append_args` API because we can't use it to add an empty
// hints dictionary
assert(libdbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}",
&array_iter));
assert(libdbus_message_iter_close_container(&iter, &array_iter));
// -1 is an implementation specific default duration
const dbus_int32_t expiry_timeout = -1;
assert(libdbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32,
&expiry_timeout));
// And after all of that we can finally send the actual notification
dbus_uint32_t message_serial = libdbus_message_get_serial(message.get());
const bool result = libdbus_connection_send(libdbus_connection.get(),
message.get(), &message_serial);
libdbus_connection_flush(libdbus_connection.get());
return result;
} }
+5 -8
View File
@@ -22,14 +22,11 @@
#include <ghc/filesystem.hpp> #include <ghc/filesystem.hpp>
#include <optional> #include <optional>
// TODO: At some point, provide an alternative to notify-send by dlopen()-ing
// libdbus instead. Some more obscure distros won't have notify-send
// available.
/** /**
* Send a desktop notification using `notify-send`. Used for diagnostics when a * Send a desktop notification using the D-Bus notifications protocol
* plugin fails to load since the user may not be checking the output in a * (<https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html>).
* terminal. * Used for diagnostics when a plugin fails to load since the user may not be
* checking the output in a terminal.
* *
* @param title The title (or technically, summary) of the notification. * @param title The title (or technically, summary) of the notification.
* @param body The message to display. This can contain line feeds, and it any * @param body The message to display. This can contain line feeds, and it any
@@ -40,7 +37,7 @@
* user can more easily navigate to the plugin's path. * user can more easily navigate to the plugin's path.
* *
* @return Whether the notification was sent. This will be false if * @return Whether the notification was sent. This will be false if
* `notify-send` is not available. * `libdbus-1.so` is not available.
*/ */
bool send_notification(const std::string& title, bool send_notification(const std::string& title,
const std::string body, const std::string body,