diff --git a/source/backend/plugin/CarlaPluginLV2.cpp b/source/backend/plugin/CarlaPluginLV2.cpp index 3f4b41f4d6..abae5185f9 100644 --- a/source/backend/plugin/CarlaPluginLV2.cpp +++ b/source/backend/plugin/CarlaPluginLV2.cpp @@ -183,6 +183,7 @@ enum CarlaLv2Features { kFeatureIdUiPortMap, kFeatureIdUiPortSubscribe, kFeatureIdUiResize, + kFeatureIdUiRequestParameter, kFeatureIdUiTouch, kFeatureIdExternalUi, kFeatureIdExternalUiOld, @@ -651,6 +652,9 @@ class CarlaPluginLV2 : public CarlaPlugin, if (fFeatures[kFeatureIdUiPortMap] != nullptr && fFeatures[kFeatureIdUiPortMap]->data != nullptr) delete (LV2UI_Port_Map*)fFeatures[kFeatureIdUiPortMap]->data; + if (fFeatures[kFeatureIdUiRequestParameter] != nullptr && fFeatures[kFeatureIdUiRequestParameter]->data != nullptr) + delete (LV2UI_Request_Parameter*)fFeatures[kFeatureIdUiRequestParameter]->data; + if (fFeatures[kFeatureIdUiResize] != nullptr && fFeatures[kFeatureIdUiResize]->data != nullptr) delete (LV2UI_Resize*)fFeatures[kFeatureIdUiResize]->data; @@ -1519,25 +1523,7 @@ class CarlaPluginLV2 : public CarlaPlugin, if (path != nullptr && path[0] != '\0') { carla_stdout("LV2 file path to send: '%s'", path); - - uint8_t atomBuf[4096]; - lv2_atom_forge_set_buffer(&fAtomForge, atomBuf, sizeof(atomBuf)); - - LV2_Atom_Forge_Frame forgeFrame; - lv2_atom_forge_object(&fAtomForge, &forgeFrame, kUridNull, kUridPatchSet); - - lv2_atom_forge_key(&fAtomForge, kUridPatchPoperty); - lv2_atom_forge_urid(&fAtomForge, getCustomURID(fFilePathURI)); - - lv2_atom_forge_key(&fAtomForge, kUridPatchValue); - lv2_atom_forge_path(&fAtomForge, path, static_cast(std::strlen(path))); - - lv2_atom_forge_pop(&fAtomForge, &forgeFrame); - - LV2_Atom* const atom((LV2_Atom*)atomBuf); - CARLA_SAFE_ASSERT(atom->size < sizeof(atomBuf)); - - fAtomBufferEvIn.put(atom, fEventsIn.ctrlIndex); + writeAtomPath(path, getCustomURID(fFilePathURI)); } } else @@ -1874,6 +1860,25 @@ class CarlaPluginLV2 : public CarlaPlugin, void uiIdle() override { + if (const char* const fileNeededForURI = fUI.fileNeededForURI) + { + const char* const path = pData->engine->runFileCallback(FILE_CALLBACK_OPEN, + /* isDir */ false, + /* title */ "File open", + /* filters */ ""); + + fUI.fileNeededForURI = nullptr; + + if (path != nullptr) + { + carla_stdout("LV2 requested path to send: '%s'", path); + writeAtomPath(path, getCustomURID(fileNeededForURI)); + } + + // this function will be called recursively, stop here + return; + } + if (fAtomBufferUiOut.isDataAvailableForReading()) { Lv2AtomRingBuffer tmpRingBuffer(fAtomBufferUiOut, fAtomBufferUiOutTmpData); @@ -2919,7 +2924,8 @@ class CarlaPluginLV2 : public CarlaPlugin, pData->options &= ~PLUGIN_OPTION_FORCE_STEREO; // plugin hints - pData->hints = (pData->hints & PLUGIN_HAS_INLINE_DISPLAY) ? PLUGIN_HAS_INLINE_DISPLAY : 0; + pData->hints = (pData->hints & PLUGIN_HAS_INLINE_DISPLAY) ? PLUGIN_HAS_INLINE_DISPLAY : 0 + | (pData->hints & PLUGIN_NEEDS_UI_MAIN_THREAD) ? PLUGIN_NEEDS_UI_MAIN_THREAD : 0; if (isRealtimeSafe()) pData->hints |= PLUGIN_IS_RTSAFE; @@ -5348,6 +5354,34 @@ class CarlaPluginLV2 : public CarlaPlugin, return LV2UI_INVALID_PORT_INDEX; } + LV2UI_Request_Parameter_Status handleUIRequestParameter(const LV2_URID key) + { + CARLA_SAFE_ASSERT_RETURN(fUI.type != UI::TYPE_NULL, LV2UI_REQUEST_PARAMETER_ERR_UNKNOWN); + carla_debug("CarlaPluginLV2::handleUIRequestParameter(%u)", key); + + // check if a file browser is already open + if (fUI.fileNeededForURI != nullptr) + return LV2UI_REQUEST_PARAMETER_ERR_UNKNOWN; + + const char* const uri = getCustomURIDString(key); + CARLA_SAFE_ASSERT_RETURN(uri != nullptr && uri != kUnmapFallback, LV2UI_REQUEST_PARAMETER_ERR_UNSUPPORTED); + + for (uint32_t i=0; i < fRdfDescriptor->ParameterCount; ++i) + { + if (fRdfDescriptor->Parameters[i].Type != LV2_PARAMETER_PATH) + continue; + if (std::strcmp(fRdfDescriptor->Parameters[i].URI, uri) != 0) + continue; + + // TODO file browser filters, also store label to use for title + fUI.fileNeededForURI = uri; + + return LV2UI_REQUEST_PARAMETER_SUCCESS; + } + + return LV2UI_REQUEST_PARAMETER_ERR_UNSUPPORTED; + } + int handleUIResize(const int width, const int height) { CARLA_SAFE_ASSERT_RETURN(fUI.window != nullptr, 1); @@ -6150,8 +6184,11 @@ class CarlaPluginLV2 : public CarlaPlugin, carla_stderr("Plugin UI wants a feature that is not supported (ignored):\n%s", uri); } + if (std::strcmp(uri, LV2_UI__makeResident) == 0 || std::strcmp(uri, LV2_UI__makeSONameResident) == 0) canDelete = false; + else if (std::strcmp(uri, LV2_UI__requestParameter) == 0) + pData->hints |= PLUGIN_NEEDS_UI_MAIN_THREAD; } if (! canContinue) @@ -6332,13 +6369,17 @@ class CarlaPluginLV2 : public CarlaPlugin, uiPortMapFt->handle = this; uiPortMapFt->port_index = carla_lv2_ui_port_map; - LV2UI_Resize* const uiResizeFt = new LV2UI_Resize; - uiResizeFt->handle = this; - uiResizeFt->ui_resize = carla_lv2_ui_resize; + LV2UI_Request_Parameter* const uiRequestParamFt = new LV2UI_Request_Parameter; + uiRequestParamFt->handle = this; + uiRequestParamFt->request = carla_lv2_ui_request_parameter; + + LV2UI_Resize* const uiResizeFt = new LV2UI_Resize; + uiResizeFt->handle = this; + uiResizeFt->ui_resize = carla_lv2_ui_resize; - LV2UI_Touch* const uiTouchFt = new LV2UI_Touch; - uiTouchFt->handle = this; - uiTouchFt->touch = carla_lv2_ui_touch; + LV2UI_Touch* const uiTouchFt = new LV2UI_Touch; + uiTouchFt->handle = this; + uiTouchFt->touch = carla_lv2_ui_touch; LV2_External_UI_Host* const uiExternalHostFt = new LV2_External_UI_Host; uiExternalHostFt->ui_closed = carla_lv2_external_ui_closed; @@ -6380,6 +6421,9 @@ class CarlaPluginLV2 : public CarlaPlugin, fFeatures[kFeatureIdUiPortSubscribe]->URI = LV2_UI__portSubscribe; fFeatures[kFeatureIdUiPortSubscribe]->data = nullptr; + fFeatures[kFeatureIdUiRequestParameter]->URI = LV2_UI__requestParameter; + fFeatures[kFeatureIdUiRequestParameter]->data = uiRequestParamFt; + fFeatures[kFeatureIdUiResize]->URI = LV2_UI__resize; fFeatures[kFeatureIdUiResize]->data = uiResizeFt; @@ -6458,6 +6502,30 @@ class CarlaPluginLV2 : public CarlaPlugin, // ------------------------------------------------------------------- + void writeAtomPath(const char* const path, const LV2_URID urid) + { + uint8_t atomBuf[4096]; + lv2_atom_forge_set_buffer(&fAtomForge, atomBuf, sizeof(atomBuf)); + + LV2_Atom_Forge_Frame forgeFrame; + lv2_atom_forge_object(&fAtomForge, &forgeFrame, kUridNull, kUridPatchSet); + + lv2_atom_forge_key(&fAtomForge, kUridPatchPoperty); + lv2_atom_forge_urid(&fAtomForge, urid); + + lv2_atom_forge_key(&fAtomForge, kUridPatchValue); + lv2_atom_forge_path(&fAtomForge, path, static_cast(std::strlen(path))); + + lv2_atom_forge_pop(&fAtomForge, &forgeFrame); + + LV2_Atom* const atom((LV2_Atom*)atomBuf); + CARLA_SAFE_ASSERT(atom->size < sizeof(atomBuf)); + + fAtomBufferEvIn.put(atom, fEventsIn.ctrlIndex); + } + + // ------------------------------------------------------------------- + private: LV2_Handle fHandle; LV2_Handle fHandle2; @@ -6541,6 +6609,7 @@ class CarlaPluginLV2 : public CarlaPlugin, const LV2UI_Descriptor* descriptor; const LV2_RDF_UI* rdfDescriptor; + const char* fileNeededForURI; CarlaPluginUI* window; UI() @@ -6549,6 +6618,7 @@ class CarlaPluginLV2 : public CarlaPlugin, widget(nullptr), descriptor(nullptr), rdfDescriptor(nullptr), + fileNeededForURI(nullptr), window(nullptr) {} ~UI() @@ -6557,6 +6627,7 @@ class CarlaPluginLV2 : public CarlaPlugin, CARLA_SAFE_ASSERT(widget == nullptr); CARLA_SAFE_ASSERT(descriptor == nullptr); CARLA_SAFE_ASSERT(rdfDescriptor == nullptr); + CARLA_SAFE_ASSERT(fileNeededForURI == nullptr); CARLA_SAFE_ASSERT(window == nullptr); } @@ -7076,6 +7147,17 @@ class CarlaPluginLV2 : public CarlaPlugin, return ((CarlaPluginLV2*)handle)->handleUIPortMap(symbol); } + // ---------------------------------------------------------------------------------------------------------------- + // UI Request Parameter Feature + + static LV2UI_Request_Parameter_Status carla_lv2_ui_request_parameter(LV2UI_Feature_Handle handle, LV2_URID key) + { + CARLA_SAFE_ASSERT_RETURN(handle != nullptr, LV2UI_REQUEST_PARAMETER_ERR_UNKNOWN); + carla_debug("carla_lv2_ui_request_parameter(%p, %u)", handle, key); + + return ((CarlaPluginLV2*)handle)->handleUIRequestParameter(key); + } + // ------------------------------------------------------------------- // UI Resize Feature @@ -7223,6 +7305,22 @@ bool CarlaPipeServerLV2::msgReceived(const char* const msg) noexcept return true; } + if (std::strcmp(msg, "requestparam") == 0) + { + uint32_t urid; + + CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(urid), true); + + if (urid != 0) + { + try { + kPlugin->handleUIRequestParameter(urid); + } CARLA_SAFE_EXCEPTION("msgReceived requestparam"); + } + + return true; + } + return false; } diff --git a/source/bridges-ui/CarlaBridgeFormatLV2.cpp b/source/bridges-ui/CarlaBridgeFormatLV2.cpp index b837fb29a8..3f9be6df1b 100644 --- a/source/bridges-ui/CarlaBridgeFormatLV2.cpp +++ b/source/bridges-ui/CarlaBridgeFormatLV2.cpp @@ -40,6 +40,7 @@ CARLA_BRIDGE_UI_START_NAMESPACE static double gInitialSampleRate = 44100.0; static const char* const kNullWindowTitle = "TestUI"; static const uint32_t kNullWindowTitleSize = 6; +static const char* const kUnmapFallback = "urn:null"; // LV2 URI Map Ids enum CarlaLv2URIDs { @@ -125,6 +126,7 @@ enum CarlaLv2Features { kFeatureIdUiParent, kFeatureIdUiPortMap, kFeatureIdUiPortSubscribe, + kFeatureIdUiRequestParameter, kFeatureIdUiResize, kFeatureIdUiTouch, kFeatureCount @@ -275,6 +277,10 @@ class CarlaLv2Client : public CarlaBridgeFormat uiPortMapFt->handle = this; uiPortMapFt->port_index = carla_lv2_ui_port_map; + LV2UI_Request_Parameter* const uiRequestParamFt = new LV2UI_Request_Parameter; + uiRequestParamFt->handle = this; + uiRequestParamFt->request = carla_lv2_ui_request_parameter; + LV2UI_Resize* const uiResizeFt = new LV2UI_Resize; uiResizeFt->handle = this; uiResizeFt->ui_resize = carla_lv2_ui_resize; @@ -333,6 +339,9 @@ class CarlaLv2Client : public CarlaBridgeFormat fFeatures[kFeatureIdUiPortSubscribe]->URI = LV2_UI__portSubscribe; fFeatures[kFeatureIdUiPortSubscribe]->data = nullptr; + fFeatures[kFeatureIdUiRequestParameter]->URI = LV2_UI__requestParameter; + fFeatures[kFeatureIdUiRequestParameter]->data = uiRequestParamFt; + fFeatures[kFeatureIdUiResize]->URI = LV2_UI__resize; fFeatures[kFeatureIdUiResize]->data = uiResizeFt; @@ -364,6 +373,7 @@ class CarlaLv2Client : public CarlaBridgeFormat delete (LV2_URID_Map*)fFeatures[kFeatureIdUridMap]->data; delete (LV2_URID_Unmap*)fFeatures[kFeatureIdUridUnmap]->data; delete (LV2UI_Port_Map*)fFeatures[kFeatureIdUiPortMap]->data; + delete (LV2UI_Request_Parameter*)fFeatures[kFeatureIdUiRequestParameter]->data; delete (LV2UI_Resize*)fFeatures[kFeatureIdUiResize]->data; for (uint32_t i=0; i < kFeatureCount; ++i) @@ -723,9 +733,8 @@ class CarlaLv2Client : public CarlaBridgeFormat const char* getCustomURIDString(const LV2_URID urid) const noexcept { - static const char* const sFallback = "urn:null"; - CARLA_SAFE_ASSERT_RETURN(urid != kUridNull, sFallback); - CARLA_SAFE_ASSERT_RETURN(urid < fCustomURIDs.size(), sFallback); + CARLA_SAFE_ASSERT_RETURN(urid != kUridNull, kUnmapFallback); + CARLA_SAFE_ASSERT_RETURN(urid < fCustomURIDs.size(), kUnmapFallback); carla_debug("CarlaLv2Client::getCustomURIDString(%i)", urid); return fCustomURIDs[urid].c_str(); @@ -753,6 +762,42 @@ class CarlaLv2Client : public CarlaBridgeFormat return LV2UI_INVALID_PORT_INDEX; } + LV2UI_Request_Parameter_Status handleUiRequestParameter(const LV2_URID key) + { + CARLA_SAFE_ASSERT_RETURN(fToolkit != nullptr, LV2UI_REQUEST_PARAMETER_ERR_UNKNOWN); + carla_debug("CarlaPluginLV2::handleUIRequestParameter(%u)", key); + + // TODO check if a file browser is already open + + const char* const uri = getCustomURIDString(key); + CARLA_SAFE_ASSERT_RETURN(uri != nullptr && uri != kUnmapFallback, LV2UI_REQUEST_PARAMETER_ERR_UNSUPPORTED); + + for (uint32_t i=0; i < fRdfDescriptor->ParameterCount; ++i) + { + if (fRdfDescriptor->Parameters[i].Type != LV2_PARAMETER_PATH) + continue; + if (std::strcmp(fRdfDescriptor->Parameters[i].URI, uri) != 0) + continue; + + // TODO file browser filters, also label for title + if (isPipeRunning()) + { + char tmpBuf[0xff]; + std::snprintf(tmpBuf, 0xff-1, "%u\n", key); + tmpBuf[0xff-1] = '\0'; + + const CarlaMutexLocker cml(getPipeLock()); + + writeMessage("requestparam\n", 13); + writeMessage(tmpBuf); + } + + return LV2UI_REQUEST_PARAMETER_SUCCESS; + } + + return LV2UI_REQUEST_PARAMETER_ERR_UNSUPPORTED; + } + int handleUiResize(const int width, const int height) { CARLA_SAFE_ASSERT_RETURN(fToolkit != nullptr, 1); @@ -1251,6 +1296,17 @@ class CarlaLv2Client : public CarlaBridgeFormat return ((CarlaLv2Client*)handle)->handleUiPortMap(symbol); } + // ---------------------------------------------------------------------------------------------------------------- + // UI Request Parameter Feature + + static LV2UI_Request_Parameter_Status carla_lv2_ui_request_parameter(LV2UI_Feature_Handle handle, LV2_URID key) + { + CARLA_SAFE_ASSERT_RETURN(handle != nullptr, LV2UI_REQUEST_PARAMETER_ERR_UNKNOWN); + carla_debug("carla_lv2_ui_request_parameter(%p, %u)", handle, key); + + return ((CarlaLv2Client*)handle)->handleUiRequestParameter(key); + } + // ---------------------------------------------------------------------------------------------------------------- // UI Resize Feature diff --git a/source/includes/lv2/ui.h b/source/includes/lv2/ui.h index 43bdf7d76d..d90755f5b0 100644 --- a/source/includes/lv2/ui.h +++ b/source/includes/lv2/ui.h @@ -31,6 +31,7 @@ #include #include "lv2.h" +#include "urid.h" #define LV2_UI_URI "http://lv2plug.in/ns/extensions/ui" ///< http://lv2plug.in/ns/extensions/ui #define LV2_UI_PREFIX LV2_UI_URI "#" ///< http://lv2plug.in/ns/extensions/ui# @@ -61,6 +62,7 @@ #define LV2_UI__protocol LV2_UI_PREFIX "protocol" ///< http://lv2plug.in/ns/extensions/ui#protocol #define LV2_UI__floatProtocol LV2_UI_PREFIX "floatProtocol" ///< http://lv2plug.in/ns/extensions/ui#floatProtocol #define LV2_UI__peakProtocol LV2_UI_PREFIX "peakProtocol" ///< http://lv2plug.in/ns/extensions/ui#peakProtocol +#define LV2_UI__requestParameter LV2_UI_PREFIX "requestParameter" ///< http://lv2plug.in/ns/extensions/ui#requestParameter #define LV2_UI__resize LV2_UI_PREFIX "resize" ///< http://lv2plug.in/ns/extensions/ui#resize #define LV2_UI__scaleFactor LV2_UI_PREFIX "scaleFactor" ///< http://lv2plug.in/ns/extensions/ui#scaleFactor #define LV2_UI__showInterface LV2_UI_PREFIX "showInterface" ///< http://lv2plug.in/ns/extensions/ui#showInterface @@ -346,6 +348,67 @@ typedef struct _LV2UI_Touch { bool grabbed); } LV2UI_Touch; +/** A status code for LV2UI_Request_Parameter::request. */ +typedef enum { + /** + Completed successfully. + + The host will set the parameter later if the user choses a new value. + */ + LV2UI_REQUEST_PARAMETER_SUCCESS, + + /** + Unknown parameter. + + The host is not aware of this parameter, and is not able to set a new + value for it. + */ + LV2UI_REQUEST_PARAMETER_ERR_UNKNOWN, + + /** + Unsupported parameter. + + The host knows about this parameter, but does not support requesting a + new value for it from the user. This is likely because the host does + not have UI support for choosing a value with the appropriate type. + */ + LV2UI_REQUEST_PARAMETER_ERR_UNSUPPORTED +} LV2UI_Request_Parameter_Status; + +/** + A feature to request a new parameter value from the host. +*/ +typedef struct _LV2UI_Request_Parameter { + /** + Pointer to opaque data which must be passed to request(). + */ + LV2UI_Feature_Handle handle; + + /** + Request a value for a parameter from the host. + + The main use case for this is a UI requesting a file path from the host, + but conceptually it can be used to request any parameter value. + + This function returns immediately, and the return value indicates + whether the host can fulfill the request. The host may notify the + plugin about any new parameter value, for example when a file is + selected by the user, via the usual mechanism. Typically, the host will + send a message (using the atom and path extensions) to the plugin that + sets the new parameter value, and the plugin will notify the UI via a + message as usual for any other parameter change. + + The host can determine details about the property, like the value type, + from the plugin data. + + @param handle The handle field of this struct. + @param key The URID of the parameter. + @return 0 on success, non-zero on error. + */ + LV2UI_Request_Parameter_Status (*request)(LV2UI_Feature_Handle handle, + LV2_URID key); +} LV2UI_Request_Parameter; + /** UI Idle Interface (LV2_UI__idleInterface) diff --git a/source/utils/CarlaLv2Utils.hpp b/source/utils/CarlaLv2Utils.hpp index 3e0e821609..950ea030be 100644 --- a/source/utils/CarlaLv2Utils.hpp +++ b/source/utils/CarlaLv2Utils.hpp @@ -3090,6 +3090,8 @@ bool is_lv2_ui_feature_supported(const LV2_URI uri) noexcept return true; if (std::strcmp(uri, LV2_UI__portSubscribe) == 0) return true; + if (std::strcmp(uri, LV2_UI__requestParameter) == 0) + return true; if (std::strcmp(uri, LV2_UI__resize) == 0) return true; if (std::strcmp(uri, LV2_UI__touch) == 0) diff --git a/source/utils/CarlaPipeUtils.cpp b/source/utils/CarlaPipeUtils.cpp index ff57924022..c81f4b580a 100644 --- a/source/utils/CarlaPipeUtils.cpp +++ b/source/utils/CarlaPipeUtils.cpp @@ -1622,7 +1622,7 @@ void CarlaPipeServer::stopPipeServer(const uint32_t timeOutMilliseconds) noexcep { const CarlaMutexLocker cml(pData->writeLock); - if (pData->pipeSend != INVALID_PIPE_VALUE) + if (pData->pipeSend != INVALID_PIPE_VALUE && ! pData->pipeClosed) { if (_writeMsgBuffer("__carla-quit__\n", 15)) flushMessages(); @@ -1640,7 +1640,7 @@ void CarlaPipeServer::stopPipeServer(const uint32_t timeOutMilliseconds) noexcep { const CarlaMutexLocker cml(pData->writeLock); - if (pData->pipeSend != INVALID_PIPE_VALUE) + if (pData->pipeSend != INVALID_PIPE_VALUE && ! pData->pipeClosed) { if (_writeMsgBuffer("__carla-quit__\n", 15)) flushMessages();