| @@ -1062,6 +1062,7 @@ protected: | |||
| friend class CarlaPluginInstance; | |||
| friend class EngineInternalGraph; | |||
| friend class ScopedActionLock; | |||
| friend class ScopedEngineEnvironmentLocker; | |||
| friend class PendingRtEventsRunner; | |||
| friend struct PatchbayGraph; | |||
| friend struct RackGraph; | |||
| @@ -1108,6 +1109,12 @@ protected: | |||
| */ | |||
| bool loadProjectInternal(juce::XmlDocument& xmlDoc); | |||
| /*! | |||
| * Lock/Unlock environment mutex, to prevent simultaneous changes from different threads. | |||
| */ | |||
| void lockEnvironment() const noexcept; | |||
| void unlockEnvironment() const noexcept; | |||
| #ifndef BUILD_BRIDGE | |||
| // ------------------------------------------------------------------- | |||
| // Patchbay stuff | |||
| @@ -1450,6 +1450,16 @@ EngineEvent* CarlaEngine::getInternalEventBuffer(const bool isInput) const noexc | |||
| return isInput ? pData->events.in : pData->events.out; | |||
| } | |||
| void CarlaEngine::lockEnvironment() const noexcept | |||
| { | |||
| pData->envMutex.lock(); | |||
| } | |||
| void CarlaEngine::unlockEnvironment() const noexcept | |||
| { | |||
| pData->envMutex.unlock(); | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| // Internal stuff | |||
| @@ -109,6 +109,7 @@ CarlaEngine::ProtectedData::ProtectedData(CarlaEngine* const engine) noexcept | |||
| curPluginCount(0), | |||
| maxPluginNumber(0), | |||
| nextPluginId(0), | |||
| envMutex(), | |||
| lastError(), | |||
| name(), | |||
| options(), | |||
| @@ -175,6 +175,7 @@ struct CarlaEngine::ProtectedData { | |||
| uint maxPluginNumber; // number of plugins allowed (0, 16, 99 or 255) | |||
| uint nextPluginId; // invalid if == maxPluginNumber | |||
| CarlaMutex envMutex; | |||
| CarlaString lastError; | |||
| CarlaString name; | |||
| EngineOptions options; | |||
| @@ -20,11 +20,11 @@ | |||
| #endif | |||
| #include "CarlaPluginInternal.hpp" | |||
| #include "CarlaEngine.hpp" | |||
| #include "CarlaBackendUtils.hpp" | |||
| #include "CarlaBase64Utils.hpp" | |||
| #include "CarlaBridgeUtils.hpp" | |||
| #include "CarlaEngineUtils.hpp" | |||
| #include "CarlaMathUtils.hpp" | |||
| #include "CarlaShmUtils.hpp" | |||
| #include "CarlaThread.hpp" | |||
| @@ -487,21 +487,22 @@ public: | |||
| fBinary(), | |||
| fLabel(), | |||
| fShmIds(), | |||
| fPluginType(PLUGIN_NONE), | |||
| fProcess(), | |||
| leakDetector_CarlaPluginBridgeThread() {} | |||
| void setData(const char* const binary, const char* const label, const char* const shmIds, const PluginType ptype) noexcept | |||
| void setData(const char* const binary, const char* const label, const char* const shmIds) noexcept | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(binary != nullptr && binary[0] != '\0',); | |||
| CARLA_SAFE_ASSERT_RETURN(label != nullptr && label[0] != '\0',); | |||
| CARLA_SAFE_ASSERT_RETURN(label != nullptr,); | |||
| CARLA_SAFE_ASSERT_RETURN(shmIds != nullptr && shmIds[0] != '\0',); | |||
| CARLA_SAFE_ASSERT(! isThreadRunning()); | |||
| fBinary = binary; | |||
| fLabel = binary; | |||
| fShmIds = shmIds; | |||
| fPluginType = ptype; | |||
| if (fLabel.isEmpty()) | |||
| fLabel = "\"\""; | |||
| } | |||
| protected: | |||
| @@ -529,32 +530,176 @@ protected: | |||
| #ifndef CARLA_OS_WIN | |||
| // start with "wine" if needed | |||
| if (fBinary.endsWith(".exe")) | |||
| if (fBinary.endsWithIgnoreCase(".exe")) | |||
| arguments.add("wine"); | |||
| #endif | |||
| // binary | |||
| arguments.add(fBinary.buffer()); | |||
| arguments.add(fBinary); | |||
| // plugin type | |||
| arguments.add(PluginType2Str(kPlugin->getType())); | |||
| // filename | |||
| arguments.add(filename); | |||
| // label | |||
| arguments.add(fLabel); | |||
| // uniqueId | |||
| arguments.add(String(static_cast<juce::int64>(kPlugin->getUniqueId()))); | |||
| bool started; | |||
| { | |||
| char strBuf[STR_MAX+1]; | |||
| strBuf[STR_MAX] = '\0'; | |||
| const EngineOptions& options(kEngine->getOptions()); | |||
| const ScopedEngineEnvironmentLocker _seel(kEngine); | |||
| #ifdef CARLA_OS_LINUX | |||
| const char* const oldPreload(std::getenv("LD_PRELOAD")); | |||
| if (oldPreload != nullptr) | |||
| ::unsetenv("LD_PRELOAD"); | |||
| #endif | |||
| carla_setenv("ENGINE_OPTION_FORCE_STEREO", bool2str(options.forceStereo)); | |||
| carla_setenv("ENGINE_OPTION_PREFER_PLUGIN_BRIDGES", bool2str(options.preferPluginBridges)); | |||
| carla_setenv("ENGINE_OPTION_PREFER_UI_BRIDGES", bool2str(options.preferUiBridges)); | |||
| carla_setenv("ENGINE_OPTION_UIS_ALWAYS_ON_TOP", bool2str(options.uisAlwaysOnTop)); | |||
| std::snprintf(strBuf, STR_MAX, "%u", options.maxParameters); | |||
| carla_setenv("ENGINE_OPTION_MAX_PARAMETERS", strBuf); | |||
| std::snprintf(strBuf, STR_MAX, "%u", options.uiBridgesTimeout); | |||
| carla_setenv("ENGINE_OPTION_UI_BRIDGES_TIMEOUT",strBuf); | |||
| if (options.pathLADSPA != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_LADSPA", options.pathLADSPA); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_LADSPA", ""); | |||
| if (options.pathDSSI != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_DSSI", options.pathDSSI); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_DSSI", ""); | |||
| if (options.pathLV2 != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_LV2", options.pathLV2); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_LV2", ""); | |||
| if (options.pathVST2 != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_VST2", options.pathVST2); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_VST2", ""); | |||
| if (options.pathVST3 != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_VST3", options.pathVST3); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_VST3", ""); | |||
| if (options.pathAU != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_AU", options.pathAU); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_AU", ""); | |||
| if (options.pathGIG != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_GIG", options.pathGIG); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_GIG", ""); | |||
| if (options.pathSF2 != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_SF2", options.pathSF2); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_SF2", ""); | |||
| if (options.pathSFZ != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_SFZ", options.pathSFZ); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_SFZ", ""); | |||
| if (options.binaryDir != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PATH_BINARIES", options.binaryDir); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PATH_BINARIES", ""); | |||
| if (options.resourceDir != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PATH_RESOURCES", options.resourceDir); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PATH_RESOURCES", ""); | |||
| carla_setenv("ENGINE_OPTION_PREVENT_BAD_BEHAVIOUR", bool2str(options.preventBadBehaviour)); | |||
| std::snprintf(strBuf, STR_MAX, P_UINTPTR, options.frontendWinId); | |||
| carla_setenv("ENGINE_OPTION_FRONTEND_WIN_ID", strBuf); | |||
| carla_setenv("ENGINE_BRIDGE_SHM_IDS", fShmIds.toRawUTF8()); | |||
| carla_setenv("WINEDEBUG", "-all"); | |||
| #if 0 | |||
| /* stype */ arguments.add(fExtra1.buffer()); | |||
| /* filename */ arguments.add(filename); | |||
| /* label */ arguments.add(fLabel.buffer()); | |||
| /* uniqueId */ arguments.add(String(static_cast<juce::int64>(fPlugin->getUniqueId()))); | |||
| carla_stdout("starting plugin bridge.."); | |||
| started = fProcess->start(arguments); | |||
| carla_setenv("ENGINE_BRIDGE_SHM_IDS", fShmIds.buffer()); | |||
| carla_setenv("WINEDEBUG", "-all"); | |||
| #ifdef CARLA_OS_LINUX | |||
| if (oldPreload != nullptr) | |||
| ::setenv("LD_PRELOAD", oldPreload, 1); | |||
| #endif | |||
| } | |||
| if (! started) | |||
| { | |||
| carla_stdout("failed!"); | |||
| fProcess = nullptr; | |||
| return; | |||
| } | |||
| for (; fProcess->isRunning() && ! shouldThreadExit();) | |||
| carla_sleep(1); | |||
| // we only get here if bridge crashed or thread asked to exit | |||
| if (fProcess->isRunning() && shouldThreadExit()) | |||
| { | |||
| fProcess->waitForProcessToFinish(2000); | |||
| if (fProcess->isRunning()) | |||
| { | |||
| carla_stdout("CarlaPluginBridgeThread::run() - bridge refused to close, force kill now"); | |||
| fProcess->kill(); | |||
| } | |||
| else | |||
| { | |||
| carla_stdout("CarlaPluginBridgeThread::run() - bridge auto-closed successfully"); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| // forced quit, may have crashed | |||
| if (fProcess->getExitCode() != 0 /*|| fProcess->exitStatus() == QProcess::CrashExit*/) | |||
| { | |||
| carla_stderr("CarlaPluginBridgeThread::run() - bridge crashed"); | |||
| CarlaString errorString("Plugin '" + CarlaString(kPlugin->getName()) + "' has crashed!\n" | |||
| "Saving now will lose its current settings.\n" | |||
| "Please remove this plugin, and not rely on it from this point."); | |||
| kEngine->callback(CarlaBackend::ENGINE_CALLBACK_ERROR, kPlugin->getId(), 0, 0, 0.0f, errorString); | |||
| } | |||
| else | |||
| carla_stderr("CarlaPluginBridgeThread::run() - bridge closed cleanly"); | |||
| } | |||
| carla_stdout("plugin bridge finished"); | |||
| fProcess = nullptr; | |||
| } | |||
| private: | |||
| CarlaEngine* const kEngine; | |||
| CarlaPlugin* const kPlugin; | |||
| CarlaString fBinary; | |||
| CarlaString fLabel; | |||
| CarlaString fShmIds; | |||
| PluginType fPluginType; | |||
| String fBinary; | |||
| String fLabel; | |||
| String fShmIds; | |||
| ScopedPointer<ChildProcess> fProcess; | |||
| @@ -720,14 +865,14 @@ public: | |||
| void getParameterName(const uint32_t parameterId, char* const strBuf) const noexcept override | |||
| { | |||
| CARLA_ASSERT(parameterId < pData->param.count); | |||
| CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, nullStrBuf(strBuf)); | |||
| std::strncpy(strBuf, fParams[parameterId].name.buffer(), STR_MAX); | |||
| } | |||
| void getParameterUnit(const uint32_t parameterId, char* const strBuf) const noexcept override | |||
| { | |||
| CARLA_ASSERT(parameterId < pData->param.count); | |||
| CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, nullStrBuf(strBuf)); | |||
| std::strncpy(strBuf, fParams[parameterId].unit.buffer(), STR_MAX); | |||
| } | |||
| @@ -886,28 +1031,44 @@ public: | |||
| CarlaPlugin::setMidiProgram(index, sendGui, sendOsc, sendCallback); | |||
| } | |||
| #if 0 | |||
| void setCustomData(const char* const type, const char* const key, const char* const value, const bool sendGui) override | |||
| { | |||
| CARLA_ASSERT(type); | |||
| CARLA_ASSERT(key); | |||
| CARLA_ASSERT(value); | |||
| CARLA_SAFE_ASSERT_RETURN(type != nullptr && type[0] != '\0',); | |||
| CARLA_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',); | |||
| CARLA_SAFE_ASSERT_RETURN(value != nullptr,); | |||
| using namespace juce; | |||
| const uint32_t typeLen(static_cast<uint32_t>(std::strlen(type))); | |||
| const uint32_t keyLen(static_cast<uint32_t>(std::strlen(key))); | |||
| MemoryOutputStream valueMemStream; | |||
| GZIPCompressorOutputStream compressedValueStream(&valueMemStream, 9, false); | |||
| compressedValueStream.write(value, std::strlen(value)); | |||
| const CarlaString valueBase64(CarlaString::asBase64(valueMemStream.getData(), valueMemStream.getDataSize())); | |||
| const uint32_t valueBase64Len(static_cast<uint32_t>(valueBase64.length())); | |||
| CARLA_SAFE_ASSERT_RETURN(valueBase64.length() > 0,); | |||
| if (sendGui) | |||
| { | |||
| // TODO - if type is chunk|binary, store it in a file and send path instead | |||
| QString cData; | |||
| cData = type; | |||
| cData += "·"; | |||
| cData += key; | |||
| cData += "·"; | |||
| cData += value; | |||
| osc_send_configure(&osc.data, CARLA_BRIDGE_MSG_SET_CUSTOM, cData.toUtf8().constData()); | |||
| const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); | |||
| fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientSetCustomData); | |||
| fShmNonRtClientControl.writeUInt(typeLen); | |||
| fShmNonRtClientControl.writeCustomData(type, typeLen); | |||
| fShmNonRtClientControl.writeUInt(keyLen); | |||
| fShmNonRtClientControl.writeCustomData(key, keyLen); | |||
| fShmNonRtClientControl.writeUInt(valueBase64Len); | |||
| fShmNonRtClientControl.writeCustomData(valueBase64.buffer(), valueBase64Len); | |||
| fShmNonRtClientControl.commitWrite(); | |||
| } | |||
| CarlaPlugin::setCustomData(type, key, value, sendGui); | |||
| } | |||
| #endif | |||
| void setChunkData(const void* const data, const std::size_t dataSize) override | |||
| { | |||
| @@ -915,13 +1076,12 @@ public: | |||
| CARLA_SAFE_ASSERT_RETURN(data != nullptr,); | |||
| CARLA_SAFE_ASSERT_RETURN(dataSize > 0,); | |||
| CarlaString dataBase64 = CarlaString::asBase64(data, dataSize); | |||
| CarlaString dataBase64(CarlaString::asBase64(data, dataSize)); | |||
| CARLA_SAFE_ASSERT_RETURN(dataBase64.length() > 0,); | |||
| String filePath(File::getSpecialLocation(File::tempDirectory).getFullPathName()); | |||
| filePath += CARLA_OS_SEP_STR; | |||
| filePath += ".CarlaChunk_"; | |||
| filePath += CARLA_OS_SEP_STR ".CarlaChunk_"; | |||
| filePath += fShmAudioPool.filename.buffer() + 18; | |||
| if (File(filePath).replaceWithText(dataBase64.buffer())) | |||
| @@ -1047,6 +1207,7 @@ public: | |||
| } | |||
| else | |||
| portName += "input"; | |||
| portName.truncate(portNameSize); | |||
| pData->audioIn.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, true); | |||
| @@ -1071,6 +1232,7 @@ public: | |||
| } | |||
| else | |||
| portName += "output"; | |||
| portName.truncate(portNameSize); | |||
| pData->audioOut.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false); | |||
| @@ -1996,6 +2158,8 @@ public: | |||
| case kPluginBridgeNonRtServerSetCustomData: { | |||
| // uint/size, str[], uint/size, str[], uint/size, str[] (compressed) | |||
| using namespace juce; | |||
| // type | |||
| const uint32_t typeSize(fShmNonRtServerControl.readUInt()); | |||
| char type[typeSize+1]; | |||
| @@ -2009,12 +2173,17 @@ public: | |||
| fShmNonRtServerControl.readCustomData(key, keySize); | |||
| // value | |||
| const uint32_t valueSize(fShmNonRtServerControl.readUInt()); | |||
| char value[valueSize+1]; | |||
| carla_zeroChar(value, valueSize+1); | |||
| fShmNonRtServerControl.readCustomData(value, valueSize); | |||
| const uint32_t valueBase64Size(fShmNonRtServerControl.readUInt()); | |||
| char valueBase64[valueBase64Size+1]; | |||
| carla_zeroChar(valueBase64, valueBase64Size+1); | |||
| fShmNonRtServerControl.readCustomData(valueBase64, valueBase64Size); | |||
| const std::vector<uint8_t> valueChunk(carla_getChunkFromBase64String(valueBase64)); | |||
| MemoryInputStream valueMemStream(valueChunk.data(), valueChunk.size(), false); | |||
| GZIPDecompressorInputStream decompressedValueStream(valueMemStream); | |||
| CarlaPlugin::setCustomData(type, key, value, false); | |||
| CarlaPlugin::setCustomData(type, key, decompressedValueStream.readEntireStreamAsString().toRawUTF8(), false); | |||
| } break; | |||
| case kPluginBridgeNonRtServerSetChunkDataFile: { | |||
| @@ -2192,7 +2361,7 @@ public: | |||
| std::strncpy(shmIdsStr+6*2, &fShmNonRtClientControl.filename[fShmNonRtClientControl.filename.length()-6], 6); | |||
| std::strncpy(shmIdsStr+6*3, &fShmNonRtServerControl.filename[fShmNonRtServerControl.filename.length()-6], 6); | |||
| fBridgeThread.setData(bridgeBinary, label, shmIdsStr, fPluginType); | |||
| fBridgeThread.setData(bridgeBinary, label, shmIdsStr); | |||
| fBridgeThread.startThread(); | |||
| } | |||
| @@ -19,7 +19,7 @@ | |||
| // TODO: common laterncy code | |||
| #include "CarlaPluginInternal.hpp" | |||
| #include "CarlaEngine.hpp" | |||
| #include "CarlaEngineUtils.hpp" | |||
| #include "CarlaDssiUtils.hpp" | |||
| #include "CarlaMathUtils.hpp" | |||
| @@ -111,14 +111,6 @@ public: | |||
| return; | |||
| } | |||
| #if 0 //def CARLA_OS_LINUX | |||
| const char* const oldPreload(std::getenv("LD_PRELOAD")); | |||
| ::unsetenv("LD_PRELOAD"); | |||
| if (oldPreload != nullptr) | |||
| ::setenv("LD_PRELOAD", oldPreload, 1); | |||
| #endif | |||
| String name(kPlugin->getName()); | |||
| String filename(kPlugin->getFilename()); | |||
| @@ -145,9 +137,28 @@ public: | |||
| // ui-title | |||
| arguments.add(name + String(" (GUI)")); | |||
| carla_stdout("starting DSSI UI..."); | |||
| bool started; | |||
| { | |||
| #ifdef CARLA_OS_LINUX | |||
| const ScopedEngineEnvironmentLocker _seel(kEngine); | |||
| const char* const oldPreload(std::getenv("LD_PRELOAD")); | |||
| if (oldPreload != nullptr) | |||
| ::unsetenv("LD_PRELOAD"); | |||
| #endif | |||
| carla_stdout("starting DSSI UI..."); | |||
| started = fProcess->start(arguments); | |||
| #ifdef CARLA_OS_LINUX | |||
| if (oldPreload != nullptr) | |||
| ::setenv("LD_PRELOAD", oldPreload, 1); | |||
| #endif | |||
| } | |||
| if (! fProcess->start(arguments)) | |||
| if (! started) | |||
| { | |||
| carla_stdout("failed!"); | |||
| fProcess = nullptr; | |||
| @@ -16,373 +16,8 @@ | |||
| */ | |||
| #if 0 | |||
| #include "CarlaPlugin.hpp" | |||
| #include "CarlaPluginThread.hpp" | |||
| #include "CarlaEngine.hpp" | |||
| #include "juce_core.h" | |||
| using juce::String; | |||
| using juce::StringArray; | |||
| CARLA_BACKEND_START_NAMESPACE | |||
| // ----------------------------------------------------------------------- | |||
| #ifdef DEBUG | |||
| static inline | |||
| const char* PluginThreadMode2str(const CarlaPluginThread::Mode mode) noexcept | |||
| { | |||
| switch (mode) | |||
| { | |||
| case CarlaPluginThread::PLUGIN_THREAD_NULL: | |||
| return "PLUGIN_THREAD_NULL"; | |||
| case CarlaPluginThread::PLUGIN_THREAD_DSSI_GUI: | |||
| return "PLUGIN_THREAD_DSSI_GUI"; | |||
| case CarlaPluginThread::PLUGIN_THREAD_LV2_GUI: | |||
| return "PLUGIN_THREAD_LV2_GUI"; | |||
| case CarlaPluginThread::PLUGIN_THREAD_VST2_GUI: | |||
| return "PLUGIN_THREAD_VST2_GUI"; | |||
| case CarlaPluginThread::PLUGIN_THREAD_BRIDGE: | |||
| return "PLUGIN_THREAD_BRIDGE"; | |||
| } | |||
| carla_stderr("CarlaPluginThread::PluginThreadMode2str(%i) - invalid mode", mode); | |||
| return nullptr; | |||
| } | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| CarlaPluginThread::CarlaPluginThread(CarlaBackend::CarlaEngine* const engine, CarlaBackend::CarlaPlugin* const plugin, const Mode mode) noexcept | |||
| : CarlaThread("CarlaPluginThread"), | |||
| fEngine(engine), | |||
| fPlugin(plugin), | |||
| fMode(mode), | |||
| fBinary(), | |||
| fLabel(), | |||
| fExtra1(), | |||
| fExtra2(), | |||
| fProcess(nullptr), | |||
| leakDetector_CarlaPluginThread() | |||
| { | |||
| carla_debug("CarlaPluginThread::CarlaPluginThread(%p, %p, %s)", engine, plugin, PluginThreadMode2str(mode)); | |||
| } | |||
| CarlaPluginThread::~CarlaPluginThread() noexcept | |||
| { | |||
| carla_debug("CarlaPluginThread::~CarlaPluginThread()"); | |||
| if (fProcess != nullptr) | |||
| { | |||
| //fProcess.release(); | |||
| //try { | |||
| //delete fProcess; | |||
| //} CARLA_SAFE_EXCEPTION("~CarlaPluginThread(): delete ChildProcess"); | |||
| fProcess = nullptr; | |||
| } | |||
| } | |||
| void CarlaPluginThread::setMode(const CarlaPluginThread::Mode mode) noexcept | |||
| { | |||
| CARLA_SAFE_ASSERT(! isThreadRunning()); | |||
| carla_debug("CarlaPluginThread::setMode(%s)", PluginThreadMode2str(mode)); | |||
| fMode = mode; | |||
| } | |||
| void CarlaPluginThread::setOscData(const char* const binary, const char* const label, const char* const extra1, const char* const extra2) noexcept | |||
| { | |||
| CARLA_SAFE_ASSERT(! isThreadRunning()); | |||
| carla_debug("CarlaPluginThread::setOscData(\"%s\", \"%s\", \"%s\", \"%s\")", binary, label, extra1, extra2); | |||
| fBinary = binary; | |||
| fLabel = label; | |||
| fExtra1 = extra1; | |||
| fExtra2 = extra2; | |||
| } | |||
| uintptr_t CarlaPluginThread::getPid() const | |||
| { | |||
| CARLA_SAFE_ASSERT_RETURN(fProcess != nullptr, 0); | |||
| return (uintptr_t)fProcess->getPID(); | |||
| } | |||
| void CarlaPluginThread::run() | |||
| { | |||
| carla_debug("CarlaPluginThread::run()"); | |||
| if (fProcess == nullptr) | |||
| { | |||
| fProcess = new ChildProcess; | |||
| //fProcess->setProcessChannelMode(QProcess::ForwardedChannels); | |||
| } | |||
| else if (fProcess->isRunning()) | |||
| { | |||
| carla_stderr("CarlaPluginThread::run() - already running, giving up..."); | |||
| switch (fMode) | |||
| { | |||
| case PLUGIN_THREAD_NULL: | |||
| case PLUGIN_THREAD_BRIDGE: | |||
| break; | |||
| case PLUGIN_THREAD_DSSI_GUI: | |||
| case PLUGIN_THREAD_LV2_GUI: | |||
| case PLUGIN_THREAD_VST2_GUI: | |||
| fEngine->callback(CarlaBackend::ENGINE_CALLBACK_UI_STATE_CHANGED, fPlugin->getId(), 0, 0, 0.0f, nullptr); | |||
| fProcess->kill(); | |||
| fProcess = nullptr; | |||
| return; | |||
| } | |||
| } | |||
| String name(fPlugin->getName()); | |||
| String filename(fPlugin->getFilename()); | |||
| if (name.isEmpty()) | |||
| name = "(none)"; | |||
| if (filename.isEmpty()) | |||
| filename = "\"\""; | |||
| if (fLabel.isEmpty()) | |||
| fLabel = "\"\""; | |||
| StringArray arguments; | |||
| #ifndef CARLA_OS_WIN | |||
| if (fBinary.endsWith(".exe")) | |||
| arguments.add("wine"); | |||
| #endif | |||
| arguments.add(fBinary.buffer()); | |||
| // use a global mutex to ensure bridge environment is correct | |||
| static CarlaMutex sEnvMutex; | |||
| char strBuf[STR_MAX+1]; | |||
| strBuf[STR_MAX] = '\0'; | |||
| const EngineOptions& options(fEngine->getOptions()); | |||
| sEnvMutex.lock(); | |||
| carla_setenv("CARLA_CLIENT_NAME", name.toRawUTF8()); | |||
| std::snprintf(strBuf, STR_MAX, "%f", fEngine->getSampleRate()); | |||
| carla_setenv("CARLA_SAMPLE_RATE", strBuf); | |||
| carla_setenv("ENGINE_OPTION_FORCE_STEREO", bool2str(options.forceStereo)); | |||
| carla_setenv("ENGINE_OPTION_PREFER_PLUGIN_BRIDGES", bool2str(options.preferPluginBridges)); | |||
| carla_setenv("ENGINE_OPTION_PREFER_UI_BRIDGES", bool2str(options.preferUiBridges)); | |||
| carla_setenv("ENGINE_OPTION_UIS_ALWAYS_ON_TOP", bool2str(options.uisAlwaysOnTop)); | |||
| std::snprintf(strBuf, STR_MAX, "%u", options.maxParameters); | |||
| carla_setenv("ENGINE_OPTION_MAX_PARAMETERS", strBuf); | |||
| std::snprintf(strBuf, STR_MAX, "%u", options.uiBridgesTimeout); | |||
| carla_setenv("ENGINE_OPTION_UI_BRIDGES_TIMEOUT",strBuf); | |||
| if (options.pathLADSPA != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_LADSPA", options.pathLADSPA); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_LADSPA", ""); | |||
| if (options.pathDSSI != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_DSSI", options.pathDSSI); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_DSSI", ""); | |||
| if (options.pathLV2 != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_LV2", options.pathLV2); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_LV2", ""); | |||
| if (options.pathVST2 != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_VST2", options.pathVST2); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_VST2", ""); | |||
| if (options.pathVST3 != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_VST3", options.pathVST3); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_VST3", ""); | |||
| if (options.pathAU != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_AU", options.pathAU); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_AU", ""); | |||
| if (options.pathGIG != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_GIG", options.pathGIG); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_GIG", ""); | |||
| if (options.pathSF2 != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_SF2", options.pathSF2); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_SF2", ""); | |||
| if (options.pathSFZ != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_SFZ", options.pathSFZ); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PLUGIN_PATH_SFZ", ""); | |||
| if (options.binaryDir != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PATH_BINARIES", options.binaryDir); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PATH_BINARIES", ""); | |||
| if (options.resourceDir != nullptr) | |||
| carla_setenv("ENGINE_OPTION_PATH_RESOURCES", options.resourceDir); | |||
| else | |||
| carla_setenv("ENGINE_OPTION_PATH_RESOURCES", ""); | |||
| carla_setenv("ENGINE_OPTION_PREVENT_BAD_BEHAVIOUR", bool2str(options.preventBadBehaviour)); | |||
| std::snprintf(strBuf, STR_MAX, P_UINTPTR, options.frontendWinId); | |||
| carla_setenv("ENGINE_OPTION_FRONTEND_WIN_ID", strBuf); | |||
| #ifdef CARLA_OS_LINUX | |||
| const char* const oldPreload(std::getenv("LD_PRELOAD")); | |||
| ::unsetenv("LD_PRELOAD"); | |||
| #endif | |||
| switch (fMode) | |||
| { | |||
| case PLUGIN_THREAD_NULL: | |||
| break; | |||
| case PLUGIN_THREAD_DSSI_GUI: | |||
| /* osc-url */ arguments.add(String(fEngine->getOscServerPathUDP()) + String("/") + String(fPlugin->getId())); | |||
| /* filename */ arguments.add(filename); | |||
| /* label */ arguments.add(fLabel.buffer()); | |||
| /* ui-title */ arguments.add(name + String(" (GUI)")); | |||
| break; | |||
| case PLUGIN_THREAD_LV2_GUI: | |||
| /* osc-url */ arguments.add(String(fEngine->getOscServerPathUDP()) + String("/") + String(fPlugin->getId())); | |||
| /* URI */ arguments.add(fLabel.buffer()); | |||
| /* UI URI */ arguments.add(fExtra1.buffer()); | |||
| /* UI Bundle */ arguments.add(fExtra2.buffer()); | |||
| /* UI Title */ arguments.add(name + String(" (GUI)")); | |||
| break; | |||
| case PLUGIN_THREAD_VST2_GUI: | |||
| /* osc-url */ arguments.add(String(fEngine->getOscServerPathUDP()) + String("/") + String(fPlugin->getId())); | |||
| /* filename */ arguments.add(filename); | |||
| /* ui-title */ arguments.add(name + String(" (GUI)")); | |||
| break; | |||
| case PLUGIN_THREAD_BRIDGE: | |||
| /* stype */ arguments.add(fExtra1.buffer()); | |||
| /* filename */ arguments.add(filename); | |||
| /* label */ arguments.add(fLabel.buffer()); | |||
| /* uniqueId */ arguments.add(String(static_cast<juce::int64>(fPlugin->getUniqueId()))); | |||
| carla_setenv("ENGINE_BRIDGE_OSC_URL", String(String(fEngine->getOscServerPathUDP()) + String("/") + String(fPlugin->getId())).toRawUTF8()); | |||
| carla_setenv("ENGINE_BRIDGE_SHM_IDS", fExtra2.buffer()); | |||
| carla_setenv("WINEDEBUG", "-all"); | |||
| break; | |||
| } | |||
| carla_stdout("starting app.."); | |||
| fProcess->start(arguments); | |||
| #ifdef CARLA_OS_LINUX | |||
| if (oldPreload != nullptr) | |||
| ::setenv("LD_PRELOAD", oldPreload, 1); | |||
| #endif | |||
| sEnvMutex.unlock(); | |||
| switch (fMode) | |||
| { | |||
| case PLUGIN_THREAD_NULL: | |||
| break; | |||
| case PLUGIN_THREAD_DSSI_GUI: | |||
| case PLUGIN_THREAD_LV2_GUI: | |||
| case PLUGIN_THREAD_VST2_GUI: | |||
| if (fPlugin->waitForOscGuiShow()) | |||
| { | |||
| while (fProcess->isRunning() && ! shouldThreadExit()) | |||
| carla_sleep(1); | |||
| // we only get here if UI was closed or thread asked to exit | |||
| if (fProcess->isRunning() && shouldThreadExit()) | |||
| { | |||
| fProcess->waitForProcessToFinish(static_cast<int>(fEngine->getOptions().uiBridgesTimeout)); | |||
| if (fProcess->isRunning()) | |||
| { | |||
| carla_stdout("CarlaPluginThread::run() - UI refused to close, force kill now"); | |||
| fProcess->kill(); | |||
| } | |||
| else | |||
| { | |||
| carla_stdout("CarlaPluginThread::run() - UI auto-closed successfully"); | |||
| } | |||
| } | |||
| else if (fProcess->getExitCode() != 0 /*|| fProcess->exitStatus() == QProcess::CrashExit*/) | |||
| carla_stderr("CarlaPluginThread::run() - UI crashed while running"); | |||
| else | |||
| carla_stdout("CarlaPluginThread::run() - UI closed cleanly"); | |||
| fEngine->callback(CarlaBackend::ENGINE_CALLBACK_UI_STATE_CHANGED, fPlugin->getId(), 0, 0, 0.0f, nullptr); | |||
| } | |||
| else | |||
| { | |||
| fProcess->kill(); | |||
| carla_stdout("CarlaPluginThread::run() - GUI timeout"); | |||
| fEngine->callback(CarlaBackend::ENGINE_CALLBACK_UI_STATE_CHANGED, fPlugin->getId(), 0, 0, 0.0f, nullptr); | |||
| } | |||
| break; | |||
| case PLUGIN_THREAD_BRIDGE: | |||
| //fProcess->waitForFinished(-1); | |||
| while (fProcess->isRunning() && ! shouldThreadExit()) | |||
| carla_sleep(1); | |||
| // we only get here if bridge crashed or thread asked to exit | |||
| if (fProcess->isRunning() && shouldThreadExit()) | |||
| { | |||
| fProcess->waitForProcessToFinish(2000); | |||
| if (fProcess->isRunning()) | |||
| { | |||
| carla_stdout("CarlaPluginThread::run() - bridge refused to close, force kill now"); | |||
| fProcess->kill(); | |||
| } | |||
| else | |||
| { | |||
| carla_stdout("CarlaPluginThread::run() - bridge auto-closed successfully"); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| // forced quit, may have crashed | |||
| if (fProcess->getExitCode() != 0 /*|| fProcess->exitStatus() == QProcess::CrashExit*/) | |||
| { | |||
| carla_stderr("CarlaPluginThread::run() - bridge crashed"); | |||
| CarlaString errorString("Plugin '" + CarlaString(fPlugin->getName()) + "' has crashed!\n" | |||
| "Saving now will lose its current settings.\n" | |||
| "Please remove this plugin, and not rely on it from this point."); | |||
| fEngine->callback(CarlaBackend::ENGINE_CALLBACK_ERROR, fPlugin->getId(), 0, 0, 0.0f, errorString); | |||
| } | |||
| } | |||
| break; | |||
| } | |||
| carla_stdout("app finished"); | |||
| fProcess = nullptr; | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| @@ -56,8 +56,8 @@ enum PluginBridgeNonRtClientOpcode { | |||
| kPluginBridgeNonRtClientSetParameterMidiCC, // uint, short | |||
| kPluginBridgeNonRtClientSetProgram, // int | |||
| kPluginBridgeNonRtClientSetMidiProgram, // int | |||
| kPluginBridgeNonRtClientSetCustomData, // uint/size, str[], uint/size, str[], uint/size, str[] (compressed) | |||
| kPluginBridgeNonRtClientSetChunkDataFile, // uint/size, str[] (filename) | |||
| kPluginBridgeNonRtClientSetCustomData, // uint/size, str[], uint/size, str[], uint/size, str[] (base64, compressed) | |||
| kPluginBridgeNonRtClientSetChunkDataFile, // uint/size, str[] (filename, base64 content) | |||
| kPluginBridgeNonRtClientSetCtrlChannel, // short | |||
| kPluginBridgeNonRtClientSetOption, // uint/option, bool | |||
| kPluginBridgeNonRtClientPrepareForSave, | |||
| @@ -92,8 +92,8 @@ enum PluginBridgeNonRtServerOpcode { | |||
| kPluginBridgeNonRtServerCurrentMidiProgram, // int/index | |||
| kPluginBridgeNonRtServerProgramName, // uint/index, uint/size, str[] (name) | |||
| kPluginBridgeNonRtServerMidiProgramData, // uint/index, uint/bank, uint/program, uint/size, str[] (name) | |||
| kPluginBridgeNonRtServerSetCustomData, // uint/size, str[], uint/size, str[], uint/size, str[] (compressed) | |||
| kPluginBridgeNonRtServerSetChunkDataFile, // uint/size, str[] (filename) | |||
| kPluginBridgeNonRtServerSetCustomData, // uint/size, str[], uint/size, str[], uint/size, str[] (base64, compressed) | |||
| kPluginBridgeNonRtServerSetChunkDataFile, // uint/size, str[] (filename, base64 content) | |||
| kPluginBridgeNonRtServerSetLatency, // uint | |||
| kPluginBridgeNonRtServerReady, | |||
| kPluginBridgeNonRtServerSaved, | |||
| @@ -194,6 +194,30 @@ void fillJuceMidiBufferFromEngineEvents(juce::MidiBuffer& midiBuffer, const Engi | |||
| } | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // Helper classes | |||
| class ScopedEngineEnvironmentLocker | |||
| { | |||
| public: | |||
| ScopedEngineEnvironmentLocker(CarlaEngine* const engine) noexcept | |||
| : kEngine(engine) | |||
| { | |||
| kEngine->lockEnvironment(); | |||
| } | |||
| ~ScopedEngineEnvironmentLocker() noexcept | |||
| { | |||
| kEngine->unlockEnvironment(); | |||
| } | |||
| private: | |||
| CarlaEngine* const kEngine; | |||
| CARLA_PREVENT_HEAP_ALLOCATION | |||
| CARLA_DECLARE_NON_COPY_CLASS(ScopedEngineEnvironmentLocker) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| CARLA_BACKEND_END_NAMESPACE | |||
| @@ -252,7 +252,7 @@ shm_t carla_shm_create_temp(char* const fileBase) noexcept | |||
| const std::size_t fileBaseLen(std::strlen(fileBase)); | |||
| CARLA_SAFE_ASSERT_RETURN(fileBaseLen > 6, gNullCarlaShm); | |||
| CARLA_SAFE_ASSERT_RETURN(std::strcmp(fileBase + fileBaseLen - 6, "XXXXXX") == 0, gNullCarlaShm); | |||
| CARLA_SAFE_ASSERT_RETURN(std::strcmp(fileBase + (fileBaseLen - 6), "XXXXXX") == 0, gNullCarlaShm); | |||
| // character set to use randomly | |||
| static const char charSet[] = "abcdefghijklmnopqrstuvwxyz" | |||