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..7a921f47 100644 --- a/src/clap_proxy.h +++ b/src/clap_proxy.h @@ -71,6 +71,39 @@ 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 +131,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 +213,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/plugview.cpp b/src/detail/vst3/plugview.cpp index 05906a2c..a00f8aa9 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 onDestroy, std::function onRunLoopAvailable) + std::function onRemoved, std::function onDestroy, + std::function onRunLoopAvailable) : IPlugView() , FObject() , _plugin(plugin) , _extgui(gui) + , _onRemoved(onRemoved) , _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,13 @@ void WrappedView::drop_ui() { if (_created) { - _created = false; _attached = false; + if (_onDestroy) + { + _onDestroy(); + } _extgui->destroy(_plugin); + _created = false; } } @@ -113,7 +115,11 @@ tresult PLUGIN_API WrappedView::attached(void* parent, FIDString /*type*/) tresult PLUGIN_API WrappedView::removed() { - drop_ui(); + if (_onRemoved) + { + _onRemoved(); + } + _attached = false; _window.ptr = nullptr; return kResultOk; } diff --git a/src/detail/vst3/plugview.h b/src/detail/vst3/plugview.h index ada28312..389d86ba 100644 --- a/src/detail/vst3/plugview.h +++ b/src/detail/vst3/plugview.h @@ -18,7 +18,7 @@ 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 onRemoved, std::function onDestroy, std::function onRunLoopAvailable); ~WrappedView(); @@ -92,7 +92,7 @@ class WrappedView : public Steinberg::IPlugView, public Steinberg::FObject void drop_ui(); const clap_plugin_t* _plugin = nullptr; const clap_plugin_gui_t* _extgui = nullptr; - std::function _onDestroy = nullptr, _onRunLoopAvailable = nullptr; + std::function _onRemoved = 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..d4adfe3a 100644 --- a/src/wrapasvst3.cpp +++ b/src/wrapasvst3.cpp @@ -230,7 +230,7 @@ IPlugView* PLUGIN_API ClapAsVst3::createView(FIDString /*name*/) if (_plugin->_ext._gui) { _wrappedview = new WrappedView( - _plugin->_plugin, _plugin->_ext._gui, + _plugin->_plugin, _plugin->_ext._gui, [this]() { clearContextMenu(); }, [this]() { #if LIN @@ -239,10 +239,13 @@ IPlugView* PLUGIN_API ClapAsVst3::createView(FIDString /*name*/) detachPosixFD(_wrappedview->getRunLoop()); _iRunLoop = nullptr; #endif - _wrappedview = nullptr; + + clearContextMenu(); + this->_wrappedview = nullptr; }, [this]() { + #if LIN attachTimers(_wrappedview->getRunLoop()); attachPosixFD(_wrappedview->getRunLoop()); @@ -305,6 +308,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 +401,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 +1223,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); + + bool is_checked = vst3item.flags & vst3item.kIsChecked; + + 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) + { + // 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) +{ + if (action_id < contextmenuitems.size()) + { + auto& item = contextmenuitems.at(action_id); + bool okay = (kResultOk == item.vst3target->executeMenuItem(item.vst3item.tag)); + clearContextMenu(); + } + 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() +{ + if (vst3ContextMenu) vst3ContextMenu->release(); + vst3ContextMenu.reset(); + contextmenuitems.clear(); +} diff --git a/src/wrapasvst3.h b/src/wrapasvst3.h index fa11b408..dba75ce3 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 @@ -190,6 +214,10 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, { DEF_INTERFACE(IMidiMapping) } + if (_plugin->_ext._contextmenu) + { + DEF_INTERFACE(IContextMenuTarget); + } } // add any other interfaces here: //if (::Steinberg::FUnknownPrivate::iidEqual(iid, IExampleSomething::iid)) @@ -202,6 +230,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 +271,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;