@@ -1062,6 +1062,7 @@ protected: | |||||
friend class CarlaPluginInstance; | friend class CarlaPluginInstance; | ||||
friend class EngineInternalGraph; | friend class EngineInternalGraph; | ||||
friend class ScopedActionLock; | friend class ScopedActionLock; | ||||
friend class ScopedEngineEnvironmentLocker; | |||||
friend class PendingRtEventsRunner; | friend class PendingRtEventsRunner; | ||||
friend struct PatchbayGraph; | friend struct PatchbayGraph; | ||||
friend struct RackGraph; | friend struct RackGraph; | ||||
@@ -1108,6 +1109,12 @@ protected: | |||||
*/ | */ | ||||
bool loadProjectInternal(juce::XmlDocument& xmlDoc); | 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 | #ifndef BUILD_BRIDGE | ||||
// ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
// Patchbay stuff | // Patchbay stuff | ||||
@@ -1450,6 +1450,16 @@ EngineEvent* CarlaEngine::getInternalEventBuffer(const bool isInput) const noexc | |||||
return isInput ? pData->events.in : pData->events.out; | 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 | // Internal stuff | ||||
@@ -109,6 +109,7 @@ CarlaEngine::ProtectedData::ProtectedData(CarlaEngine* const engine) noexcept | |||||
curPluginCount(0), | curPluginCount(0), | ||||
maxPluginNumber(0), | maxPluginNumber(0), | ||||
nextPluginId(0), | nextPluginId(0), | ||||
envMutex(), | |||||
lastError(), | lastError(), | ||||
name(), | name(), | ||||
options(), | options(), | ||||
@@ -175,6 +175,7 @@ struct CarlaEngine::ProtectedData { | |||||
uint maxPluginNumber; // number of plugins allowed (0, 16, 99 or 255) | uint maxPluginNumber; // number of plugins allowed (0, 16, 99 or 255) | ||||
uint nextPluginId; // invalid if == maxPluginNumber | uint nextPluginId; // invalid if == maxPluginNumber | ||||
CarlaMutex envMutex; | |||||
CarlaString lastError; | CarlaString lastError; | ||||
CarlaString name; | CarlaString name; | ||||
EngineOptions options; | EngineOptions options; | ||||
@@ -20,11 +20,11 @@ | |||||
#endif | #endif | ||||
#include "CarlaPluginInternal.hpp" | #include "CarlaPluginInternal.hpp" | ||||
#include "CarlaEngine.hpp" | |||||
#include "CarlaBackendUtils.hpp" | #include "CarlaBackendUtils.hpp" | ||||
#include "CarlaBase64Utils.hpp" | #include "CarlaBase64Utils.hpp" | ||||
#include "CarlaBridgeUtils.hpp" | #include "CarlaBridgeUtils.hpp" | ||||
#include "CarlaEngineUtils.hpp" | |||||
#include "CarlaMathUtils.hpp" | #include "CarlaMathUtils.hpp" | ||||
#include "CarlaShmUtils.hpp" | #include "CarlaShmUtils.hpp" | ||||
#include "CarlaThread.hpp" | #include "CarlaThread.hpp" | ||||
@@ -487,21 +487,22 @@ public: | |||||
fBinary(), | fBinary(), | ||||
fLabel(), | fLabel(), | ||||
fShmIds(), | fShmIds(), | ||||
fPluginType(PLUGIN_NONE), | |||||
fProcess(), | fProcess(), | ||||
leakDetector_CarlaPluginBridgeThread() {} | 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(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_RETURN(shmIds != nullptr && shmIds[0] != '\0',); | ||||
CARLA_SAFE_ASSERT(! isThreadRunning()); | CARLA_SAFE_ASSERT(! isThreadRunning()); | ||||
fBinary = binary; | fBinary = binary; | ||||
fLabel = binary; | fLabel = binary; | ||||
fShmIds = shmIds; | fShmIds = shmIds; | ||||
fPluginType = ptype; | |||||
if (fLabel.isEmpty()) | |||||
fLabel = "\"\""; | |||||
} | } | ||||
protected: | protected: | ||||
@@ -529,32 +530,176 @@ protected: | |||||
#ifndef CARLA_OS_WIN | #ifndef CARLA_OS_WIN | ||||
// start with "wine" if needed | // start with "wine" if needed | ||||
if (fBinary.endsWith(".exe")) | |||||
if (fBinary.endsWithIgnoreCase(".exe")) | |||||
arguments.add("wine"); | arguments.add("wine"); | ||||
#endif | #endif | ||||
// binary | // 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 | #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: | private: | ||||
CarlaEngine* const kEngine; | CarlaEngine* const kEngine; | ||||
CarlaPlugin* const kPlugin; | CarlaPlugin* const kPlugin; | ||||
CarlaString fBinary; | |||||
CarlaString fLabel; | |||||
CarlaString fShmIds; | |||||
PluginType fPluginType; | |||||
String fBinary; | |||||
String fLabel; | |||||
String fShmIds; | |||||
ScopedPointer<ChildProcess> fProcess; | ScopedPointer<ChildProcess> fProcess; | ||||
@@ -720,14 +865,14 @@ public: | |||||
void getParameterName(const uint32_t parameterId, char* const strBuf) const noexcept override | 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); | std::strncpy(strBuf, fParams[parameterId].name.buffer(), STR_MAX); | ||||
} | } | ||||
void getParameterUnit(const uint32_t parameterId, char* const strBuf) const noexcept override | 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); | std::strncpy(strBuf, fParams[parameterId].unit.buffer(), STR_MAX); | ||||
} | } | ||||
@@ -886,28 +1031,44 @@ public: | |||||
CarlaPlugin::setMidiProgram(index, sendGui, sendOsc, sendCallback); | 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 | 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); | CarlaPlugin::setCustomData(type, key, value, sendGui); | ||||
} | } | ||||
#endif | |||||
void setChunkData(const void* const data, const std::size_t dataSize) override | 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(data != nullptr,); | ||||
CARLA_SAFE_ASSERT_RETURN(dataSize > 0,); | 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,); | CARLA_SAFE_ASSERT_RETURN(dataBase64.length() > 0,); | ||||
String filePath(File::getSpecialLocation(File::tempDirectory).getFullPathName()); | 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; | filePath += fShmAudioPool.filename.buffer() + 18; | ||||
if (File(filePath).replaceWithText(dataBase64.buffer())) | if (File(filePath).replaceWithText(dataBase64.buffer())) | ||||
@@ -1047,6 +1207,7 @@ public: | |||||
} | } | ||||
else | else | ||||
portName += "input"; | portName += "input"; | ||||
portName.truncate(portNameSize); | portName.truncate(portNameSize); | ||||
pData->audioIn.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, true); | pData->audioIn.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, true); | ||||
@@ -1071,6 +1232,7 @@ public: | |||||
} | } | ||||
else | else | ||||
portName += "output"; | portName += "output"; | ||||
portName.truncate(portNameSize); | portName.truncate(portNameSize); | ||||
pData->audioOut.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false); | pData->audioOut.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false); | ||||
@@ -1996,6 +2158,8 @@ public: | |||||
case kPluginBridgeNonRtServerSetCustomData: { | case kPluginBridgeNonRtServerSetCustomData: { | ||||
// uint/size, str[], uint/size, str[], uint/size, str[] (compressed) | // uint/size, str[], uint/size, str[], uint/size, str[] (compressed) | ||||
using namespace juce; | |||||
// type | // type | ||||
const uint32_t typeSize(fShmNonRtServerControl.readUInt()); | const uint32_t typeSize(fShmNonRtServerControl.readUInt()); | ||||
char type[typeSize+1]; | char type[typeSize+1]; | ||||
@@ -2009,12 +2173,17 @@ public: | |||||
fShmNonRtServerControl.readCustomData(key, keySize); | fShmNonRtServerControl.readCustomData(key, keySize); | ||||
// value | // 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; | } break; | ||||
case kPluginBridgeNonRtServerSetChunkDataFile: { | case kPluginBridgeNonRtServerSetChunkDataFile: { | ||||
@@ -2192,7 +2361,7 @@ public: | |||||
std::strncpy(shmIdsStr+6*2, &fShmNonRtClientControl.filename[fShmNonRtClientControl.filename.length()-6], 6); | std::strncpy(shmIdsStr+6*2, &fShmNonRtClientControl.filename[fShmNonRtClientControl.filename.length()-6], 6); | ||||
std::strncpy(shmIdsStr+6*3, &fShmNonRtServerControl.filename[fShmNonRtServerControl.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(); | fBridgeThread.startThread(); | ||||
} | } | ||||
@@ -19,7 +19,7 @@ | |||||
// TODO: common laterncy code | // TODO: common laterncy code | ||||
#include "CarlaPluginInternal.hpp" | #include "CarlaPluginInternal.hpp" | ||||
#include "CarlaEngine.hpp" | |||||
#include "CarlaEngineUtils.hpp" | |||||
#include "CarlaDssiUtils.hpp" | #include "CarlaDssiUtils.hpp" | ||||
#include "CarlaMathUtils.hpp" | #include "CarlaMathUtils.hpp" | ||||
@@ -111,14 +111,6 @@ public: | |||||
return; | 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 name(kPlugin->getName()); | ||||
String filename(kPlugin->getFilename()); | String filename(kPlugin->getFilename()); | ||||
@@ -145,9 +137,28 @@ public: | |||||
// ui-title | // ui-title | ||||
arguments.add(name + String(" (GUI)")); | 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!"); | carla_stdout("failed!"); | ||||
fProcess = nullptr; | fProcess = nullptr; | ||||
@@ -16,373 +16,8 @@ | |||||
*/ | */ | ||||
#if 0 | #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); | //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 | kPluginBridgeNonRtClientSetParameterMidiCC, // uint, short | ||||
kPluginBridgeNonRtClientSetProgram, // int | kPluginBridgeNonRtClientSetProgram, // int | ||||
kPluginBridgeNonRtClientSetMidiProgram, // 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 | kPluginBridgeNonRtClientSetCtrlChannel, // short | ||||
kPluginBridgeNonRtClientSetOption, // uint/option, bool | kPluginBridgeNonRtClientSetOption, // uint/option, bool | ||||
kPluginBridgeNonRtClientPrepareForSave, | kPluginBridgeNonRtClientPrepareForSave, | ||||
@@ -92,8 +92,8 @@ enum PluginBridgeNonRtServerOpcode { | |||||
kPluginBridgeNonRtServerCurrentMidiProgram, // int/index | kPluginBridgeNonRtServerCurrentMidiProgram, // int/index | ||||
kPluginBridgeNonRtServerProgramName, // uint/index, uint/size, str[] (name) | kPluginBridgeNonRtServerProgramName, // uint/index, uint/size, str[] (name) | ||||
kPluginBridgeNonRtServerMidiProgramData, // uint/index, uint/bank, uint/program, 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 | kPluginBridgeNonRtServerSetLatency, // uint | ||||
kPluginBridgeNonRtServerReady, | kPluginBridgeNonRtServerReady, | ||||
kPluginBridgeNonRtServerSaved, | 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 | 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)); | const std::size_t fileBaseLen(std::strlen(fileBase)); | ||||
CARLA_SAFE_ASSERT_RETURN(fileBaseLen > 6, gNullCarlaShm); | 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 | // character set to use randomly | ||||
static const char charSet[] = "abcdefghijklmnopqrstuvwxyz" | static const char charSet[] = "abcdefghijklmnopqrstuvwxyz" | ||||