From 91908c57feb99a267536136ede204222d06632cc Mon Sep 17 00:00:00 2001 From: defiantnerd <97224712+defiantnerd@users.noreply.github.com> Date: Sun, 21 Apr 2024 16:02:40 +0200 Subject: [PATCH] Adding support for context menus. (#242) * Adding support for context menus. * adding host side context menu functions * extending the calling structure of plugview * creating strings for MIDI values * fixed an interface bug --- src/clap_proxy.cpp | 71 ++++++++++- src/clap_proxy.h | 44 +++++++ src/detail/vst3/parameter.cpp | 2 +- src/detail/vst3/plugview.cpp | 36 ++++-- src/detail/vst3/plugview.h | 7 +- src/wrapasvst3.cpp | 215 +++++++++++++++++++++++++++++++--- src/wrapasvst3.h | 47 ++++++++ 7 files changed, 389 insertions(+), 33 deletions(-) diff --git a/src/clap_proxy.cpp b/src/clap_proxy.cpp index 36074673..67867ff1 100644 --- a/src/clap_proxy.cpp +++ b/src/clap_proxy.cpp @@ -110,6 +110,23 @@ const clap_host_posix_fd_support hostposixfd = { const clap_host_latency latency = {[](const clap_host_t* host) -> void { self(host)->latency_changed(); }}; +const clap_host_state_t state = {[](const clap_host_t* host) -> void { self(host)->mark_dirty(); }}; + +const clap_host_context_menu_t context_menu = { + /* populate */ + [](const clap_host_t* host, const clap_context_menu_target_t* target, + const clap_context_menu_builder_t* builder) -> bool + { return self(host)->context_menu_populate(target, builder); }, + /* perform */ + [](const clap_host_t* host, const clap_context_menu_target_t* target, clap_id action_id) -> bool + { return self(host)->context_menu_perform(target, action_id); }, + /* can_popup */ + [](const clap_host_t* host) -> bool { return self(host)->context_menu_can_popup(); }, + /* popup */ + [](const clap_host_t* host, const clap_context_menu_target_t* target, int32_t screen_index, + int32_t x, int32_t y) -> bool + { return self(host)->context_menu_popup(target, screen_index, x, y); }}; + static void tail_changed(const clap_host_t* host) { self(host)->tail_changed(); @@ -119,23 +136,23 @@ const clap_host_tail tail = {tail_changed}; } // namespace HostExt -std::shared_ptr Plugin::createInstance(const clap_plugin_factory* fac, const std::string& id, +std::shared_ptr Plugin::createInstance(const clap_plugin_factory* factory, const std::string& id, Clap::IHost* host) { auto plug = std::shared_ptr(new Plugin(host)); - auto instance = fac->create_plugin(fac, plug->getClapHostInterface(), id.c_str()); + auto instance = factory->create_plugin(factory, plug->getClapHostInterface(), id.c_str()); plug->connectClap(instance); return plug; } -std::shared_ptr Plugin::createInstance(const clap_plugin_factory* fac, size_t idx, +std::shared_ptr Plugin::createInstance(const clap_plugin_factory* factory, size_t idx, Clap::IHost* host) { - auto pc = fac->get_plugin_count(fac); + auto pc = factory->get_plugin_count(factory); if (idx >= pc) return nullptr; - auto desc = fac->get_plugin_descriptor(fac, (uint32_t)idx); - return createInstance(fac, desc->id, host); + auto desc = factory->get_plugin_descriptor(factory, (uint32_t)idx); + return createInstance(factory, desc->id, host); } std::shared_ptr Plugin::createInstance(Clap::Library& library, size_t index, IHost* host) @@ -195,6 +212,13 @@ void Plugin::connectClap(const clap_plugin_t* clap) getExtension(_plugin, _ext._tail, CLAP_EXT_TAIL); getExtension(_plugin, _ext._gui, CLAP_EXT_GUI); getExtension(_plugin, _ext._timer, CLAP_EXT_TIMER_SUPPORT); + + getExtension(_plugin, _ext._contextmenu, CLAP_EXT_CONTEXT_MENU); + if (_ext._contextmenu == nullptr) + { + getExtension(_plugin, _ext._contextmenu, CLAP_EXT_CONTEXT_MENU_COMPAT); + } + #if LIN getExtension(_plugin, _ext._posixfd, CLAP_EXT_POSIX_FD_SUPPORT); #endif @@ -331,6 +355,11 @@ const clap_plugin_gui_t* Plugin::getUI() const return nullptr; } +void Plugin::mark_dirty() +{ + _parentHost->mark_dirty(); +} + void Plugin::latency_changed() { _parentHost->latency_changed(); @@ -341,6 +370,34 @@ void Plugin::tail_changed() _parentHost->tail_changed(); } +bool Plugin::context_menu_populate(const clap_context_menu_target_t* target, + const clap_context_menu_builder_t* builder) +{ + if (_parentHost->supportsContextMenu()) + { + return this->_parentHost->context_menu_populate(target, builder); + } + + // don't interfere with the context menu building + return true; +} + +bool Plugin::context_menu_perform(const clap_context_menu_target_t* target, clap_id action_id) +{ + return this->_parentHost->context_menu_perform(target, action_id); +} + +bool Plugin::context_menu_can_popup() +{ + return this->_parentHost->supportsContextMenu(); +} + +bool Plugin::context_menu_popup(const clap_context_menu_target_t* target, int32_t screen_index, + int32_t x, int32_t y) +{ + return this->_parentHost->context_menu_popup(target, screen_index, x, y); +} + void Plugin::log(clap_log_severity severity, const char* msg) { std::string n; @@ -448,6 +505,8 @@ const void* Plugin::clapExtension(const clap_host* /*host*/, const char* extensi { // TODO: implement CLAP_EXT_RENDER } + if (!strcmp(extension, CLAP_EXT_STATE)) return &HostExt::state; + if (!strcmp(extension, CLAP_EXT_CONTEXT_MENU)) return &HostExt::context_menu; return nullptr; } diff --git a/src/clap_proxy.h b/src/clap_proxy.h index 4e51bafd..e0c1590e 100644 --- a/src/clap_proxy.h +++ b/src/clap_proxy.h @@ -71,6 +71,38 @@ class IHost virtual const char* host_get_name() = 0; + // context menu + + // actually, everything here should be virtual only, but until all wrappers are updated, + // IHost provides default implementations. + + virtual bool supportsContextMenu() const + { + return false; + } + + virtual bool context_menu_populate(const clap_context_menu_target_t* target, + const clap_context_menu_builder_t* builder) + { + return false; + } + + virtual bool context_menu_perform(const clap_context_menu_target_t* target, clap_id action_id) + { + return false; + } + + virtual bool context_menu_can_popup() + { + return false; + } + + virtual bool context_menu_popup(const clap_context_menu_target_t* target, int32_t screen_index, + int32_t x, int32_t y) + { + return false; + } + #if LIN virtual bool register_fd(int fd, clap_posix_fd_flags_t flags) = 0; virtual bool modify_fd(int fd, clap_posix_fd_flags_t flags) = 0; @@ -98,6 +130,7 @@ struct ClapPluginExtensions const clap_plugin_render_t* _render = nullptr; const clap_plugin_tail_t* _tail = nullptr; const clap_plugin_timer_support_t* _timer = nullptr; + const clap_plugin_context_menu_t* _contextmenu = nullptr; #if LIN const clap_plugin_posix_fd_support* _posixfd = nullptr; #endif @@ -179,12 +212,23 @@ class Plugin void param_clear(clap_id param, clap_param_clear_flags flags); void param_request_flush(); + // state + void mark_dirty(); + // latency void latency_changed(); // tail void tail_changed(); + // context_menu + bool context_menu_populate(const clap_context_menu_target_t* target, + const clap_context_menu_builder_t* builder); + bool context_menu_perform(const clap_context_menu_target_t* target, clap_id action_id); + bool context_menu_can_popup(); + bool context_menu_popup(const clap_context_menu_target_t* target, int32_t screen_index, int32_t x, + int32_t y); + // hostgui void resize_hints_changed() { diff --git a/src/detail/vst3/parameter.cpp b/src/detail/vst3/parameter.cpp index e21e0a46..4edd5af0 100644 --- a/src/detail/vst3/parameter.cpp +++ b/src/detail/vst3/parameter.cpp @@ -45,7 +45,7 @@ Vst3Parameter::~Vst3Parameter() = default; bool Vst3Parameter::setNormalized(Steinberg::Vst::ParamValue v) { - if (isMidi && info.flags & Steinberg::Vst::ParameterInfo::kIsProgramChange) + if (isMidi && (info.flags & Steinberg::Vst::ParameterInfo::kIsProgramChange)) { return true; } diff --git a/src/detail/vst3/plugview.cpp b/src/detail/vst3/plugview.cpp index 05906a2c..2a985d32 100644 --- a/src/detail/vst3/plugview.cpp +++ b/src/detail/vst3/plugview.cpp @@ -3,11 +3,13 @@ #include WrappedView::WrappedView(const clap_plugin_t* plugin, const clap_plugin_gui_t* gui, + std::function onReleaseAdditionalReferences, std::function onDestroy, std::function onRunLoopAvailable) : IPlugView() , FObject() , _plugin(plugin) , _extgui(gui) + , _onReleaseAdditionalReferences(onReleaseAdditionalReferences) , _onDestroy(onDestroy) , _onRunLoopAvailable(onRunLoopAvailable) { @@ -15,10 +17,6 @@ WrappedView::WrappedView(const clap_plugin_t* plugin, const clap_plugin_gui_t* g WrappedView::~WrappedView() { - if (_onDestroy) - { - _onDestroy(); - } drop_ui(); } @@ -47,9 +45,25 @@ void WrappedView::drop_ui() { if (_created) { - _created = false; + releaseAdditionalReferences(); _attached = false; + if (_onDestroy) + { + _onDestroy(); + } _extgui->destroy(_plugin); + _created = false; + } +} + +void WrappedView::releaseAdditionalReferences() +{ + // releases things like IContextMenu when the wrapper provided entries, but the user chose + // a plugin-owned entry. + + if (_onReleaseAdditionalReferences) + { + _onReleaseAdditionalReferences(); } } @@ -113,7 +127,8 @@ tresult PLUGIN_API WrappedView::attached(void* parent, FIDString /*type*/) tresult PLUGIN_API WrappedView::removed() { - drop_ui(); + releaseAdditionalReferences(); + _attached = false; _window.ptr = nullptr; return kResultOk; } @@ -183,15 +198,21 @@ tresult PLUGIN_API WrappedView::onSize(ViewRect* newSize) return kResultOk; } -tresult PLUGIN_API WrappedView::onFocus(TBool /*state*/) +tresult PLUGIN_API WrappedView::onFocus(TBool state) { // TODO: this might be something for the wrapperhost API // to notify the plugin about a focus change + if (state == false) + { + releaseAdditionalReferences(); + } return kResultOk; } tresult PLUGIN_API WrappedView::setFrame(IPlugFrame* frame) { + releaseAdditionalReferences(); + _plugFrame = frame; #if LIN @@ -205,7 +226,6 @@ tresult PLUGIN_API WrappedView::setFrame(IPlugFrame* frame) } } #endif - return kResultOk; } diff --git a/src/detail/vst3/plugview.h b/src/detail/vst3/plugview.h index ada28312..f9d1e989 100644 --- a/src/detail/vst3/plugview.h +++ b/src/detail/vst3/plugview.h @@ -18,7 +18,8 @@ using namespace Steinberg; class WrappedView : public Steinberg::IPlugView, public Steinberg::FObject { public: - WrappedView(const clap_plugin_t* plugin, const clap_plugin_gui_t* gui, std::function onDestroy, + WrappedView(const clap_plugin_t* plugin, const clap_plugin_gui_t* gui, + std::function onReleaseAdditionalReferences, std::function onDestroy, std::function onRunLoopAvailable); ~WrappedView(); @@ -90,9 +91,11 @@ class WrappedView : public Steinberg::IPlugView, public Steinberg::FObject private: void ensure_ui(); void drop_ui(); + void releaseAdditionalReferences(); const clap_plugin_t* _plugin = nullptr; const clap_plugin_gui_t* _extgui = nullptr; - std::function _onDestroy = nullptr, _onRunLoopAvailable = nullptr; + std::function _onReleaseAdditionalReferences = nullptr, _onDestroy = nullptr, + _onRunLoopAvailable = nullptr; clap_window_t _window = {nullptr, {nullptr}}; IPlugFrame* _plugFrame = nullptr; ViewRect _rect = {0, 0, 0, 0}; diff --git a/src/wrapasvst3.cpp b/src/wrapasvst3.cpp index ac0cb38f..dca175fa 100644 --- a/src/wrapasvst3.cpp +++ b/src/wrapasvst3.cpp @@ -56,6 +56,7 @@ tresult PLUGIN_API ClapAsVst3::initialize(FUnknown* context) tresult PLUGIN_API ClapAsVst3::terminate() { + clearContextMenu(); if (_plugin) { _os_attached.off(); // ensure we are detached @@ -229,27 +230,34 @@ IPlugView* PLUGIN_API ClapAsVst3::createView(FIDString /*name*/) { if (_plugin->_ext._gui) { - _wrappedview = new WrappedView( - _plugin->_plugin, _plugin->_ext._gui, - [this]() - { + clearContextMenu(); + if (_wrappedview == nullptr) + { + _wrappedview = new WrappedView( + _plugin->_plugin, _plugin->_ext._gui, [this]() { clearContextMenu(); }, + [this]() + { #if LIN - // the host calls the destructor, the wrapper just removes its pointer - detachTimers(_wrappedview->getRunLoop()); - detachPosixFD(_wrappedview->getRunLoop()); - _iRunLoop = nullptr; + // the host calls the destructor, the wrapper just removes its pointer + detachTimers(_wrappedview->getRunLoop()); + detachPosixFD(_wrappedview->getRunLoop()); + _iRunLoop = nullptr; #endif - _wrappedview = nullptr; - }, - [this]() - { + + clearContextMenu(); + this->_wrappedview = nullptr; + }, + [this]() + { + #if LIN - attachTimers(_wrappedview->getRunLoop()); - attachPosixFD(_wrappedview->getRunLoop()); + attachTimers(_wrappedview->getRunLoop()); + attachPosixFD(_wrappedview->getRunLoop()); #else - (void)this; // silence warning on non-linux + (void)this; // silence warning on non-linux #endif - }); + }); + } return _wrappedview; } return nullptr; @@ -269,8 +277,19 @@ tresult PLUGIN_API ClapAsVst3::getParamStringByValue(Vst::ParamID id, Vst::Param return kResultOk; } + if (param->isMidi) + { + auto r = std::to_string((int)val); + UString wrapper(&string[0], str16BufferSize(Steinberg::Vst::String128)); + + wrapper.assign(r.c_str(), (Steinberg::int32)(r.size() + 1)); + + return kResultOk; + } + char outbuf[128]; memset(outbuf, 0, sizeof(outbuf)); + if (this->_plugin->_ext._params->value_to_text(_plugin->_plugin, param->id, val, outbuf, 127)) { UString wrapper(&string[0], str16BufferSize(Steinberg::Vst::String128)); @@ -305,6 +324,23 @@ tresult PLUGIN_API ClapAsVst3::activateBus(Vst::MediaType type, Vst::BusDirectio //----------------------------------------------------------------------------- +tresult PLUGIN_API ClapAsVst3::setComponentHandler(Vst::IComponentHandler* handler) +{ + componentHandler3.reset(); + + // the base class extracts IComponentHandler and IComponentHandler2 + auto result = super::setComponentHandler(handler); + // but for context menus IComponentHandler3 is needed, so it is being retrieved here. + if (componentHandler && result == kResultOk) + { + this->componentHandler->queryInterface(Vst::IComponentHandler3::iid, (void**)&componentHandler3); + } + + return result; +} + +//----------------------------------------------------------------------------- + tresult PLUGIN_API ClapAsVst3::getMidiControllerAssignment(int32 busIndex, int16 channel, Vst::CtrlNumber midiControllerNumber, Vst::ParamID& id /*out*/) @@ -381,6 +417,11 @@ tresult ClapAsVst3::getUnitByBus(Vst::MediaType type, Vst::BusDirection dir, int return kResultFalse; } +tresult ClapAsVst3::executeMenuItem(int32 tag) +{ + return kResultOk; +} + static Vst::SpeakerArrangement speakerArrFromPortType(const char* port_type) { static const std::pair arrangementmap[] = { @@ -1198,3 +1239,145 @@ void ClapAsVst3::firePosixFDIsSet(int fd, clap_posix_fd_flags_t flags) _plugin->_ext._posixfd->on_fd(_plugin->_plugin, fd, flags); } #endif + +void wrapper_context_menu_item::vst3_to_clap(clap_id action_id) +{ + name = std::make_unique(); + *name = VST3::StringConvert::convert(vst3item.name); + + if (vst3item.flags == vst3item.kIsGroupStart) + { + kind = CLAP_CONTEXT_MENU_ITEM_BEGIN_SUBMENU; + this->clap.submenu.label = name->c_str(); + this->clap.submenu.is_enabled = true; // this works different to the VST3 submenus + return; + } + if (vst3item.flags == vst3item.kIsGroupEnd) + { + this->kind = CLAP_CONTEXT_MENU_ITEM_END_SUBMENU; + return; + } + if (vst3item.flags & vst3item.kIsSeparator) + { + this->kind = CLAP_CONTEXT_MENU_ITEM_SEPARATOR; + return; + } + // now this is a weird one in VST3 (or in CLAP, depends on the POV) + if (vst3item.flags & vst3item.kIsChecked) + { + this->kind = CLAP_CONTEXT_MENU_ITEM_CHECK_ENTRY; + this->clap.menu_check_entry.label = name->c_str(); + this->clap.menu_check_entry.is_checked = true; // of course it is checked + this->clap.menu_check_entry.is_enabled = !(vst3item.flags & vst3item.kIsDisabled); + this->clap.menu_check_entry.action_id = action_id; + return; + } + this->kind = CLAP_CONTEXT_MENU_ITEM_ENTRY; + this->clap.entry.label = name->c_str(); + this->clap.entry.is_enabled = !(vst3item.flags & vst3item.kIsDisabled); + this->clap.entry.action_id = action_id; +} + +bool ClapAsVst3::supportsContextMenu() const +{ + return (this->componentHandler3 != nullptr); +} + +bool ClapAsVst3::context_menu_populate(const clap_context_menu_target_t* target, + const clap_context_menu_builder_t* builder) +{ + vst3ContextMenu.reset(); + + // first check if all entry types are supported. + if (!builder->supports(builder, CLAP_CONTEXT_MENU_ITEM_ENTRY)) return false; + if (!builder->supports(builder, CLAP_CONTEXT_MENU_ITEM_CHECK_ENTRY)) return false; + if (!builder->supports(builder, CLAP_CONTEXT_MENU_ITEM_SEPARATOR)) return false; + if (!builder->supports(builder, CLAP_CONTEXT_MENU_ITEM_BEGIN_SUBMENU)) return false; + if (!builder->supports(builder, CLAP_CONTEXT_MENU_ITEM_END_SUBMENU)) return false; + // CLAP_CONTEXT_MENU_ITEM_TITLE is not used by VST3 + + if (target->kind == CLAP_CONTEXT_MENU_TARGET_KIND_GLOBAL) + { + this->vst3ContextMenu = componentHandler3->createContextMenu(this->_wrappedview, nullptr); + } + if (target->kind == CLAP_CONTEXT_MENU_TARGET_KIND_PARAM) + { + vst3ContextMenuParamID = target->id; + vst3ContextMenu = componentHandler3->createContextMenu(_wrappedview, &vst3ContextMenuParamID); + } + if (vst3ContextMenu) + { + vst3ContextMenu->release(); // the IPtr holds the reference + // preparing the internal mapping structure with wrapper_context_menu_item + auto itmcnt = vst3ContextMenu->getItemCount(); + this->contextmenuitems.resize(itmcnt); + + for (decltype(itmcnt) i = 0; i < itmcnt; ++i) + { + wrapper_context_menu_item& item = contextmenuitems.at(i); + + if (kResultOk == vst3ContextMenu->getItem(i, item.vst3item, &item.vst3target)) + { + // create the appropriate clap structure + item.vst3_to_clap((clap_id)i); + + switch (item.kind) + { + case CLAP_CONTEXT_MENU_ITEM_ENTRY: + builder->add_item(builder, item.kind, &item.clap.entry); + break; + case CLAP_CONTEXT_MENU_ITEM_CHECK_ENTRY: + builder->add_item(builder, item.kind, &item.clap.menu_check_entry); + break; + case CLAP_CONTEXT_MENU_ITEM_SEPARATOR: + builder->add_item(builder, item.kind, nullptr); + break; + case CLAP_CONTEXT_MENU_ITEM_BEGIN_SUBMENU: + builder->add_item(builder, item.kind, &item.clap.submenu); + break; + case CLAP_CONTEXT_MENU_ITEM_END_SUBMENU: + builder->add_item(builder, item.kind, nullptr); + break; + case CLAP_CONTEXT_MENU_ITEM_TITLE: + // this does not exist + break; + default: + break; + } + } + } + return true; + } + + return false; +} + +bool ClapAsVst3::context_menu_perform(const clap_context_menu_target_t* target, clap_id action_id) +{ + (void)target; + if (action_id < contextmenuitems.size()) + { + auto& item = contextmenuitems.at(action_id); + bool okay = (kResultOk == item.vst3target->executeMenuItem(item.vst3item.tag)); + clearContextMenu(); + return okay; + } + return false; +} + +bool ClapAsVst3::context_menu_can_popup() +{ + return false; +} + +bool ClapAsVst3::context_menu_popup(const clap_context_menu_target_t* target, int32_t screen_index, + int32_t x, int32_t y) +{ + return false; +} + +void ClapAsVst3::clearContextMenu() +{ + vst3ContextMenu.reset(); + contextmenuitems.clear(); +} diff --git a/src/wrapasvst3.h b/src/wrapasvst3.h index fa11b408..d27ea0a0 100644 --- a/src/wrapasvst3.h +++ b/src/wrapasvst3.h @@ -24,6 +24,7 @@ #include #include #include +#include #ifdef __GNUC__ #pragma GCC diagnostic pop @@ -89,9 +90,26 @@ class valueEvent : public queueEvent } }; +struct wrapper_context_menu_item +{ + Vst::IContextMenuItem vst3item; + Vst::IContextMenuTarget* vst3target; + clap_context_menu_item_kind_t kind; + union _entry + { + clap_context_menu_entry_t entry; + clap_context_menu_check_entry_t menu_check_entry; + clap_context_menu_item_title_t menu_item_title; + clap_context_menu_submenu_t submenu; + } clap; + std::unique_ptr name; + void vst3_to_clap(clap_id action_id); +}; + class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, public Steinberg::Vst::IMidiMapping, public Steinberg::Vst::INoteExpressionController, + public Steinberg::Vst::IContextMenuTarget, public Clap::IHost, public Clap::IAutomation, public os::IPlugObject @@ -131,6 +149,9 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, tresult PLUGIN_API activateBus(Vst::MediaType type, Vst::BusDirection dir, int32 index, TBool state) override; + // from IEditController + tresult PLUGIN_API setComponentHandler(Vst::IComponentHandler* handler) override; + //----from IEditControllerEx1-------------------------------- IPlugView* PLUGIN_API createView(FIDString name) override; /** Gets for a given paramID and normalized value its associated string representation. */ @@ -179,6 +200,9 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, } #endif + //---IContextMenuTarget ---------------------------------------------------------------- + tresult PLUGIN_API executeMenuItem(int32 tag) override; + //---Interface-------------------------------------------------------------------------- OBJ_METHODS(ClapAsVst3, SingleComponentEffect) DEFINE_INTERFACES @@ -191,6 +215,13 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, DEF_INTERFACE(IMidiMapping) } } + if (::Steinberg::FUnknownPrivate::iidEqual(iid, IContextMenuTarget::iid)) + { + if (_plugin->_ext._contextmenu) + { + DEF_INTERFACE(IContextMenuTarget); + } + } // add any other interfaces here: //if (::Steinberg::FUnknownPrivate::iidEqual(iid, IExampleSomething::iid)) //{ @@ -202,6 +233,13 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, END_DEFINE_INTERFACES(SingleComponentEffect) REFCOUNT_METHODS(SingleComponentEffect); + IPtr componentHandler3 = nullptr; + IPtr vst3ContextMenu = nullptr; + std::vector contextmenuitems; + uint32_t vst3ContextMenuParamID = 0; + + void clearContextMenu(); + //---Clap::IHost------------------------------------------------------------------------ void setupWrapperSpecifics(const clap_plugin_t* plugin) override; @@ -236,6 +274,15 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, const char* host_get_name() override; + bool supportsContextMenu() const override; + // context_menu + bool context_menu_populate(const clap_context_menu_target_t* target, + const clap_context_menu_builder_t* builder) override; + bool context_menu_perform(const clap_context_menu_target_t* target, clap_id action_id) override; + bool context_menu_can_popup() override; + bool context_menu_popup(const clap_context_menu_target_t* target, int32_t screen_index, int32_t x, + int32_t y) override; + #if LIN bool register_fd(int fd, clap_posix_fd_flags_t flags) override; bool modify_fd(int fd, clap_posix_fd_flags_t flags) override;