From ce375d8820e0a4992d20943b095bf69a6aa171b9 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Mon, 14 Sep 2026 14:13:41 -0400 Subject: [PATCH] Tell a wrapped plugin which format it is in, and which host A CLAP inside clap-wrapper had no way to learn that it was wrapped, or as what. host->name only half answered who the host was: it carries a " (CLAP-as-VST3)" style suffix, the Apple formats add a version, and an AUv3 gets the name of its own appex. clap-wrapper.host-information/0, in include/clapwrapper/wrapper_host.h, is a host extension every wrapper now provides. get_wrapper_flavor answers auv2, auv3, aax, vst3 or standalone. get_underlying_host_name answers the host's own name, "REAPER" rather than "REAPER (CLAP-as-VST3)", or null when it cannot be known. host->name stays as it was. REAPER as a native CLAP host also calls itself "REAPER", and a plugin special-casing that would start doing so inside the VST3. The AU formats take the name from the process they were loaded into, and give null when that process is an XPC service. AUHostingService, which runs an out of process AUv2, and the appex an AUv3 runs in are both XPC! bundles, and neither is the host. The standalone names itself per platform. The distortion example logs both. Assisted-by: Claude Opus 5 --- include/clapwrapper/wrapper_host.h | 54 ++++++++++++++++++++ src/clap_proxy.cpp | 5 ++ src/clap_proxy.h | 14 +++++ src/detail/aax/audioconfig.cpp | 4 ++ src/detail/aax/wrapper.cpp | 11 ++-- src/detail/aax/wrapper.h | 9 ++++ src/detail/auv2/auv2_base_classes.h | 10 ++++ src/detail/auv3/auv3_audiounit.mm | 11 ++++ src/detail/clap/fsutil.h | 1 + src/detail/os/macos.mm | 11 ++++ src/detail/os/osutil.h | 5 ++ src/detail/standalone/standalone_host.h | 14 +++++ src/wrapasauv2.cpp | 1 + src/wrapasvst3.cpp | 13 ++--- src/wrapasvst3.h | 9 ++++ tests/clap-first-example/distortion_clap.cpp | 27 ++++++++++ 16 files changed, 183 insertions(+), 16 deletions(-) create mode 100644 include/clapwrapper/wrapper_host.h diff --git a/include/clapwrapper/wrapper_host.h b/include/clapwrapper/wrapper_host.h new file mode 100644 index 00000000..903da62c --- /dev/null +++ b/include/clapwrapper/wrapper_host.h @@ -0,0 +1,54 @@ +#ifndef CLAPWRAPPER_WRAPPER_HOST_H +#define CLAPWRAPPER_WRAPPER_HOST_H + +#include "clap/private/macros.h" +#include "clap/host.h" + +// CLAP_ABI was introduced in CLAP 1.1.2, for older versions we make it transparent +#ifndef CLAP_ABI +#define CLAP_ABI +#endif + +/* + clap_wrapper_host_information + + a host extension every clap-wrapper host provides, so a plugin can tell it is wrapped + and in what. a null result from get_extension means not wrapped. + + auto *cwh = (const clap_wrapper_host_information_t *)host->get_extension( + host, CLAP_WRAPPER_HOST_INFORMATION); + if (cwh && strcmp(cwh->get_wrapper_flavor(host), CLAP_WRAPPER_HOST_FLAVOR_AUV2) == 0) + ... +*/ + +#ifdef __cplusplus +extern "C" +{ +#endif + + static const CLAP_CONSTEXPR char CLAP_WRAPPER_HOST_INFORMATION[] = "clap-wrapper.host-information/0"; + + // compare with strcmp; the pointers are not guaranteed to be these arrays + static const CLAP_CONSTEXPR char CLAP_WRAPPER_HOST_FLAVOR_AUV2[] = "auv2"; + static const CLAP_CONSTEXPR char CLAP_WRAPPER_HOST_FLAVOR_AUV3[] = "auv3"; + static const CLAP_CONSTEXPR char CLAP_WRAPPER_HOST_FLAVOR_AAX[] = "aax"; + static const CLAP_CONSTEXPR char CLAP_WRAPPER_HOST_FLAVOR_VST3[] = "vst3"; + static const CLAP_CONSTEXPR char CLAP_WRAPPER_HOST_FLAVOR_STANDALONE[] = "standalone"; + + typedef struct clap_wrapper_host_information + { + // one of CLAP_WRAPPER_HOST_FLAVOR_*, valid for the life of the host + // [thread-safe] + const char *(CLAP_ABI *get_wrapper_flavor)(const clap_host_t *host); + + // the real host's name without host->name's wrapper suffix, e.g. "REAPER" + // null when unknowable + // [thread-safe] + const char *(CLAP_ABI *get_underlying_host_name)(const clap_host_t *host); + } clap_wrapper_host_information_t; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/clap_proxy.cpp b/src/clap_proxy.cpp index b836e522..1d4e84b5 100644 --- a/src/clap_proxy.cpp +++ b/src/clap_proxy.cpp @@ -145,6 +145,10 @@ static bool track_info_get(const clap_host_t *host, clap_track_info_t *info) const clap_host_track_info trackinfo = {track_info_get}; +const clap_wrapper_host_information_t wrapper_host_information = { + [](const clap_host_t *host) -> const char * { return self(host)->wrapper_flavor(); }, + [](const clap_host_t *host) -> const char * { return self(host)->underlying_host_name(); }}; + const clap_host_preset_load_t preset_load = { /* on_error */ [](const clap_host_t *host, uint32_t location_kind, const char *location, const char *load_key, @@ -589,6 +593,7 @@ const void *Plugin::clapExtension(const clap_host * /*host*/, const char *extens if (!strcmp(extension, CLAP_EXT_CONTEXT_MENU)) return &HostExt::context_menu; if (!strcmp(extension, CLAP_EXT_PRESET_LOAD) || !strcmp(extension, CLAP_EXT_PRESET_LOAD_COMPAT)) return &HostExt::preset_load; + if (!strcmp(extension, CLAP_WRAPPER_HOST_INFORMATION)) return &HostExt::wrapper_host_information; #if LIN if (!strcmp(extension, CLAP_EXT_POSIX_FD_SUPPORT)) return &HostExt::hostposixfd; diff --git a/src/clap_proxy.h b/src/clap_proxy.h index de63839c..88f58a3a 100644 --- a/src/clap_proxy.h +++ b/src/clap_proxy.h @@ -74,6 +74,11 @@ class IHost virtual bool track_info_get(clap_track_info_t *info) = 0; virtual const char *host_get_name() = 0; + // one of CLAP_WRAPPER_HOST_FLAVOR_*, reported through CLAP_WRAPPER_HOST_INFORMATION + virtual const char *wrapper_flavor() const = 0; + // set before the plugin is created and not changed after; nullptr when unknowable + virtual const char *underlying_host_name() const = 0; + // clap.preset-load, host side. Defaulted rather than pure: only the formats // that can show a preset list (VST3 program lists, AU factory presets) have // anything to do here, and making these pure would force every other @@ -283,6 +288,15 @@ class Plugin return _parentHost->track_info_get(info); } + const char *wrapper_flavor() const + { + return _parentHost->wrapper_flavor(); + } + const char *underlying_host_name() const + { + return _parentHost->underlying_host_name(); + } + // clap_timer support bool register_timer(uint32_t period_ms, clap_id *timer_id); bool unregister_timer(clap_id timer_id); diff --git a/src/detail/aax/audioconfig.cpp b/src/detail/aax/audioconfig.cpp index 8f21905c..fc2b4606 100644 --- a/src/detail/aax/audioconfig.cpp +++ b/src/detail/aax/audioconfig.cpp @@ -196,6 +196,9 @@ plugin_bus_info_t getAvailableBusConfigs(Clap::Library *factory, uint32_t index) static const clap_host_audio_ports_t micro_audio_ports = { [](const clap_host_t *host, uint32_t flag) -> bool { return false; }, [](const clap_host_t *host, uint32_t flags) -> void {}}; + static const clap_wrapper_host_information_t micro_wrapper_info = { + [](const clap_host_t *host) -> const char * { return CLAP_WRAPPER_HOST_FLAVOR_AAX; }, + [](const clap_host_t *host) -> const char * { return nullptr; }}; clap_host_t microhost = { CLAP_VERSION, nullptr, @@ -209,6 +212,7 @@ plugin_bus_info_t getAvailableBusConfigs(Clap::Library *factory, uint32_t index) LOGDETAIL("plugin requests microhost extension {}", extension_id); if (!strcmp(CLAP_EXT_PARAMS, extension_id)) return µ_params; if (!strcmp(CLAP_EXT_AUDIO_PORTS, extension_id)) return µ_audio_ports; + if (!strcmp(CLAP_WRAPPER_HOST_INFORMATION, extension_id)) return µ_wrapper_info; return nullptr; }, [](const struct clap_host *host) -> void {}, // request_restart diff --git a/src/detail/aax/wrapper.cpp b/src/detail/aax/wrapper.cpp index caf7e05d..d22fefa7 100644 --- a/src/detail/aax/wrapper.cpp +++ b/src/detail/aax/wrapper.cpp @@ -550,6 +550,9 @@ AAX_Result ClapAsAAX::EffectInit() LOGINFO(fmt::format("AAX Effect Init for '{}'", m.StdString().c_str())); + AAX_CString hostname; + if (AAX_SUCCESS == _aax_ctrl->GetHostName(&hostname)) _underlying_hostname = hostname.StdString(); + _library = CLAPAAX::guarantee_clap(); _plugin = Clap::Plugin::createInstance(_library->_pluginFactory, m.StdString(), this); @@ -1163,13 +1166,7 @@ bool ClapAsAAX::track_info_get(clap_track_info_t *info) const char *ClapAsAAX::host_get_name() { - AAX_IController *ctrl = Controller(); - AAX_CString hostname; - if (AAX_SUCCESS == ctrl->GetHostName(&hostname)) - { - _wrapper_hostname = hostname.StdString(); - _wrapper_hostname.append(" (CLAP-as-AAX)"); - } + if (!_underlying_hostname.empty()) _wrapper_hostname = _underlying_hostname + " (CLAP-as-AAX)"; return _wrapper_hostname.c_str(); } diff --git a/src/detail/aax/wrapper.h b/src/detail/aax/wrapper.h index 51eb7f49..29994b74 100644 --- a/src/detail/aax/wrapper.h +++ b/src/detail/aax/wrapper.h @@ -246,6 +246,14 @@ class ClapAsAAX : public AAX_CEffectParameters, bool track_info_get(clap_track_info_t *info) override; const char *host_get_name() override; + const char *wrapper_flavor() const override + { + return CLAP_WRAPPER_HOST_FLAVOR_AAX; + } + const char *underlying_host_name() const override + { + return _underlying_hostname.empty() ? nullptr : _underlying_hostname.c_str(); + } bool supportsContextMenu() const override; // context_menu @@ -273,6 +281,7 @@ class ClapAsAAX : public AAX_CEffectParameters, os::State _os_attached; std::string _wrapper_hostname = "CLAP-As-AAX-Wrapper"; + std::string _underlying_hostname; // _parameterMap maps the AAX CParamID to the wrapped parameter std::map> _parameterMap; diff --git a/src/detail/auv2/auv2_base_classes.h b/src/detail/auv2/auv2_base_classes.h index 34aafe44..e1ff5c29 100644 --- a/src/detail/auv2/auv2_base_classes.h +++ b/src/detail/auv2/auv2_base_classes.h @@ -698,6 +698,15 @@ class WrapAsAUV2 : public ausdk::AUBase, return false; } + const char *wrapper_flavor() const override + { + return CLAP_WRAPPER_HOST_FLAVOR_AUV2; + } + const char *underlying_host_name() const override + { + return _underlying_hostname.empty() ? nullptr : _underlying_hostname.c_str(); + } + const char *host_get_name() override { char text[65]; @@ -920,6 +929,7 @@ class WrapAsAUV2 : public ausdk::AUBase, // std::vector _midi_outports_info; std::string _hostname = "CLAP-as-AUv2"; + std::string _underlying_hostname; #ifdef DUAL_SCHEDULING_ENABLED bool _midi_dualscheduling_mode = false; diff --git a/src/detail/auv3/auv3_audiounit.mm b/src/detail/auv3/auv3_audiounit.mm index 6f1dc567..d9bb62e5 100644 --- a/src/detail/auv3/auv3_audiounit.mm +++ b/src/detail/auv3/auv3_audiounit.mm @@ -136,6 +136,7 @@ - (void)_applyGUISizeWidth:(uint32_t)width height:(uint32_t)height; int _idx = 0; os::State _os_attached; std::string _hostname = "CLAP-as-AUv3"; + std::string _underlying_hostname; std::atomic _initialized{false}; std::atomic_bool _requestUICallback{false}; // set by mark_dirty(), serviced by the idle timer @@ -600,6 +601,15 @@ void fireTimers() } } + const char *wrapper_flavor() const override + { + return CLAP_WRAPPER_HOST_FLAVOR_AUV3; + } + const char *underlying_host_name() const override + { + return _underlying_hostname.empty() ? nullptr : _underlying_hostname.c_str(); + } + const char *host_get_name() override { NSBundle *mainBundle = [NSBundle mainBundle]; @@ -981,6 +991,7 @@ - (instancetype)initWithComponentDescription:(AudioComponentDescription)componen // Create the plugin instance AUV3LOG("init: creating plugin instance via factory"); + _impl->_underlying_hostname = os::getHostAppName(); _impl->_plugin = Clap::Plugin::createInstance(_library._pluginFactory, _impl->_desc->id, _impl.get()); if (!_impl->_plugin) diff --git a/src/detail/clap/fsutil.h b/src/detail/clap/fsutil.h index aaeb78b7..61210460 100644 --- a/src/detail/clap/fsutil.h +++ b/src/detail/clap/fsutil.h @@ -20,6 +20,7 @@ #include "clapwrapper/vst3.h" #include "clapwrapper/auv2.h" #include "clapwrapper/aax.h" +#include "clapwrapper/wrapper_host.h" #include "../ara/ara.h" #include "detail/os/fs.h" diff --git a/src/detail/os/macos.mm b/src/detail/os/macos.mm index 05de6147..dc84e209 100644 --- a/src/detail/os/macos.mm +++ b/src/detail/os/macos.mm @@ -149,4 +149,15 @@ uint64_t getTickInMS() return getPluginPath().stem(); } +std::string getHostAppName() +{ + NSBundle *mainBundle = [NSBundle mainBundle]; + // AUHostingService and our own appex are both XPC! bundles + if ([[mainBundle objectForInfoDictionaryKey:@"CFBundlePackageType"] isEqualToString:@"XPC!"]) + return {}; + NSString *name = [mainBundle objectForInfoDictionaryKey:@"CFBundleName"]; + if (!name) name = [[NSProcessInfo processInfo] processName]; + return name ? std::string([name UTF8String]) : std::string(); +} + } // namespace os diff --git a/src/detail/os/osutil.h b/src/detail/os/osutil.h index 49b4670d..f0d8e705 100644 --- a/src/detail/os/osutil.h +++ b/src/detail/os/osutil.h @@ -85,6 +85,11 @@ fs::path getPluginPath(); std::string getParentFolderName(); std::string getBinaryName(); +#if MAC +// empty when loaded into an xpc service or appex, where the real host is unknowable +std::string getHostAppName(); +#endif + #ifdef WIN32 void init(); void terminate(); diff --git a/src/detail/standalone/standalone_host.h b/src/detail/standalone/standalone_host.h index da2fef4e..61f14e95 100644 --- a/src/detail/standalone/standalone_host.h +++ b/src/detail/standalone/standalone_host.h @@ -230,6 +230,20 @@ struct StandaloneHost : Clap::IHost bool unregister_timer(clap_id timer_id) override; const char *host_get_name() override; + const char *wrapper_flavor() const override + { + return CLAP_WRAPPER_HOST_FLAVOR_STANDALONE; + } + const char *underlying_host_name() const override + { +#if MAC + return "Wrapper Mac Standalone"; +#elif WIN + return "Wrapper Windows Standalone"; +#else + return "Wrapper Linux Standalone"; +#endif + } bool track_info_get(clap_track_info_t *info) override { diff --git a/src/wrapasauv2.cpp b/src/wrapasauv2.cpp index 63f3b8a2..ab4f3c6f 100644 --- a/src/wrapasauv2.cpp +++ b/src/wrapasauv2.cpp @@ -169,6 +169,7 @@ WrapAsAUV2::WrapAsAUV2(const std::string &clapname, const std::string &clapid, i */ // pffffrzz(); // <- enable this to have a hook to attach a debugger + _underlying_hostname = os::getHostAppName(); _plugin = Clap::Plugin::createInstance(_library._pluginFactory, _desc->id, this); if (_plugin) { diff --git a/src/wrapasvst3.cpp b/src/wrapasvst3.cpp index 0baf2881..d9b2ef01 100644 --- a/src/wrapasvst3.cpp +++ b/src/wrapasvst3.cpp @@ -152,6 +152,9 @@ tresult PLUGIN_API ClapAsVst3::initialize(FUnknown *context) { if (!_plugin) { + Steinberg::Vst::String128 res; + if (vst3HostApplication && kResultOk == vst3HostApplication->getName(res)) + underlying_hostname = stringconv::convert(res); _plugin = Clap::Plugin::createInstance(*_library, _libraryIndex, this); } result = (_plugin && _plugin->initialize()) ? kResultOk : kResultFalse; @@ -1759,15 +1762,7 @@ bool ClapAsVst3::unregister_timer(clap_id timer_id) const char *ClapAsVst3::host_get_name() { - if (vst3HostApplication) - { - Steinberg::Vst::String128 res; - if (kResultOk == vst3HostApplication->getName(res)) - { - wrapper_hostname = stringconv::convert(res); - wrapper_hostname.append(" (CLAP-as-VST3)"); - } - } + if (!underlying_hostname.empty()) wrapper_hostname = underlying_hostname + " (CLAP-as-VST3)"; return wrapper_hostname.c_str(); } diff --git a/src/wrapasvst3.h b/src/wrapasvst3.h index 5d809c73..cf59fc48 100644 --- a/src/wrapasvst3.h +++ b/src/wrapasvst3.h @@ -306,6 +306,7 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, IPtr vst3ContextMenu = nullptr; IPtr vst3HostApplication = nullptr; std::string wrapper_hostname = "CLAP-As-VST3-Wrapper"; + std::string underlying_hostname; std::vector contextmenuitems; uint32_t vst3ContextMenuParamID = 0; @@ -348,6 +349,14 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, bool unregister_timer(clap_id timer_id) override; const char *host_get_name() override; + const char *wrapper_flavor() const override + { + return CLAP_WRAPPER_HOST_FLAVOR_VST3; + } + const char *underlying_host_name() const override + { + return underlying_hostname.empty() ? nullptr : underlying_hostname.c_str(); + } bool supportsContextMenu() const override; // context_menu diff --git a/tests/clap-first-example/distortion_clap.cpp b/tests/clap-first-example/distortion_clap.cpp index 1b765922..ee1c96ca 100644 --- a/tests/clap-first-example/distortion_clap.cpp +++ b/tests/clap-first-example/distortion_clap.cpp @@ -14,6 +14,7 @@ #include #include #include "clapwrapper/vst3.h" +#include "clapwrapper/wrapper_host.h" #include "distortion_clap_entry.h" static const char *features[] = {CLAP_PLUGIN_FEATURE_AUDIO_EFFECT, CLAP_PLUGIN_FEATURE_STEREO, @@ -371,6 +372,32 @@ static bool clap1stDist_init(const struct clap_plugin *plugin) if (plug->hostLog && plug->host) { plug->hostLog->log(plug->host, CLAP_LOG_INFO, "Created clap1st Distortion compiled with C++"); + + // null means a real clap host rather than clap-wrapper + auto *cwh = (const clap_wrapper_host_information_t *)plug->host->get_extension( + plug->host, CLAP_WRAPPER_HOST_INFORMATION); + const char *how = "Running as a CLAP"; + if (cwh) + { + const char *flavor = cwh->get_wrapper_flavor(plug->host); + if (strcmp(flavor, CLAP_WRAPPER_HOST_FLAVOR_VST3) == 0) + how = "Wrapped as a VST3"; + else if (strcmp(flavor, CLAP_WRAPPER_HOST_FLAVOR_AUV2) == 0) + how = "Wrapped as an AUv2"; + else if (strcmp(flavor, CLAP_WRAPPER_HOST_FLAVOR_STANDALONE) == 0) + how = "Wrapped as a standalone"; + else + how = "Wrapped in some other format"; + } + plug->hostLog->log(plug->host, CLAP_LOG_INFO, how); + + if (cwh) + { + const char *underlying = cwh->get_underlying_host_name(plug->host); + char msg[256]; + snprintf(msg, sizeof(msg), "Underlying host is '%s'", underlying ? underlying : "unknown"); + plug->hostLog->log(plug->host, CLAP_LOG_INFO, msg); + } } return true;