mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-09 20:29:10 +02:00
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.
This commit is contained in:
@@ -18,11 +18,32 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#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<int32>(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<int32>(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<int32>(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<int32>(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<YaContextMenuTarget::ConstructArgs>(
|
||||
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<int32>(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(),
|
||||
|
||||
@@ -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<int32,
|
||||
Steinberg::IPtr<Steinberg::Vst::IContextMenuTarget>>
|
||||
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<int32, Steinberg::IPtr<YaContextMenuTarget>>
|
||||
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<Steinberg::Vst::IContextMenuItem> items_;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user