From c625deadef7d21f259a104adc9b2088d7afa483c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 3 Jan 2022 17:04:00 +0100 Subject: [PATCH] Proxy host context menu items for VST3 plugins This wasn't implemented yet because no plugin tried using the interface in this way before this, but Surge XT incorporates the host's context menu items into their own (much more elaborate) context menu. To accommodate this, we now copy over all of the host's prepopulated context menu items to the Wine plugin host, and calling the targets associated with any of those items will cause the target on the associated context menu item on the host to be called. This is slightly more complicated than what would otherwise be necessary because Bitwig does not assign tags to their context menu items and instead always uses 0. --- CHANGELOG.md | 11 +++ src/common/logging/vst3.cpp | 11 +-- src/common/logging/vst3.h | 1 - .../vst3-impls/context-menu-target.h | 18 ++-- src/common/serialization/vst3.h | 2 +- .../serialization/vst3/context-menu-proxy.cpp | 2 +- .../serialization/vst3/context-menu-proxy.h | 2 +- .../vst3/context-menu-target.cpp | 10 --- .../serialization/vst3/context-menu-target.h | 53 ++++++++---- .../vst3/context-menu/context-menu.cpp | 19 ++++- .../vst3/context-menu/context-menu.h | 36 ++++---- src/plugin/bridges/vst3-impls/plugin-proxy.h | 5 +- src/plugin/bridges/vst3.cpp | 41 ++++++--- .../bridges/vst3-impls/context-menu-proxy.cpp | 85 +++++++++++++++---- .../bridges/vst3-impls/context-menu-proxy.h | 25 ++++-- src/wine-host/bridges/vst3.cpp | 5 +- 16 files changed, 219 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a287f82..6b0bf5d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + +- Added support for VST3 plugins interacting directly with the host's context + menu items. Most plugins that use VST3's context menu support let the host + handle drawing the actual menu, but it's also possible for plugins to + incorporate the host's menu items into their own custom context menu. So far + this feature has only been tested with [Surge + XT](https://github.com/surge-synthesizer/surge)'s Windows VST3 version since + very few if any other plugins do this right now, but other plugins may start + doing this too in the future. + ### Changed - Added support for Wine 6.23's new fixed winedbg command line argument diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index b098ff43..f0ddfe2f 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -216,7 +216,8 @@ bool Vst3Logger::log_request( const YaContextMenuTarget::ExecuteMenuItem& request) { return log_request_base(is_host_vst, [&](auto& message) { message << request.owner_instance_id << ": ::executeMenuItem(tag = " << request.tag << ")"; }); } @@ -1272,14 +1273,6 @@ bool Vst3Logger::log_request( }); } -bool Vst3Logger::log_request(bool is_host_vst, - const YaContextMenu::GetItemCount& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message << request.owner_instance_id << ": ::getItemCount()"; - }); -} - bool Vst3Logger::log_request(bool is_host_vst, const YaContextMenu::AddItem& request) { return log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 2df3095e..b4af31b6 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -225,7 +225,6 @@ class Vst3Logger { bool log_request( bool is_host_vst, const YaComponentHandlerBusActivation::RequestBusActivation&); - bool log_request(bool is_host_vst, const YaContextMenu::GetItemCount&); bool log_request(bool is_host_vst, const YaContextMenu::AddItem&); bool log_request(bool is_host_vst, const YaContextMenu::RemoveItem&); bool log_request(bool is_host_vst, const YaContextMenu::Popup&); diff --git a/src/common/serialization/vst3-impls/context-menu-target.h b/src/common/serialization/vst3-impls/context-menu-target.h index 32728c11..9434b66d 100644 --- a/src/common/serialization/vst3-impls/context-menu-target.h +++ b/src/common/serialization/vst3-impls/context-menu-target.h @@ -22,6 +22,9 @@ * This implementation used to live in `src/plugin/bridges/vst3-impls`, but * since plugins can also call context menu items added by the host this is * needed on both sides. + * + * NOTE: Bitwig does not actually set the tags here, so host menu items need to + * be identified through their item ID, not through the tag. */ template class YaContextMenuTargetImpl : public YaContextMenuTarget { @@ -45,11 +48,16 @@ class YaContextMenuTargetImpl : public YaContextMenuTarget { // From `IContextMenuTarget` tresult PLUGIN_API executeMenuItem(int32 tag) override { - return bridge_.send_message(YaContextMenuTarget::ExecuteMenuItem{ - .owner_instance_id = owner_instance_id(), - .context_menu_id = context_menu_id(), - .target_tag = target_tag(), - .tag = tag}); + // NOTE: This requires mutual recursion, because REAPER will call + // `getState()` whle the context menu is open, and `getState()` + // also has to be handled from the GUI thread + return bridge_.send_mutually_recursive_message( + YaContextMenuTarget::ExecuteMenuItem{ + .owner_instance_id = owner_instance_id(), + .context_menu_id = context_menu_id(), + .item_id = item_id(), + .target_tag = target_tag(), + .tag = tag}); } private: diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index f7d45c95..d4901000 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -260,10 +260,10 @@ using CallbackRequest = // `IConnectionPoint::notify` calls through // there YaConnectionPoint::Notify, - YaContextMenu::GetItemCount, YaContextMenu::AddItem, YaContextMenu::RemoveItem, YaContextMenu::Popup, + YaContextMenuTarget::ExecuteMenuItem, YaHostApplication::GetName, YaPlugFrame::ResizeView, YaPlugInterfaceSupport::IsPlugInterfaceSupported, diff --git a/src/common/serialization/vst3/context-menu-proxy.cpp b/src/common/serialization/vst3/context-menu-proxy.cpp index 74cd5b48..59ec7069 100644 --- a/src/common/serialization/vst3/context-menu-proxy.cpp +++ b/src/common/serialization/vst3/context-menu-proxy.cpp @@ -21,7 +21,7 @@ Vst3ContextMenuProxy::ConstructArgs::ConstructArgs() noexcept {} Vst3ContextMenuProxy::ConstructArgs::ConstructArgs( Steinberg::IPtr object, size_t owner_instance_id, - size_t context_menu_id) noexcept + size_t context_menu_id) : owner_instance_id(owner_instance_id), context_menu_id(context_menu_id), context_menu_args(object) {} diff --git a/src/common/serialization/vst3/context-menu-proxy.h b/src/common/serialization/vst3/context-menu-proxy.h index 4b35435b..65fc3dde 100644 --- a/src/common/serialization/vst3/context-menu-proxy.h +++ b/src/common/serialization/vst3/context-menu-proxy.h @@ -49,7 +49,7 @@ class Vst3ContextMenuProxy : public YaContextMenu { */ ConstructArgs(Steinberg::IPtr object, size_t owner_instance_id, - size_t context_menu_id) noexcept; + size_t context_menu_id); /** * The unique instance identifier of the proxy object instance this diff --git a/src/common/serialization/vst3/context-menu-target.cpp b/src/common/serialization/vst3/context-menu-target.cpp index 527e7e92..56b7ba6a 100644 --- a/src/common/serialization/vst3/context-menu-target.cpp +++ b/src/common/serialization/vst3/context-menu-target.cpp @@ -16,16 +16,6 @@ #include "context-menu-target.h" -YaContextMenuTarget::ConstructArgs::ConstructArgs() noexcept {} - -YaContextMenuTarget::ConstructArgs::ConstructArgs( - native_size_t owner_instance_id, - native_size_t context_menu_id, - int32 tag) noexcept - : owner_instance_id(owner_instance_id), - context_menu_id(context_menu_id), - tag(tag) {} - YaContextMenuTarget::YaContextMenuTarget(ConstructArgs&& args) noexcept : arguments_(std::move(args)){FUNKNOWN_CTOR} diff --git a/src/common/serialization/vst3/context-menu-target.h b/src/common/serialization/vst3/context-menu-target.h index 057d699a..38c5eceb 100644 --- a/src/common/serialization/vst3/context-menu-target.h +++ b/src/common/serialization/vst3/context-menu-target.h @@ -37,30 +37,26 @@ class YaContextMenuTarget : public Steinberg::Vst::IContextMenuTarget { * `YaContextMenuTargetImpl`. */ struct ConstructArgs { - ConstructArgs() noexcept; - - /** - * Read from an existing object. We will try to mimic this object, so - * we'll support any interfaces this object also supports. - * - * @param owner_instance_id The object instance that this target's - * context menu belongs to. - * @param context_menu_id The unique ID of the context menu requested by - * `owwner_instance_id`. - * @param tag The tag of the menu item this target belongs to. - */ - ConstructArgs(native_size_t owner_instance_id, - native_size_t context_menu_id, - int32 tag) noexcept; - native_size_t owner_instance_id; native_size_t context_menu_id; + /** + * The ID of the menu item this target belongs to, only used when + * calling host targets from the plugin. + * + * NOTE: Needed to work around a Bitwig bug, see the comment in + * `ExecuteMenuItem` + */ + int32 item_id; + /** + * The tag of the menu item this target belongs to. + */ int32 tag; template void serialize(S& s) { s.value8b(owner_instance_id); s.value8b(context_menu_id); + s.value4b(item_id); s.value4b(tag); } }; @@ -87,6 +83,12 @@ class YaContextMenuTarget : public Steinberg::Vst::IContextMenuTarget { */ inline size_t context_menu_id() const { return arguments_.context_menu_id; } + /** + * Get the ID of the menu item this target was obtained from. This value is + * only actually used when calling host context menu items from a plugin. + */ + inline int32 item_id() const { return arguments_.item_id; } + /** * Get the tag of the menu item this target was passed to. */ @@ -103,8 +105,22 @@ class YaContextMenuTarget : public Steinberg::Vst::IContextMenuTarget { native_size_t owner_instance_id; native_size_t context_menu_id; /** - * The tag this target was passed for. This should be the same as `tag`, - * but it doesn't have to be. + * The menu item ID this target belongs to. + * + * This is used when calling host context menu items from the plugin's + * side. + * + * NOTE: This is needed because Bitwig identifies its own menu items by + * opaque ID, and not through the tag. They use 0 for all tags. + */ + int32 item_id; + /** + * The tag of the target this method was called on. Presumably this + * would always be the same as the `tag` argument passed to this + * function, but it doesn't have to be. + * + * This is used when calling plugin context menu items from the host's + * side. */ int32 target_tag; @@ -114,6 +130,7 @@ class YaContextMenuTarget : public Steinberg::Vst::IContextMenuTarget { void serialize(S& s) { s.value8b(owner_instance_id); s.value8b(context_menu_id); + s.value4b(item_id); s.value4b(target_tag); s.value4b(tag); } diff --git a/src/common/serialization/vst3/context-menu/context-menu.cpp b/src/common/serialization/vst3/context-menu/context-menu.cpp index f016172e..67def627 100644 --- a/src/common/serialization/vst3/context-menu/context-menu.cpp +++ b/src/common/serialization/vst3/context-menu/context-menu.cpp @@ -20,7 +20,24 @@ YaContextMenu::ConstructArgs::ConstructArgs() noexcept {} YaContextMenu::ConstructArgs::ConstructArgs( Steinberg::IPtr object) noexcept - : supported(Steinberg::FUnknownPtr(object)) {} + : supported(Steinberg::FUnknownPtr(object)) { + Steinberg::FUnknownPtr context_menu(object); + if (context_menu) { + // Can't trust plugins to check for null pointers, so we'll just always + // pass something + Steinberg::Vst::IContextMenuTarget* dummyTarget = nullptr; + + // Prepopulate the context menu with these targets + // NOTE: Bitwig does not actually set the tags here, so host menu items + // need to be identified through their item ID, not through the + // tag + items.resize(context_menu->getItemCount()); + for (size_t i = 0; i < items.size(); i++) { + context_menu->getItem(static_cast(i), items[i], + &dummyTarget); + } + } +} YaContextMenu::YaContextMenu(ConstructArgs&& args) noexcept : arguments_(std::move(args)) {} diff --git a/src/common/serialization/vst3/context-menu/context-menu.h b/src/common/serialization/vst3/context-menu/context-menu.h index b40e37d0..6e98f930 100644 --- a/src/common/serialization/vst3/context-menu/context-menu.h +++ b/src/common/serialization/vst3/context-menu/context-menu.h @@ -29,6 +29,9 @@ /** * Wraps around `IContextMenu` for serialization purposes. This is instantiated * as part of `Vst3ContextMenuProxy`. + * + * Plugins can also call context menu items created by the host, in which case + * we'll proxy that call through to the host. */ class YaContextMenu : public Steinberg::Vst::IContextMenu { public: @@ -49,9 +52,18 @@ class YaContextMenu : public Steinberg::Vst::IContextMenu { */ bool supported; + /** + * The context menu items prepopulated by the host so the plugin can + * call them. These items will receive `YaContextMenuTarget` proxy + * targets in `Vst3ContextMenuProxyImpl`, so when the plugin calls them + * it will dispatch a call to the host instead. + */ + std::vector items; + template void serialize(S& s) { s.value1b(supported); + s.container(items, 1 << 16); } }; @@ -63,28 +75,10 @@ class YaContextMenu : public Steinberg::Vst::IContextMenu { inline bool supported() const noexcept { return arguments_.supported; } - /** - * Message to pass through a call to `IContextMenu::getItemCount()` to the - * corresponding context menu instance returned by the host. - */ - struct GetItemCount { - using Response = PrimitiveWrapper; - - native_size_t owner_instance_id; - native_size_t context_menu_id; - - template - void serialize(S& s) { - s.value8b(owner_instance_id); - s.value8b(context_menu_id); - } - }; - + // Since we pass along a list of initial items, we don't need to proxy this + // unless the host somehow adds more items after the plugin adds an item virtual int32 PLUGIN_API getItemCount() override = 0; - - // XXX: Can a plugin call this to get items created by the host? Why would - // they do that? We should find a host/plugin combination that supports - // `IComponentHandler3` first. + // Plugins can also call context menu items created by the host virtual tresult PLUGIN_API getItem(int32 index, Steinberg::Vst::IContextMenuItem& item /*out*/, diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index 268cd790..cf07d5e8 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -353,13 +353,14 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { Steinberg::IPtr menu; /** - * All targets we pass to `IContextMenu::addItem`. We'll store them per + * All targets passed to `IContextMenu::addItem`. We'll store them per * item tag, so we can drop them together with the menu. We probably * don't have to use smart pointers for this, but the docs are missing a * lot of details o how this should be implemented and there's no * example implementation around. */ - std::unordered_map> targets; + std::unordered_map> + plugin_targets; }; /** diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 1b3ee249..56793224 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -183,15 +183,6 @@ Vst3PluginBridge::Vst3PluginBridge() request.type, request.dir, request.index, request.state); }, - [&](const YaContextMenu::GetItemCount& request) - -> YaContextMenu::GetItemCount::Response { - const auto& [proxy_object, _] = - get_proxy(request.owner_instance_id); - - return proxy_object.context_menus_ - .at(request.context_menu_id) - .menu->getItemCount(); - }, [&](YaContextMenu::AddItem& request) -> YaContextMenu::AddItem::Response { const auto& [proxy_object, _] = @@ -201,13 +192,13 @@ Vst3PluginBridge::Vst3PluginBridge() proxy_object.context_menus_.at(request.context_menu_id); if (request.target) { - context_menu.targets[request.item.tag] = + context_menu.plugin_targets[request.item.tag] = Steinberg::owned(new YaContextMenuTargetImpl( *this, std::move(*request.target))); return context_menu.menu->addItem( request.item, - context_menu.targets[request.item.tag]); + context_menu.plugin_targets[request.item.tag]); } else { return context_menu.menu->addItem(request.item, nullptr); @@ -222,8 +213,8 @@ Vst3PluginBridge::Vst3PluginBridge() proxy_object.context_menus_.at(request.context_menu_id); if (const auto it = - context_menu.targets.find(request.item.tag); - it != context_menu.targets.end()) { + context_menu.plugin_targets.find(request.item.tag); + it != context_menu.plugin_targets.end()) { return context_menu.menu->removeItem(request.item, it->second); } else { @@ -245,6 +236,30 @@ Vst3PluginBridge::Vst3PluginBridge() .menu->popup(request.x, request.y); }); }, + [&](YaContextMenuTarget::ExecuteMenuItem& request) + -> YaContextMenuTarget::ExecuteMenuItem::Response { + const auto& [proxy, _] = + get_proxy(request.owner_instance_id); + + // This is of course only used for calling host defined + // targets from the plugin, this will never be called when + // the plugin calls their own targets for whatever reason + Steinberg::Vst::IContextMenuItem item; + Steinberg::Vst::IContextMenuTarget* target = nullptr; + Steinberg::IPtr menu = + proxy.context_menus_.at(request.context_menu_id).menu; + if (menu->getItem(request.item_id, item, &target) == + Steinberg::kResultOk && + target) { + return target->executeMenuItem(request.tag); + } else { + logger_.log( + "WARNING: A IContextMenuTarget::ExecuteMenuItem " + "from the plugin could not be handled"); + + return Steinberg::kInvalidArgument; + } + }, [&](YaConnectionPoint::Notify& request) -> YaConnectionPoint::Notify::Response { const auto& [proxy_object, _] = diff --git a/src/wine-host/bridges/vst3-impls/context-menu-proxy.cpp b/src/wine-host/bridges/vst3-impls/context-menu-proxy.cpp index 4b02293c..61e6a548 100644 --- a/src/wine-host/bridges/vst3-impls/context-menu-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/context-menu-proxy.cpp @@ -18,11 +18,32 @@ #include +#include "../../common/serialization/vst3-impls/context-menu-target.h" + Vst3ContextMenuProxyImpl::Vst3ContextMenuProxyImpl( Vst3Bridge& bridge, - Vst3ContextMenuProxy::ConstructArgs&& args) noexcept - : Vst3ContextMenuProxy(std::move(args)), bridge_(bridge) { + Vst3ContextMenuProxy::ConstructArgs&& args) + : Vst3ContextMenuProxy(std::move(args)), + bridge_(bridge), + items_(std::move(YaContextMenu::arguments_.items)) { bridge.register_context_menu(*this); + + // The host has likely prepopulated the context menu with its own items. In + // that case we should create proxy targets for those so the plugin can call + // those menu items. + const int32 num_items = static_cast(items_.size()); + for (int32 item_idx = 0; item_idx < num_items; item_idx++) { + auto& item = items_[item_idx]; + + // NOTE: These host targets are indexed by the item's index because + // Bitwig doesn't assign tags to their own menu items + host_targets_[item_idx] = Steinberg::owned(new YaContextMenuTargetImpl( + bridge, YaContextMenuTarget::ConstructArgs{ + .owner_instance_id = owner_instance_id(), + .context_menu_id = context_menu_id(), + .item_id = static_cast(item_idx), + .tag = item.tag})); + } } Vst3ContextMenuProxyImpl::~Vst3ContextMenuProxyImpl() noexcept { @@ -49,9 +70,7 @@ Vst3ContextMenuProxyImpl::queryInterface(const Steinberg::TUID _iid, } int32 PLUGIN_API Vst3ContextMenuProxyImpl::getItemCount() { - return bridge_.send_message( - YaContextMenu::GetItemCount{.owner_instance_id = owner_instance_id(), - .context_menu_id = context_menu_id()}); + return static_cast(items_.size()); } tresult PLUGIN_API Vst3ContextMenuProxyImpl::getItem( @@ -63,31 +82,56 @@ tresult PLUGIN_API Vst3ContextMenuProxyImpl::getItem( // the plugin (but we'll implement a basic version anyways). if (index < 0 || index >= static_cast(items_.size())) { return Steinberg::kInvalidArgument; - } else { - item = items_[index]; - *target = context_menu_targets_[item.tag]; + } - return Steinberg::kResultOk; + item = items_[index]; + if (target) { + // The item is either a context menu item prepopulated by the host or an + // item created by the plugin itself + if (auto plugin_target = plugin_targets_.find(item.tag); + plugin_target != plugin_targets_.end()) { + *target = plugin_target->second; + return Steinberg::kResultOk; + } else if (auto proxy_target = host_targets_.find(index); + // NOTE: These proxy targets are indexed by the item's index + // because Bitwig doesn't assign tags to their context + // menu items + proxy_target != host_targets_.end()) { + *target = proxy_target->second; + return Steinberg::kResultOk; + } else { + *target = nullptr; + return Steinberg::kResultFalse; + } + } else { + std::cerr << "WARNING: Null pointer passed to 'IContextMenu::getItem()'" + << std::endl; + return Steinberg::kInvalidArgument; } } tresult PLUGIN_API Vst3ContextMenuProxyImpl::addItem(const Steinberg::Vst::IContextMenuItem& item, Steinberg::Vst::IContextMenuTarget* target) { - // TODO: I haven't come across a plugin that adds its own items, so this - // hasn't been tested yet + // TODO: I haven't come across a plugin that adds its own items to the + // host's context menu, so this hasn't been tested yet const tresult result = bridge_.send_message(YaContextMenu::AddItem{ .owner_instance_id = owner_instance_id(), .context_menu_id = context_menu_id(), .item = item, - .target = - (target ? std::make_optional( - owner_instance_id(), context_menu_id(), item.tag) - : std::nullopt)}); + .target = (target ? std::optional(YaContextMenuTarget::ConstructArgs{ + .owner_instance_id = owner_instance_id(), + .context_menu_id = context_menu_id(), + // This item ID isn't actually used here because + // it's only needed to work around a Bitwig bug + // when calling host menu items from a plugin + .item_id = static_cast(items_.size()), + .tag = item.tag}) + : std::nullopt)}); if (result == Steinberg::kResultOk) { items_.push_back(item); - context_menu_targets_[item.tag] = target; + plugin_targets_[item.tag] = target; } return result; @@ -110,7 +154,12 @@ tresult PLUGIN_API Vst3ContextMenuProxyImpl::removeItem( return candidate_item.tag == item.tag; }), items_.end()); - context_menu_targets_.erase(item.tag); + + // The target can be either a proxy target or a target added by the + // plugin + if (plugin_targets_.erase(item.tag) == 0) { + host_targets_.erase(item.tag); + } } return result; @@ -120,7 +169,7 @@ tresult PLUGIN_API Vst3ContextMenuProxyImpl::popup(Steinberg::UCoord x, Steinberg::UCoord y) { // NOTE: This requires mutual recursion, because REAPER will call // `getState()` whle the context menu is open, and `getState()` also - // has to be handled from the GUi thread + // has to be handled from the GUI thread return bridge_.send_mutually_recursive_message( YaContextMenu::Popup{.owner_instance_id = owner_instance_id(), .context_menu_id = context_menu_id(), diff --git a/src/wine-host/bridges/vst3-impls/context-menu-proxy.h b/src/wine-host/bridges/vst3-impls/context-menu-proxy.h index 2d2741a9..2782b2fd 100644 --- a/src/wine-host/bridges/vst3-impls/context-menu-proxy.h +++ b/src/wine-host/bridges/vst3-impls/context-menu-proxy.h @@ -20,9 +20,8 @@ class Vst3ContextMenuProxyImpl : public Vst3ContextMenuProxy { public: - Vst3ContextMenuProxyImpl( - Vst3Bridge& bridge, - Vst3ContextMenuProxy::ConstructArgs&& args) noexcept; + Vst3ContextMenuProxyImpl(Vst3Bridge& bridge, + Vst3ContextMenuProxy::ConstructArgs&& args); /** * When the reference count reaches zero and this destructor is called, @@ -52,19 +51,35 @@ class Vst3ContextMenuProxyImpl : public Vst3ContextMenuProxy { tresult PLUGIN_API popup(Steinberg::UCoord x, Steinberg::UCoord y) override; /** - * The targets passed when to `addItem` calls made by the plugin. This way + * The targets passed when to `addItem()` calls made by the plugin. This way * we can call these same targets later. The key here is the item's tag. + * + * If `getItem()` returns a context menu item with a tag that is not in this + * map then it's from an item belonging to the host, and we'll return a + * proxy target that would call the host's target instead. */ std::unordered_map> - context_menu_targets_; + plugin_targets_; private: Vst3Bridge& bridge_; + /** + * As mentioned above, these are the targets belonging to context items + * prepopulated by the host. Because Bitwig doesn't assign a tag to its own + * context menu items all of these this map is indexed by the **item id**. + * Calling one of these sends a message to the host to call the + * corresponding menu item. + */ + std::unordered_map> + host_targets_; + /** * The items passed when to `addItem` calls made by the plugin. This way we * can call these same targets later. + * + * This will be initialized with targets created by the host. */ std::vector items_; }; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 9a514466..2c3b21d8 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -387,10 +387,13 @@ void Vst3Bridge::run() { const auto& [instance, _] = get_instance(request.owner_instance_id); + // This is of course only used for calling plugin defined + // targets from the host, this will never be called when the + // host calls their own targets for whatever reason return instance.registered_context_menus .at(request.context_menu_id) .get() - .context_menu_targets_[request.target_tag] + .plugin_targets_[request.target_tag] ->executeMenuItem(request.tag); }, [&](YaEditController::SetComponentState& request)