diff --git a/meson.build b/meson.build
index e8a7132d..dd1d6904 100644
--- a/meson.build
+++ b/meson.build
@@ -314,16 +314,15 @@ if with_clap
dependencies : clap_plugin_deps,
cpp_args : compiler_options,
)
- # TODO: Chainloader
- # shared_library(
- # 'yabridge-chainloader-clap',
- # clap_chainloader_sources,
- # native : true,
- # dependencies : chainloader_deps,
- # cpp_args : compiler_options + chainloader_compiler_options,
- # # See above
- # override_options : ['b_lto=true'],
- # )
+ shared_library(
+ 'yabridge-chainloader-clap',
+ clap_chainloader_sources,
+ native : true,
+ dependencies : clap_chainloader_deps,
+ cpp_args : compiler_options + chainloader_compiler_options,
+ # See above
+ override_options : ['b_lto=true'],
+ )
endif
if with_vst3
diff --git a/src/chainloader/clap-chainloader.cpp b/src/chainloader/clap-chainloader.cpp
new file mode 100644
index 00000000..2a1396d0
--- /dev/null
+++ b/src/chainloader/clap-chainloader.cpp
@@ -0,0 +1,159 @@
+// yabridge: a Wine plugin bridge
+// Copyright (C) 2020-2022 Robbert van der Helm
+//
+// 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
+#include
+#include
+
+#include
+#include
+
+// Generated inside of the build directory
+#include
+
+#include "../common/linking.h"
+#include "../common/utils.h"
+#include "utils.h"
+
+// These chainloader libraries are tiny, mostly dependencyless libraries that
+// `dlopen()` the actual `libyabridge-{vst2,vst3}.so` files and forward the
+// entry point function calls from this library to those. Or technically, these
+// libraries use dedicated entry point functions because multiple chainloader
+// libraries may all dynamically link to the exact same plugin library, so we
+// can't store any bridge information in a global there. This approach avoids
+// wasting disk space on copies on file systems that don't support reflinking,
+// but more importantly it also avoids the need to rerun `yabridgectl sync`
+// whenever yabridge is updated. This is even more important when considering
+// distro packaging, because updates to Boost might require the package to be
+// rebuilt, which in turn would also require a resync.
+
+namespace fs = ghc::filesystem;
+
+// These functions are loaded from `libyabridge-clap.so` the first time
+// `clap_entry.init` gets called
+using ClapPluginBridge = void;
+
+ClapPluginBridge* (*yabridge_bridge_init)(const char* plugin_path) = nullptr;
+void (*yabridge_bridge_free)(ClapPluginBridge* instance) = nullptr;
+const void* (*yabridge_module_get_plugin_factory)(ClapPluginBridge* instance,
+ const char* factory_id) =
+ nullptr;
+
+/**
+ * The bridge instance for this chainloader. This is initialized when
+ * `clap_entry.init` first gets called.
+ */
+std::unique_ptr bridge(
+ nullptr,
+ nullptr);
+/**
+ * The number of active instances. Incremented when `clap_entry_init()` is
+ * called, decremented when `clap_entry_exit()` is called. We'll initialize the
+ * bridge when this is first incremented from 0, and we'll free the bridge again
+ * when a `clap_entry_exit()` call causes this to return back to 0.
+ */
+std::atomic_size_t active_instances = 0;
+
+/**
+ * The first time one of the exported functions from this library gets called,
+ * we'll need to load the corresponding `libyabridge-*.so` file and fetch the
+ * the entry point functions from that file.
+ */
+bool initialize_library() {
+ static void* library_handle = nullptr;
+ static std::mutex library_handle_mutex;
+
+ std::lock_guard lock(library_handle_mutex);
+
+ // There should be no situation where this library gets loaded and then two
+ // threads immediately start calling functions, but we'll handle that
+ // situation just in case it does happen
+ if (library_handle) {
+ return true;
+ }
+
+ library_handle = find_plugin_library(yabridge_clap_plugin_name);
+ if (!library_handle) {
+ return false;
+ }
+
+#define LOAD_FUNCTION(name) \
+ do { \
+ (name) = \
+ reinterpret_cast(dlsym(library_handle, #name)); \
+ if (!(name)) { \
+ log_failing_dlsym(yabridge_clap_plugin_name, #name); \
+ return false; \
+ } \
+ } while (false)
+
+ LOAD_FUNCTION(yabridge_bridge_init);
+ LOAD_FUNCTION(yabridge_bridge_free);
+ LOAD_FUNCTION(yabridge_module_get_plugin_factory);
+
+#undef LOAD_FUNCTION
+
+ return true;
+}
+
+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) {
+ if (!initialize_library()) {
+ return false;
+ }
+
+ // You can't change the deleter function with `.reset()` so we'll need
+ // this abomination instead
+ // 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 this_plugin_path = get_this_file_location();
+ bridge =
+ decltype(bridge)(yabridge_bridge_init(this_plugin_path.c_str()),
+ yabridge_bridge_free);
+ if (!bridge) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+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) {
+ bridge.reset();
+ }
+}
+
+const void* clap_entry_get_factory(const char* factory_id) {
+ // The host should have called `clap_entry.init` first
+ assert(bridge);
+ assert(factory_id);
+
+ return yabridge_module_get_plugin_factory(bridge.get(), factory_id);
+}
+
+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,
+};
diff --git a/src/chainloader/meson.build b/src/chainloader/meson.build
index d2b5387a..2b9bfff6 100644
--- a/src/chainloader/meson.build
+++ b/src/chainloader/meson.build
@@ -9,6 +9,10 @@ chainloader_deps = [
rt_dep,
]
+if with_clap
+ clap_chainloader_deps = chainloader_deps + [clap_dep]
+endif
+
vst2_chainloader_sources = files(
'../common/logging/common.cpp',
'../common/linking.cpp',
@@ -18,6 +22,17 @@ vst2_chainloader_sources = files(
'vst2-chainloader.cpp',
)
+if with_clap
+ clap_chainloader_sources = files(
+ '../common/logging/common.cpp',
+ '../common/linking.cpp',
+ '../common/notifications.cpp',
+ '../common/process.cpp',
+ 'utils.cpp',
+ 'clap-chainloader.cpp',
+ )
+endif
+
if with_vst3
vst3_chainloader_sources = files(
'../common/logging/common.cpp',
diff --git a/src/common/config/config.h.in b/src/common/config/config.h.in
index 779c87ce..712655a5 100644
--- a/src/common/config/config.h.in
+++ b/src/common/config/config.h.in
@@ -16,6 +16,11 @@
#pragma once
+/**
+ * The name of yabridge's CLAP library, e.g. `libyabridge-clap.so`.
+ */
+constexpr char yabridge_clap_plugin_name[] = "@clap_plugin_name@";
+
/**
* The name of yabridge's VST2 library, e.g. `libyabridge-vst2.so`.
*/