@@ -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" | |||