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
added in a future yabridge release as Windows-only plugins that rely on the
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
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
@@ -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/pull/154>). Building against older
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
-6
View File
@@ -16,12 +16,6 @@ page lists some of those.
- An easier [updater](https://github.com/robbert-vdh/yabridge/issues/51) through
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
- 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')
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.
dbus_dep = dependency('dbus-1').partial_dependency(compile_args : true, includes : true)
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;
// 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;
#define LIBDBUS_FUNCTIONS \
X(dbus_bus_get) \
X(dbus_connection_flush) \
X(dbus_connection_send) \
X(dbus_connection_set_exit_on_disconnect) \
X(dbus_connection_unref) \
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(
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.
* Try to set up D-Bus. Returns `false` if a function could not be resolved or
* if we could not connect to the D-Bus session.
*/
bool setup_libdbus() {
// If this function is called from two threads at the same time, then we can
@@ -68,7 +81,7 @@ bool setup_libdbus() {
return false;
}
#define LOAD_FUNCTION(name) \
#define X(name) \
do { \
lib##name = \
reinterpret_cast<decltype(lib##name)>(dlsym(handle, #name)); \
@@ -78,18 +91,13 @@ bool setup_libdbus() {
"', not sending desktop notifications"); \
return false; \
} \
} while (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);
LIBDBUS_FUNCTIONS
#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;
libdbus_error_init(&error);
@@ -97,7 +105,7 @@ bool setup_libdbus() {
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: " +
logger.log("Could not connect to D-Bus session bus: " +
std::string(error.message));
libdbus_error_free(&error);
@@ -119,7 +127,7 @@ bool setup_libdbus() {
bool send_notification(const std::string& title,
const std::string body,
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
// don't install that by default.
if (!libdbus_handle && !setup_libdbus()) {
@@ -151,20 +159,65 @@ bool send_notification(const std::string& title,
}
}
Process process("notify-send");
process.arg("--urgency=normal");
process.arg("--app-name=yabridge");
process.arg(title);
process.arg(formatted_body.str());
// Actually sending the notification is done directly using the D-Bus API.
std::unique_ptr<DBusMessage, void (*)(DBusMessage*)> message(
libdbus_message_new_method_call(
"org.freedesktop.Notifications", "/org/freedesktop/Notifications",
"org.freedesktop.Notifications", "Notify"),
libdbus_message_unref);
assert(message);
// We will have printed the message to the terminal anyways, so if the user
// doesn't have libnotify installed we'll just fail silently
const auto result = process.spawn_get_status();
return std::visit(
overload{
[](int status) -> bool { return status == EXIT_SUCCESS; },
[](const Process::CommandNotFound&) -> bool { return false; },
[](const std::error_code&) -> bool { return false; },
},
result);
// Can't use `dbus_message_append_args` because that doesn't support
// dictionaries
DBusMessageIter iter;
libdbus_message_iter_init_append(message.get(), &iter);
const char* app_name = "yabridge";
assert(
libdbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &app_name));
// 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 <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
* plugin fails to load since the user may not be checking the output in a
* terminal.
* Send a desktop notification using the D-Bus notifications protocol
* (<https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html>).
* 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 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.
*
* @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,
const std::string body,