From 707092086e880e77ad4207802de86a9086f1c1b0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 24 Aug 2022 15:05:22 +0200 Subject: [PATCH] Interact with CLAP bridge in libyabridge-clap.so --- src/plugin/clap-plugin.cpp | 122 +++++++++++++++++++++++++++++++++++-- src/plugin/meson.build | 14 +++++ 2 files changed, 131 insertions(+), 5 deletions(-) diff --git a/src/plugin/clap-plugin.cpp b/src/plugin/clap-plugin.cpp index 77ba86cf..3f777e28 100644 --- a/src/plugin/clap-plugin.cpp +++ b/src/plugin/clap-plugin.cpp @@ -18,6 +18,28 @@ #include +#include "bridges/clap.h" + +using namespace std::literals::string_literals; + +namespace fs = ghc::filesystem; + +// These plugin libraries can be used in one of two ways: they can either be +// loaded directly (the yabridge <4.0 way), or they can be loaded indirectly +// from `yabridge-chainloader-*.so` (the yabridge >=4.0 way). The advantage of +// chainloading this library from a tiny stub library is that yabridge can be +// updated without having to also replace all of the library copies and that it +// takes up less space on filesystems that don't support reflinking, but the +// catch is that we no longer have one unique plugin bridge library per plugin. +// This means that we cannot store the current bridge instance as a global in +// this library (because it would then be shared by multiple chainloaders), and +// that we cannot use `dladdr()` within this library to get the path to the +// current plugin, because thatq would return the path to this shared plugin +// library instead. To accommodate for this, we'll provide the usual plugin +// entry points, and we'll also provide simple methods for initializing the +// bridge so that the chainloading library can hold on to the bridge instance +// instead of this library. + /** * The number of active instances. Incremented when `clap_entry_init()` is * called, decremented when `clap_entry_exit()` is called. We'll initialize the @@ -25,12 +47,49 @@ * when a `clap_entry_exit()` call causes this to return back to 0. */ std::atomic_size_t active_instances = 0; +/** + * The global plugin bridge instance. Only used if this plugin library is used + * directly. When the library is chainloaded, this will remain a null pointer. + */ +std::unique_ptr bridge; -bool clap_entry_init(const char* plugin_path) { +void log_init_error(const std::exception& error, const fs::path& plugin_path) { + Logger logger = Logger::create_exception_logger(); + + logger.log(""); + logger.log("Error during initialization:"); + logger.log(error.what()); + logger.log(""); + + // Also show a desktop notification since most people likely won't see the + // above message + send_notification( + "Failed to initialize CLAP plugin", + error.what() + + "\nCheck the plugin's output in a terminal for more information"s, + plugin_path); +} + +bool clap_entry_init(const char* /*plugin_path*/) { // This function can be called multiple times, so we should make sure to // only initialize the bridge on the first call if (active_instances.fetch_add(1, std::memory_order_seq_cst) == 0) { - // TODO: Increase reference count, init factory if it was zero + assert(!bridge); + + // XXX: The host also provides us with the plugin path which we could + // just use instead. Should we? The advantage of doing it this way + // instead is that we'll have consistent behavior between all + // plugin formats. + const fs::path plugin_path = get_this_file_location(); + try { + bridge = std::make_unique(plugin_path); + + return true; + } catch (const std::exception& error) { + log_init_error(error, plugin_path); + + return false; + } } return true; @@ -40,18 +99,71 @@ void clap_entry_deinit() { // We'll free the bridge when this exits brings the reference count back to // zero if (active_instances.fetch_sub(1, std::memory_order_seq_cst) == 1) { - // TODO: Destroy the bridge instance + assert(bridge); + + bridge.reset(); } } const void* clap_entry_get_factory(const char* factory_id) { - // TODO: Do the thing - // TODO: Assertion + assert(bridge); + assert(factory_id); + + return bridge->get_factory(factory_id); } +// This visibility attribute doesn't do anything on data with external linkage, +// but we'll include it here just because it's in the CLAP template +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wattributes" + CLAP_EXPORT const clap_plugin_entry_t clap_entry = { .clap_version = CLAP_VERSION_INIT, .init = clap_entry_init, .deinit = clap_entry_deinit, .get_factory = clap_entry_get_factory, }; + +#pragma GCC diagnostic pop + +/** + * This function can be called from the chainloader to initialize a new plugin + * bridge instance. The caller should store the pointer and later free it again + * using the `yabridge_module_free()` function. If the bridge could not + * initialize due to an error, then the error will be logged and a null pointer + * will be returned. + */ +extern "C" YABRIDGE_EXPORT ClapPluginBridge* yabridge_module_init( + const char* plugin_path) { + assert(plugin_path); + + try { + return new ClapPluginBridge(plugin_path); + } catch (const std::exception& error) { + log_init_error(error, plugin_path); + + return nullptr; + } +} + +/** + * Free a bridge instance returned by `yabridge_module_init`. + */ +extern "C" YABRIDGE_EXPORT void yabridge_module_free( + ClapPluginBridge* instance) { + if (instance) { + delete instance; + } +} + +/** + * Create and return a factory from a bridge instance. Used by the chainloaders. + */ +extern "C" YABRIDGE_EXPORT const void* yabridge_module_get_factory( + ClapPluginBridge* instance, + const char* factory_id) { + assert(instance); + assert(factory_id); + + return instance->get_factory(factory_id); +} diff --git a/src/plugin/meson.build b/src/plugin/meson.build index 100d6553..eaf5b73d 100644 --- a/src/plugin/meson.build +++ b/src/plugin/meson.build @@ -68,6 +68,20 @@ vst2_plugin_sources = files( if with_clap clap_plugin_sources = files( + '../common/communication/common.cpp', + '../common/configuration.cpp', + '../common/logging/clap.cpp', + '../common/logging/common.cpp', + '../common/audio-shm.cpp', + '../common/linking.cpp', + '../common/notifications.cpp', + '../common/plugins.cpp', + '../common/process.cpp', + '../common/utils.cpp', + '../include/llvm/small-vector.cpp', + 'bridges/clap.cpp', + 'host-process.cpp', + 'utils.cpp', 'clap-plugin.cpp', ) endif