From c178105a1526e5ee6f3fce749a5dbc4c9b8195dc Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 16 May 2020 22:13:19 +0100 Subject: [PATCH] First go at backend-side canvas positions; Safer jack callbacks Signed-off-by: falkTX --- source/backend/CarlaBackend.h | 12 +- source/backend/CarlaEngine.hpp | 13 +- source/backend/CarlaHost.h | 9 + source/backend/CarlaStandalone.cpp | 10 + source/backend/engine/CarlaEngine.cpp | 213 ++++- source/backend/engine/CarlaEngineGraph.cpp | 271 ++++++- source/backend/engine/CarlaEngineGraph.hpp | 14 + source/backend/engine/CarlaEngineJack.cpp | 817 ++++++++++++++------ source/backend/engine/CarlaEngineNative.cpp | 16 + source/frontend/carla_backend.py | 28 + source/frontend/carla_backend_qt.py | 1 + source/frontend/carla_host.py | 40 +- source/frontend/patchcanvas/__init__.py | 26 +- source/frontend/patchcanvas/canvasbox.py | 82 +- source/frontend/patchcanvas/patchcanvas.py | 65 +- source/frontend/patchcanvas/scene.py | 4 +- source/utils/CarlaBackendUtils.hpp | 2 + source/utils/CarlaPatchbayUtils.hpp | 38 +- 18 files changed, 1324 insertions(+), 337 deletions(-) diff --git a/source/backend/CarlaBackend.h b/source/backend/CarlaBackend.h index 95642c416..c8ccb46cb 100644 --- a/source/backend/CarlaBackend.h +++ b/source/backend/CarlaBackend.h @@ -1141,7 +1141,17 @@ typedef enum { * @a value1 Parameter index * @a valueStr New mapped range as "%f:%f" syntax */ - ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46 + ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46, + + /*! + * A patchbay client position has changed. + * @a pluginId Client Id + * @a value1 X position 1 + * @a value2 Y position 1 + * @a value3 X position 2 + * @a valuef Y position 2 + */ + ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED = 47, } EngineCallbackOpcode; diff --git a/source/backend/CarlaEngine.hpp b/source/backend/CarlaEngine.hpp index c1fdfc279..66f0f9a87 100644 --- a/source/backend/CarlaEngine.hpp +++ b/source/backend/CarlaEngine.hpp @@ -1168,6 +1168,12 @@ public: */ virtual bool patchbayDisconnect(bool external, uint connectionId); + /*! + * Set the position of a group. + */ + virtual bool patchbaySetGroupPos(bool sendHost, bool sendOSC, bool external, + uint groupId, int x1, int y1, int x2, int y2); + /*! * Force the engine to resend all patchbay clients, ports and connections again. */ @@ -1348,10 +1354,11 @@ protected: * Virtual functions for handling patchbay state. * Do not free returned data. */ + struct PatchbayPosition { const char* name; int x1, y1, x2, y2; bool dealloc; }; virtual const char* const* getPatchbayConnections(bool external) const; - virtual void restorePatchbayConnection(bool external, - const char* sourcePort, - const char* targetPort); + virtual const PatchbayPosition* getPatchbayPositions(bool external, uint& count) const; + virtual void restorePatchbayConnection(bool external, const char* sourcePort, const char* targetPort); + virtual void restorePatchbayGroupPosition(bool external, const PatchbayPosition& ppos); /*! * Virtual functions for handling external graph ports. diff --git a/source/backend/CarlaHost.h b/source/backend/CarlaHost.h index d268b830e..156f7cbaa 100644 --- a/source/backend/CarlaHost.h +++ b/source/backend/CarlaHost.h @@ -550,6 +550,15 @@ CARLA_EXPORT bool carla_patchbay_connect(CarlaHostHandle handle, bool external, */ CARLA_EXPORT bool carla_patchbay_disconnect(CarlaHostHandle handle, bool external, uint connectionId); +/*! + * Set the position of a group. + * This is purely cached and saved in the project file, Carla backend does nothing with the value. + * When loading a project, callbacks are used to inform of the previously saved positions. + * @see ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED + */ +CARLA_EXPORT bool carla_patchbay_set_group_pos(CarlaHostHandle handle, bool external, + uint groupId, int x1, int y1, int x2, int y2); + /*! * Force the engine to resend all patchbay clients, ports and connections again. * @param external Wherever to show external/hardware ports instead of internal ones. diff --git a/source/backend/CarlaStandalone.cpp b/source/backend/CarlaStandalone.cpp index 16f92a633..d93a6e715 100644 --- a/source/backend/CarlaStandalone.cpp +++ b/source/backend/CarlaStandalone.cpp @@ -970,6 +970,16 @@ bool carla_patchbay_disconnect(CarlaHostHandle handle, bool external, uint conne return handle->engine->patchbayDisconnect(external, connectionId); } +bool carla_patchbay_set_group_pos(CarlaHostHandle handle, bool external, uint groupId, int x1, int y1, int x2, int y2) +{ + CARLA_SAFE_ASSERT_WITH_LAST_ERROR_RETURN(handle->engine != nullptr, "Engine is not initialized", false); + + carla_debug("carla_patchbay_set_group_pos(%p, %s, %u, %i, %i, %i, %i)", + handle, bool2str(external), groupId, x1, y1, x2, y2); + + return handle->engine->patchbaySetGroupPos(true, false, external, groupId, x1, y1, x2, y2); +} + bool carla_patchbay_refresh(CarlaHostHandle handle, bool external) { CARLA_SAFE_ASSERT_WITH_LAST_ERROR_RETURN(handle->engine != nullptr, "Engine is not initialized", false); diff --git a/source/backend/engine/CarlaEngine.cpp b/source/backend/engine/CarlaEngine.cpp index 1c20cf1e9..2704ada2f 100644 --- a/source/backend/engine/CarlaEngine.cpp +++ b/source/backend/engine/CarlaEngine.cpp @@ -2198,67 +2198,149 @@ void CarlaEngine::saveProjectInternal(water::MemoryOutputStream& outStream) cons // save internal connections if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) { - if (const char* const* const patchbayConns = getPatchbayConnections(false)) + uint posCount = 0; + const char* const* const patchbayConns = getPatchbayConnections(false); + const PatchbayPosition* const patchbayPos = getPatchbayPositions(false, posCount); + + if (patchbayConns != nullptr || patchbayPos != nullptr) { MemoryOutputStream outPatchbay(2048); outPatchbay << "\n \n"; - for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i ) + if (patchbayConns != nullptr) { - const char* const connSource(patchbayConns[i]); - const char* const connTarget(patchbayConns[i+1]); + for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i) + { + const char* const connSource(patchbayConns[i]); + const char* const connTarget(patchbayConns[i+1]); - CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0'); - CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0'); + CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0'); + CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0'); - outPatchbay << " \n"; - outPatchbay << " " << xmlSafeString(connSource, true) << "\n"; - outPatchbay << " " << xmlSafeString(connTarget, true) << "\n"; - outPatchbay << " \n"; + outPatchbay << " \n"; + outPatchbay << " " << xmlSafeString(connSource, true) << "\n"; + outPatchbay << " " << xmlSafeString(connTarget, true) << "\n"; + outPatchbay << " \n"; + } + } + + if (patchbayPos != nullptr && posCount != 0) + { + outPatchbay << " \n"; + + for (uint i=0; i\n"; + outPatchbay << " " << xmlSafeString(ppos.name, true) << "\n"; + outPatchbay << " \n"; + + if (ppos.dealloc) + delete[] ppos.name; + } + + outPatchbay << " \n"; } outPatchbay << " \n"; outStream << outPatchbay; + + delete[] patchbayPos; } + } // if we're running inside some session-manager (and using JACK), let them handle the connections - bool saveExternalConnections; + bool saveExternalConnections, saveExternalPositions = true; /**/ if (isPlugin) + { saveExternalConnections = false; + saveExternalPositions = false; + } else if (std::strcmp(getCurrentDriverName(), "JACK") != 0) + { saveExternalConnections = true; + } else if (std::getenv("CARLA_DONT_MANAGE_CONNECTIONS") != nullptr) + { saveExternalConnections = false; + } else if (std::getenv("LADISH_APP_NAME") != nullptr) + { saveExternalConnections = false; + } else if (std::getenv("NSM_URL") != nullptr) + { saveExternalConnections = false; + } else + { saveExternalConnections = true; + } - if (saveExternalConnections) + if (saveExternalConnections || saveExternalPositions) { - if (const char* const* const patchbayConns = getPatchbayConnections(true)) + uint posCount = 0; + const char* const* const patchbayConns = saveExternalConnections + ? getPatchbayConnections(true) + : nullptr; + const PatchbayPosition* const patchbayPos = saveExternalPositions + ? getPatchbayPositions(true, posCount) + : nullptr; + + if (patchbayConns != nullptr || patchbayPos != nullptr) { MemoryOutputStream outPatchbay(2048); outPatchbay << "\n \n"; - for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i ) + if (patchbayConns != nullptr) { - const char* const connSource(patchbayConns[i]); - const char* const connTarget(patchbayConns[i+1]); + for (int i=0; patchbayConns[i] != nullptr && patchbayConns[i+1] != nullptr; ++i, ++i ) + { + const char* const connSource(patchbayConns[i]); + const char* const connTarget(patchbayConns[i+1]); - CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0'); - CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0'); + CARLA_SAFE_ASSERT_CONTINUE(connSource != nullptr && connSource[0] != '\0'); + CARLA_SAFE_ASSERT_CONTINUE(connTarget != nullptr && connTarget[0] != '\0'); - outPatchbay << " \n"; - outPatchbay << " " << xmlSafeString(connSource, true) << "\n"; - outPatchbay << " " << xmlSafeString(connTarget, true) << "\n"; - outPatchbay << " \n"; + outPatchbay << " \n"; + outPatchbay << " " << xmlSafeString(connSource, true) << "\n"; + outPatchbay << " " << xmlSafeString(connTarget, true) << "\n"; + outPatchbay << " \n"; + } + } + + if (patchbayPos != nullptr && posCount != 0) + { + outPatchbay << " \n"; + + for (uint i=0; i\n"; + outPatchbay << " " << xmlSafeString(ppos.name, true) << "\n"; + outPatchbay << " \n"; + + if (ppos.dealloc) + delete[] ppos.name; + } + + outPatchbay << " \n"; } outPatchbay << " \n"; @@ -2843,8 +2925,9 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc) // plus external connections too if (loadExternalConnections) { - const bool loadingAsExternal = pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && - hasInternalConnections; + bool loadingAsExternal = hasInternalConnections && + (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || + pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY); for (XmlElement* elem = xmlElement->getFirstChildElement(); elem != nullptr; elem = elem->getNextElement()) { @@ -2857,7 +2940,11 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc) continue; } // or load external patchbay connections - else if (tagName != "ExternalPatchbay") + else if (tagName == "ExternalPatchbay") + { + loadingAsExternal = true; + } + else { continue; } @@ -2891,6 +2978,82 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc) break; } } + + // finally, we handle positions + if (XmlElement* const elemPatchbay = xmlElement->getChildByName("Patchbay")) + { + if (XmlElement* const elemPositions = elemPatchbay->getChildByName("Positions")) + { + String name; + PatchbayPosition ppos = { nullptr, 0, 0, 0, 0, false }; + + for (XmlElement* patchElem = elemPositions->getFirstChildElement(); patchElem != nullptr; patchElem = patchElem->getNextElement()) + { + const String& patchTag(patchElem->getTagName()); + + if (patchTag != "Position") + continue; + + XmlElement* const patchName = patchElem->getChildByName("Name"); + CARLA_SAFE_ASSERT_CONTINUE(patchName != nullptr); + + const String nameText(patchName->getAllSubText().trim()); + name = xmlSafeString(nameText, false); + + ppos.name = name.toRawUTF8(); + ppos.x1 = patchElem->getIntAttribute("x1"); + ppos.y1 = patchElem->getIntAttribute("y1"); + ppos.x2 = patchElem->getIntAttribute("x2"); + ppos.y2 = patchElem->getIntAttribute("y2"); + + if (name.isNotEmpty()) + restorePatchbayGroupPosition(false, ppos); + } + + callback(true, true, ENGINE_CALLBACK_IDLE, 0, 0, 0, 0, 0.0f, nullptr); + + if (pData->aboutToClose) + return true; + + if (pData->actionCanceled) + { + setLastError("Project load canceled"); + return false; + } + } + } + + if (XmlElement* const elemPatchbay = xmlElement->getChildByName("ExternalPatchbay")) + { + if (XmlElement* const elemPositions = elemPatchbay->getChildByName("Positions")) + { + String name; + PatchbayPosition ppos = { nullptr, 0, 0, 0, 0, false }; + + for (XmlElement* patchElem = elemPositions->getFirstChildElement(); patchElem != nullptr; patchElem = patchElem->getNextElement()) + { + const String& patchTag(patchElem->getTagName()); + + if (patchTag != "Position") + continue; + + XmlElement* const patchName = patchElem->getChildByName("Name"); + CARLA_SAFE_ASSERT_CONTINUE(patchName != nullptr); + + const String nameText(patchName->getAllSubText().trim()); + name = xmlSafeString(nameText, false); + + ppos.name = name.toRawUTF8(); + ppos.x1 = patchElem->getIntAttribute("x1"); + ppos.y1 = patchElem->getIntAttribute("y1"); + ppos.x2 = patchElem->getIntAttribute("x2"); + ppos.y2 = patchElem->getIntAttribute("y2"); + + if (name.isNotEmpty()) + restorePatchbayGroupPosition(true, ppos); + } + } + } #endif if (pData->options.resetXruns) diff --git a/source/backend/engine/CarlaEngineGraph.cpp b/source/backend/engine/CarlaEngineGraph.cpp index 1d198e8e4..29280d0c5 100644 --- a/source/backend/engine/CarlaEngineGraph.cpp +++ b/source/backend/engine/CarlaEngineGraph.cpp @@ -134,8 +134,12 @@ ExternalGraph::ExternalGraph(CarlaEngine* const engine) noexcept : connections(), audioPorts(), midiPorts(), + positions(), retCon(), - kEngine(engine) {} + kEngine(engine) +{ + carla_zeroStruct(positions); +} void ExternalGraph::clear() noexcept { @@ -313,11 +317,24 @@ bool ExternalGraph::disconnect(const bool sendHost, const bool sendOSC, return false; } +void ExternalGraph::setGroupPos(const bool sendHost, const bool sendOSC, + const uint groupId, const int x1, const int y1, const int x2, const int y2) +{ + CARLA_SAFE_ASSERT_UINT_RETURN(groupId >= kExternalGraphGroupCarla && groupId < kExternalGraphGroupMax, groupId,); + + positions[groupId] = { true, x1, y1, x2, y2 }; + + kEngine->callback(sendHost, sendOSC, + ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED, + groupId, x1, y1, x2, static_cast(y2), + nullptr); +} + void ExternalGraph::refresh(const bool sendHost, const bool sendOSC, const char* const deviceName) { CARLA_SAFE_ASSERT_RETURN(deviceName != nullptr,); - const bool isRack(kEngine->getOptions().processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK); + const bool isRack = kEngine->getOptions().processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK; // Main { @@ -591,6 +608,39 @@ const char* const* ExternalGraph::getConnections() const noexcept return retCon; } +bool ExternalGraph::getGroupFromName(const char* groupName, uint& groupId) const noexcept +{ + CARLA_SAFE_ASSERT_RETURN(groupName != nullptr && groupName[0] != '\0', false); + + if (std::strcmp(groupName, "Carla") == 0) + { + groupId = kExternalGraphGroupCarla; + return true; + } + if (std::strcmp(groupName, "AudioIn") == 0) + { + groupId = kExternalGraphGroupAudioIn; + return true; + } + if (std::strcmp(groupName, "AudioOut") == 0) + { + groupId = kExternalGraphGroupAudioOut; + return true; + } + if (std::strcmp(groupName, "MidiIn") == 0) + { + groupId = kExternalGraphGroupMidiIn; + return true; + } + if (std::strcmp(groupName, "MidiOut") == 0) + { + groupId = kExternalGraphGroupMidiOut; + return true; + } + + return false; +} + bool ExternalGraph::getGroupAndPortIdFromFullName(const char* const fullPortName, uint& groupId, uint& portId) const noexcept { CARLA_SAFE_ASSERT_RETURN(fullPortName != nullptr && fullPortName[0] != '\0', false); @@ -1230,16 +1280,19 @@ const String getProcessorFullPortName(AudioProcessor* const proc, const uint32_t static inline void addNodeToPatchbay(const bool sendHost, const bool sendOSC, CarlaEngine* const engine, - const uint32_t groupId, const int clientId, const AudioProcessor* const proc) + AudioProcessorGraph::Node* const node, const int pluginId, const AudioProcessor* const proc) { CARLA_SAFE_ASSERT_RETURN(engine != nullptr,); + CARLA_SAFE_ASSERT_RETURN(node != nullptr,); CARLA_SAFE_ASSERT_RETURN(proc != nullptr,); + const uint groupId = node->nodeId; + engine->callback(sendHost, sendOSC, ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, groupId, - clientId >= 0 ? PATCHBAY_ICON_PLUGIN : PATCHBAY_ICON_HARDWARE, - clientId, + pluginId >= 0 ? PATCHBAY_ICON_PLUGIN : PATCHBAY_ICON_HARDWARE, + pluginId, 0, 0.0f, proc->getName().toRawUTF8()); @@ -1308,6 +1361,18 @@ void addNodeToPatchbay(const bool sendHost, const bool sendOSC, CarlaEngine* con 0, 0.0f, proc->getOutputChannelName(AudioProcessor::ChannelTypeMIDI, i).toRawUTF8()); } + + if (node->properties.contains("x1")) + { + engine->callback(sendHost, sendOSC, + ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED, + groupId, + node->properties.getWithDefault("x1", 0), + node->properties.getWithDefault("y1", 0), + node->properties.getWithDefault("x2", 0), + static_cast(node->properties.getWithDefault("y2", 0)), + nullptr); + } } static inline @@ -1813,7 +1878,7 @@ void PatchbayGraph::addPlugin(CarlaPlugin* const plugin) node->properties.set("isPlugin", true); node->properties.set("pluginId", static_cast(plugin->getId())); - addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, static_cast(plugin->getId()), instance); + addNodeToPatchbay(sendHost, sendOSC, kEngine, node, static_cast(plugin->getId()), instance); } void PatchbayGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* const newPlugin) @@ -1845,7 +1910,7 @@ void PatchbayGraph::replacePlugin(CarlaPlugin* const oldPlugin, CarlaPlugin* con node->properties.set("isPlugin", true); node->properties.set("pluginId", static_cast(newPlugin->getId())); - addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, static_cast(newPlugin->getId()), instance); + addNodeToPatchbay(sendHost, sendOSC, kEngine, node, static_cast(newPlugin->getId()), instance); } void PatchbayGraph::renamePlugin(CarlaPlugin* const plugin, const char* const newName) @@ -2093,6 +2158,26 @@ void PatchbayGraph::disconnectInternalGroup(const uint groupId) noexcept } } +void PatchbayGraph::setGroupPos(const bool sendHost, const bool sendOSC, const bool external, + uint groupId, int x1, int y1, int x2, int y2) +{ + if (external) + return extGraph.setGroupPos(sendHost, sendOSC, groupId, x1, y1, x2, y2); + + AudioProcessorGraph::Node* const node(graph.getNodeForId(groupId)); + CARLA_SAFE_ASSERT_RETURN(node != nullptr,); + + node->properties.set("x1", x1); + node->properties.set("y1", y1); + node->properties.set("x2", x2); + node->properties.set("y2", y2); + + kEngine->callback(sendHost, sendOSC, + ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED, + groupId, x1, y1, x2, static_cast(y2), + nullptr); +} + void PatchbayGraph::refresh(const bool sendHost, const bool sendOSC, const bool external, const char* const deviceName) { if (external) @@ -2111,13 +2196,13 @@ void PatchbayGraph::refresh(const bool sendHost, const bool sendOSC, const bool AudioProcessor* const proc(node->getProcessor()); CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); - int clientId = -1; + int pluginId = -1; // plugin node if (node->properties.getWithDefault("isPlugin", false) == water::var(true)) - clientId = node->properties.getWithDefault("pluginId", -1); + pluginId = node->properties.getWithDefault("pluginId", -1); - addNodeToPatchbay(sendHost, sendOSC, kEngine, node->nodeId, clientId, proc); + addNodeToPatchbay(sendHost, sendOSC, kEngine, node, pluginId, proc); } char strBuf[STR_MAX+1]; @@ -2212,6 +2297,115 @@ const char* const* PatchbayGraph::getConnections(const bool external) const return retCon; } +const CarlaEngine::PatchbayPosition* PatchbayGraph::getPositions(bool external, uint& count) const +{ + CarlaEngine::PatchbayPosition* ret; + + if (external) + { + try { + ret = new CarlaEngine::PatchbayPosition[kExternalGraphGroupMax]; + } CARLA_SAFE_EXCEPTION_RETURN("new CarlaEngine::PatchbayPosition", nullptr); + + count = 0; + + for (uint i=kExternalGraphGroupCarla; igetName(); + break; + case kExternalGraphGroupAudioIn: + ppos.name = "Capture"; + break; + case kExternalGraphGroupAudioOut: + ppos.name = "Playback"; + break; + case kExternalGraphGroupMidiIn: + ppos.name = "Readable MIDI ports"; + break; + case kExternalGraphGroupMidiOut: + ppos.name = "Writable MIDI ports"; + break; + } + ppos.dealloc = false; + + ppos.x1 = eppos.x1; + ppos.y1 = eppos.y1; + ppos.x2 = eppos.x2; + ppos.y2 = eppos.y2; + } + + return ret; + } + else + { + const int numNodes = graph.getNumNodes(); + CARLA_SAFE_ASSERT_RETURN(numNodes > 0, nullptr); + + try { + ret = new CarlaEngine::PatchbayPosition[numNodes]; + } CARLA_SAFE_EXCEPTION_RETURN("new CarlaEngine::PatchbayPosition", nullptr); + + count = 0; + + for (int i=numNodes; --i >= 0;) + { + AudioProcessorGraph::Node* const node(graph.getNode(i)); + CARLA_SAFE_ASSERT_CONTINUE(node != nullptr); + + if (! node->properties.contains("x1")) + continue; + + AudioProcessor* const proc(node->getProcessor()); + CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); + + CarlaEngine::PatchbayPosition& ppos(ret[count++]); + + ppos.name = carla_strdup(proc->getName().toRawUTF8()); + ppos.dealloc = true; + + ppos.x1 = node->properties.getWithDefault("x1", 0); + ppos.y1 = node->properties.getWithDefault("y1", 0); + ppos.x2 = node->properties.getWithDefault("x2", 0); + ppos.y2 = node->properties.getWithDefault("y2", 0); + } + + return ret; + } +} + +bool PatchbayGraph::getGroupFromName(bool external, const char* groupName, uint& groupId) const +{ + if (external) + return extGraph.getGroupFromName(groupName, groupId); + + for (int i=0, count=graph.getNumNodes(); igetProcessor()); + CARLA_SAFE_ASSERT_CONTINUE(proc != nullptr); + + if (proc->getName() != groupName) + continue; + + groupId = node->nodeId; + return true; + } + + return false; +} + bool PatchbayGraph::getGroupAndPortIdFromFullName(const bool external, const char* const fullPortName, uint& groupId, uint& portId) const { if (external) @@ -2607,8 +2801,6 @@ bool CarlaEngine::patchbayConnect(const bool external, return graph->connect(external, groupA, portA, groupB, portB); } - - return false; } bool CarlaEngine::patchbayDisconnect(const bool external, const uint connectionId) @@ -2631,8 +2823,28 @@ bool CarlaEngine::patchbayDisconnect(const bool external, const uint connectionI return graph->disconnect(external, connectionId); } +} - return false; +bool CarlaEngine::patchbaySetGroupPos(const bool sendHost, const bool sendOSC, const bool external, + const uint groupId, const int x1, const int y1, const int x2, const int y2) +{ + CARLA_SAFE_ASSERT_RETURN(pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK || pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY, false); + CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), false); + carla_debug("CarlaEngine::patchbaySetGroupPos(%u, %i, %i, %i, %i)", groupId, x1, y1, x2, y2); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + { + // we don't bother to save position in this case, there is only midi in/out + return true; + } + else + { + PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); + CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); + + graph->setGroupPos(sendHost, sendOSC, external, groupId, x1, y1, x2, y2); + return true; + } } bool CarlaEngine::patchbayRefresh(const bool sendHost, const bool sendOSC, const bool external) @@ -2687,6 +2899,22 @@ const char* const* CarlaEngine::getPatchbayConnections(const bool external) cons return nullptr; } +const CarlaEngine::PatchbayPosition* CarlaEngine::getPatchbayPositions(bool external, uint& count) const +{ + CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), nullptr); + carla_debug("CarlaEngine::getPatchbayPositions(%s)", bool2str(external)); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + { + PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); + CARLA_SAFE_ASSERT_RETURN(graph != nullptr, nullptr); + + return graph->getPositions(external, count); + } + + return nullptr; +} + void CarlaEngine::restorePatchbayConnection(const bool external, const char* const sourcePort, const char* const targetPort) @@ -2726,6 +2954,23 @@ void CarlaEngine::restorePatchbayConnection(const bool external, } } +void CarlaEngine::restorePatchbayGroupPosition(const bool external, const PatchbayPosition& ppos) +{ + CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(),); + CARLA_SAFE_ASSERT_RETURN(ppos.name != nullptr && ppos.name[0] != '\0',); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + { + PatchbayGraph* const graph = pData->graph.getPatchbayGraph(); + CARLA_SAFE_ASSERT_RETURN(graph != nullptr,); + + uint groupId; + CARLA_SAFE_ASSERT_RETURN(graph->getGroupFromName(external, ppos.name, groupId),); + + graph->setGroupPos(true, true, external, groupId, ppos.x1, ppos.y1, ppos.x2, ppos.y2); + } +} + // ----------------------------------------------------------------------- bool CarlaEngine::connectExternalGraphPort(const uint connectionType, const uint portId, const char* const portName) diff --git a/source/backend/engine/CarlaEngineGraph.hpp b/source/backend/engine/CarlaEngineGraph.hpp index 14436e7c2..33688fadc 100644 --- a/source/backend/engine/CarlaEngineGraph.hpp +++ b/source/backend/engine/CarlaEngineGraph.hpp @@ -33,6 +33,13 @@ using water::MidiBuffer; CARLA_BACKEND_START_NAMESPACE +// ----------------------------------------------------------------------- + +struct PatchbayPosition { + bool active; + int x1, y1, x2, y2; +}; + // ----------------------------------------------------------------------- // External Graph stuff @@ -80,6 +87,7 @@ struct ExternalGraphPorts { struct ExternalGraph { PatchbayConnectionList connections; ExternalGraphPorts audioPorts, midiPorts; + PatchbayPosition positions[kExternalGraphGroupMax]; mutable CharStringListPtr retCon; ExternalGraph(CarlaEngine* engine) noexcept; @@ -89,10 +97,13 @@ struct ExternalGraph { uint groupA, uint portA, uint groupB, uint portB) noexcept; bool disconnect(bool sendHost, bool sendOSC, uint connectionId) noexcept; + void setGroupPos(bool sendHost, bool sendOSC, + uint groupId, int x1, int y1, int x2, int y2); void refresh(bool sendHost, bool sendOSC, const char* deviceName); const char* const* getConnections() const noexcept; + bool getGroupFromName(const char* groupName, uint& groupId) const noexcept; bool getGroupAndPortIdFromFullName(const char* fullPortName, uint& groupId, uint& portId) const noexcept; CarlaEngine* const kEngine; @@ -190,9 +201,12 @@ public: bool connect(bool external, uint groupA, uint portA, uint groupB, uint portB); bool disconnect(bool external, uint connectionId); void disconnectInternalGroup(uint groupId) noexcept; + void setGroupPos(bool sendHost, bool sendOsc, bool external, uint groupId, int x1, int y1, int x2, int y2); void refresh(bool sendHost, bool sendOsc, bool external, const char* deviceName); const char* const* getConnections(bool external) const; + const CarlaEngine::PatchbayPosition* getPositions(bool external, uint& count) const; + bool getGroupFromName(bool external, const char* groupName, uint& groupId) const; bool getGroupAndPortIdFromFullName(bool external, const char* fullPortName, uint& groupId, uint& portId) const; void process(CarlaEngine::ProtectedData* data, diff --git a/source/backend/engine/CarlaEngineJack.cpp b/source/backend/engine/CarlaEngineJack.cpp index a492a1527..d034e602c 100644 --- a/source/backend/engine/CarlaEngineJack.cpp +++ b/source/backend/engine/CarlaEngineJack.cpp @@ -56,6 +56,7 @@ #define URI_CANVAS_ICON "http://kxstudio.sf.net/ns/canvas/icon" #define URI_MAIN_CLIENT_NAME "https://kx.studio/ns/carla/main-client-name" +#define URI_POSITION "https://kx.studio/ns/carla/position" #define URI_PLUGIN_ICON "https://kx.studio/ns/carla/plugin-icon" #define URI_PLUGIN_ID "https://kx.studio/ns/carla/plugin-id" @@ -111,8 +112,11 @@ struct CarlaJackPortHints { // ----------------------------------------------------------------------- // Fallback data -static const EngineEvent kFallbackJackEngineEvent = { kEngineEventTypeNull, 0, 0, {{ kEngineControlEventTypeNull, 0, 0.0f }} }; -//static CarlaEngineEventCV kFallbackEngineEventCV = { nullptr, (uint32_t)-1, 0.0f }; +static const GroupNameToId kGroupNameToIdFallback = { 0, { '\0' } }; +static const PortNameToId kPortNameToIdFallback = { 0, 0, { '\0' }, { '\0' } }; +static /* */ PortNameToId kPortNameToIdFallbackNC = { 0, 0, { '\0' }, { '\0' } }; +static const ConnectionToId kConnectionToIdFallback = { 0, 0, 0, 0, 0 }; +static const EngineEvent kFallbackJackEngineEvent = { kEngineEventTypeNull, 0, 0, {{ kEngineControlEventTypeNull, 0, 0.0f }} }; // ----------------------------------------------------------------------- // Carla Engine Port removal helper @@ -1896,46 +1900,70 @@ public: stopThread(-1); - const uint groupId(fUsedGroups.getGroupId(plugin->getName())); + LinkedList ports; + LinkedList conns; - if (groupId > 0) { - for (LinkedList::Itenerator it = fUsedPorts.list.begin2(); it.valid(); it.next()) + const CarlaMutexLocker cml1(fUsedGroups.mutex); + + if (const uint groupId = fUsedGroups.getGroupId(plugin->getName())) { - for (LinkedList::Itenerator it2 = fUsedConnections.list.begin2(); it2.valid(); it2.next()) + const CarlaMutexLocker cml2(fUsedPorts.mutex); + + for (LinkedList::Itenerator it = fUsedPorts.list.begin2(); it.valid(); it.next()) { - static ConnectionToId connectionFallback = { 0, 0, 0, 0, 0 }; + const PortNameToId& portNameToId(it.getValue(kPortNameToIdFallback)); + CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group != 0); + + if (portNameToId.group != groupId) + continue; - ConnectionToId& connectionToId = it2.getValue(connectionFallback); + ports.append(portNameToId); + fUsedPorts.list.remove(it); + } + + const CarlaMutexLocker cml3(fUsedConnections.mutex); + + for (LinkedList::Itenerator it = fUsedConnections.list.begin2(); it.valid(); it.next()) + { + const ConnectionToId& connectionToId = it.getValue(kConnectionToIdFallback); CARLA_SAFE_ASSERT_CONTINUE(connectionToId.id != 0); if (connectionToId.groupA != groupId && connectionToId.groupB != groupId) continue; - callback(fExternalPatchbayHost, fExternalPatchbayOsc, - ENGINE_CALLBACK_PATCHBAY_CONNECTION_REMOVED, - connectionToId.id, - 0, 0, 0, 0.0f, nullptr); - fUsedConnections.list.remove(it2); + conns.append(connectionToId); + fUsedConnections.list.remove(it); } + } + } - static PortNameToId portNameFallback = { 0, 0, { '\0' }, { '\0' } }; - - PortNameToId& portNameToId(it.getValue(portNameFallback)); - CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group != 0); + for (LinkedList::Itenerator it = conns.begin2(); it.valid(); it.next()) + { + const ConnectionToId& connectionToId = it.getValue(kConnectionToIdFallback); + CARLA_SAFE_ASSERT_CONTINUE(connectionToId.id != 0); - if (portNameToId.group != groupId) - continue; + callback(fExternalPatchbayHost, fExternalPatchbayOsc, + ENGINE_CALLBACK_PATCHBAY_CONNECTION_REMOVED, + connectionToId.id, + 0, 0, 0, 0.0f, nullptr); + } - callback(fExternalPatchbayHost, fExternalPatchbayOsc, - ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED, - portNameToId.group, - static_cast(portNameToId.port), - 0, 0, 0.0f, nullptr); - fUsedPorts.list.remove(it); - } + for (LinkedList::Itenerator it = ports.begin2(); it.valid(); it.next()) + { + const PortNameToId& portNameToId(it.getValue(kPortNameToIdFallback)); + CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group != 0); + + callback(fExternalPatchbayHost, fExternalPatchbayOsc, + ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED, + portNameToId.group, + static_cast(portNameToId.port), + 0, 0, 0.0f, nullptr); } + ports.clear(); + conns.clear(); + startThread(); } else @@ -1971,6 +1999,8 @@ public: if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! external) return CarlaEngine::patchbayConnect(false, groupA, portA, groupB, portB); + const CarlaMutexLocker cml(fUsedPorts.mutex); + const char* const fullPortNameA = fUsedPorts.getFullPortName(groupA, portA); CARLA_SAFE_ASSERT_RETURN(fullPortNameA != nullptr && fullPortNameA[0] != '\0', false); @@ -2000,7 +2030,7 @@ public: for (LinkedList::Itenerator it = fUsedConnections.list.begin2(); it.valid(); it.next()) { - connectionToId = it.getValue(connectionToId); + connectionToId = it.getValue(kConnectionToIdFallback); CARLA_SAFE_ASSERT_CONTINUE(connectionToId.id != 0); if (connectionToId.id == connectionId) @@ -2014,6 +2044,8 @@ public: return false; } + const CarlaMutexLocker cml(fUsedPorts.mutex); + const char* const fullPortNameA = fUsedPorts.getFullPortName(connectionToId.groupA, connectionToId.portA); CARLA_SAFE_ASSERT_RETURN(fullPortNameA != nullptr && fullPortNameA[0] != '\0', false); @@ -2029,6 +2061,36 @@ public: return true; } + bool patchbaySetGroupPos(const bool sendHost, const bool sendOSC, const bool external, + const uint groupId, const int x1, const int y1, const int x2, const int y2) override + { + CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, false); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! external) + return CarlaEngine::patchbaySetGroupPos(sendHost, sendOSC, false, groupId, x1, y1, x2, y2); + + const char* groupName; + + { + const CarlaMutexLocker cml(fUsedPorts.mutex); + + groupName = fUsedGroups.getGroupName(groupId); + CARLA_SAFE_ASSERT_RETURN(groupName != nullptr && groupName[0] != '\0', false); + } + + const char* const uuidstr = jackbridge_get_uuid_for_client_name(fClient, groupName); + CARLA_SAFE_ASSERT_RETURN(uuidstr != nullptr && uuidstr[0] != '\0', false); + + jack_uuid_t uuid; + CARLA_SAFE_ASSERT_RETURN(jackbridge_uuid_parse(uuidstr, &uuid), false); + + char valueStr[STR_MAX]; + std::snprintf(valueStr, 63, "%i:%i:%i:%i", x1, y1, x2, y2); + valueStr[STR_MAX-1] = '\0'; + + return jackbridge_set_property(fClient, uuid, URI_POSITION, valueStr, URI_TYPE_STRING); + } + bool patchbayRefresh(const bool sendHost, const bool sendOSC, const bool external) override { CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, false); @@ -2051,9 +2113,15 @@ public: return CarlaEngine::patchbayRefresh(sendHost, sendOSC, false); } - fUsedGroups.clear(); - fUsedPorts.clear(); - fUsedConnections.clear(); + { + const CarlaMutexLocker cml1(fUsedGroups.mutex); + const CarlaMutexLocker cml2(fUsedPorts.mutex); + const CarlaMutexLocker cml3(fUsedConnections.mutex); + + fUsedGroups.clear(); + fUsedPorts.clear(); + fUsedConnections.clear(); + } initJackPatchbay(sendHost, sendOSC, jackbridge_get_client_name(fClient)); return true; @@ -2179,7 +2247,81 @@ public: return fRetConns; } - void restorePatchbayConnection(const bool external, const char* const connSource, const char* const connTarget) override + const PatchbayPosition* getPatchbayPositions(const bool external, uint& count) const override + { + CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, nullptr); + carla_debug("CarlaEngineJack::getPatchbayPositions(%s)", bool2str(external)); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! external) + return CarlaEngine::getPatchbayPositions(external, count); + + if (const std::size_t maxCount = fUsedGroups.list.count()) + { + PatchbayPosition* ret; + + try { + ret = new CarlaEngine::PatchbayPosition[maxCount]; + } CARLA_SAFE_EXCEPTION_RETURN("new CarlaEngine::PatchbayPosition", nullptr); + + count = 0; + + GroupNameToId groupNameToId; + + const CarlaMutexLocker cml1(fUsedGroups.mutex); + + for (LinkedList::Itenerator it = fUsedGroups.list.begin2(); it.valid(); it.next()) + { + groupNameToId = it.getValue(kGroupNameToIdFallback); + CARLA_SAFE_ASSERT_CONTINUE(groupNameToId.group != 0); + + const char* const uuidstr = jackbridge_get_uuid_for_client_name(fClient, groupNameToId.name); + CARLA_SAFE_ASSERT_CONTINUE(uuidstr != nullptr && uuidstr[0] != '\0'); + + jack_uuid_t uuid; + CARLA_SAFE_ASSERT_CONTINUE(jackbridge_uuid_parse(uuidstr, &uuid)); + + char* value = nullptr; + char* type = nullptr; + + if (jackbridge_get_property(uuid, URI_POSITION, &value, &type) + && value != nullptr + && type != nullptr + && std::strcmp(type, URI_TYPE_STRING) == 0) + { + CarlaEngine::PatchbayPosition& ppos(ret[count++]); + + ppos.name = carla_strdup_safe(groupNameToId.name); + ppos.dealloc = true; + + if (char* sep1 = std::strstr(value, ":")) + { + *sep1++ = '\0'; + ppos.x1 = std::atoi(value); + + if (char* sep2 = std::strstr(sep1, ":")) + { + *sep2++ = '\0'; + ppos.y1 = std::atoi(sep1); + + if (char* sep3 = std::strstr(sep2, ":")) + { + *sep3++ = '\0'; + ppos.x2 = std::atoi(sep2); + ppos.y2 = std::atoi(sep3); + } + } + } + } + } + + return ret; + } + + return nullptr; + } + + void restorePatchbayConnection(const bool external, + const char* const connSource, const char* const connTarget) override { CARLA_SAFE_ASSERT_RETURN(fClient != nullptr,); CARLA_SAFE_ASSERT_RETURN(connSource != nullptr && connSource[0] != '\0',); @@ -2199,6 +2341,51 @@ public: jackbridge_connect(fClient, connSource, connTarget); } } + + void restorePatchbayGroupPosition(const bool external, const PatchbayPosition& ppos) override + { + CARLA_SAFE_ASSERT_RETURN(fClient != nullptr,); + carla_debug("CarlaEngineJack::restorePatchbayGroupPosition(%s, ...)", bool2str(external)); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY && ! external) + return CarlaEngine::restorePatchbayGroupPosition(external, ppos); + + uint groupId = 0; + + // it might take a bit to receive jack client registration callback, so we ease things a bit + for (int i=10; --i >=0;) + { + { + const CarlaMutexLocker cml1(fUsedGroups.mutex); + groupId = fUsedGroups.getGroupId(ppos.name); + } + + if (groupId != 0) + break; + + carla_msleep(200); + callback(true, true, ENGINE_CALLBACK_IDLE, 0, 0, 0, 0, 0.0f, nullptr); + } + + CARLA_SAFE_ASSERT_RETURN(groupId != 0,); + + const char* const uuidstr = jackbridge_get_uuid_for_client_name(fClient, ppos.name); + CARLA_SAFE_ASSERT_RETURN(uuidstr != nullptr && uuidstr[0] != '\0',); + + jack_uuid_t uuid; + CARLA_SAFE_ASSERT_RETURN(jackbridge_uuid_parse(uuidstr, &uuid),); + + char valueStr[STR_MAX]; + std::snprintf(valueStr, 63, "%i:%i:%i:%i", ppos.x1, ppos.y1, ppos.x2, ppos.y2); + valueStr[STR_MAX-1] = '\0'; + + jackbridge_set_property(fClient, uuid, URI_POSITION, valueStr, URI_TYPE_STRING); + + callback(true, true, + ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED, + groupId, ppos.x1, ppos.y1, ppos.x2, static_cast(ppos.y2), + nullptr); + } #endif // ------------------------------------------------------------------- @@ -2539,6 +2726,7 @@ protected: callback(fExternalPatchbayHost, fExternalPatchbayOsc, ENGINE_CALLBACK_PATCHBAY_CLIENT_REMOVED, groupNameToId.group, 0, 0, 0, 0.0f, nullptr); + const CarlaMutexLocker cml(fUsedGroups.mutex); fUsedGroups.list.removeOne(groupNameToId); } @@ -2553,39 +2741,84 @@ protected: if (! fExternalPatchbayHost) return; #endif - bool found; + bool groupFound; CarlaString groupName(portName); - groupName.truncate(groupName.rfind(shortPortName, &found)-1); + groupName.truncate(groupName.rfind(shortPortName, &groupFound)-1); + CARLA_SAFE_ASSERT_RETURN(groupFound,); - CARLA_SAFE_ASSERT_RETURN(found,); - - uint groupId(fUsedGroups.getGroupId(groupName)); + groupFound = false; + GroupToIdData groupData; + PortToIdData portData; - if (groupId == 0) { - groupId = ++fUsedGroups.lastId; + const CarlaMutexLocker cml1(fUsedGroups.mutex); - GroupNameToId groupNameToId; - groupNameToId.setData(groupId, groupName); + groupData.id = fUsedGroups.getGroupId(groupName); + + if (groupData.id == 0) + { + groupData.id = ++fUsedGroups.lastId; + + GroupNameToId groupNameToId; + groupNameToId.setData(groupData.id, groupName); + + int pluginId = -1; + PatchbayIcon icon = jackPortHints.isHardware ? PATCHBAY_ICON_HARDWARE : PATCHBAY_ICON_APPLICATION; - int pluginId = -1; - PatchbayIcon icon = jackPortHints.isHardware ? PATCHBAY_ICON_HARDWARE : PATCHBAY_ICON_APPLICATION; + findPluginIdAndIcon(groupName, pluginId, icon); - findPluginIdAndIcon(groupName, pluginId, icon); + fUsedGroups.list.append(groupNameToId); + groupFound = true; + groupData.icon = icon; + groupData.pluginId = pluginId; + std::strncpy(groupData.strVal, groupName, STR_MAX-1); + groupData.strVal[STR_MAX-1] = '\0'; + } + + uint canvasPortFlags = 0x0; + canvasPortFlags |= jackPortHints.isInput ? PATCHBAY_PORT_IS_INPUT : 0x0; + + /**/ if (jackPortHints.isCV) + canvasPortFlags |= PATCHBAY_PORT_TYPE_CV; + else if (jackPortHints.isOSC) + canvasPortFlags |= PATCHBAY_PORT_TYPE_OSC; + else if (jackPortHints.isAudio) + canvasPortFlags |= PATCHBAY_PORT_TYPE_AUDIO; + else if (jackPortHints.isMIDI) + canvasPortFlags |= PATCHBAY_PORT_TYPE_MIDI; + + const CarlaMutexLocker cml2(fUsedPorts.mutex); + + portData.group = groupData.id; + portData.port = ++fUsedPorts.lastId; + portData.flags = canvasPortFlags; + std::strncpy(portData.strVal, shortPortName, STR_MAX-1); + portData.strVal[STR_MAX-1] = '\0'; + + PortNameToId portNameToId; + portNameToId.setData(portData.group, portData.port, shortPortName, portName); + fUsedPorts.list.append(portNameToId); + } + + if (groupFound) + { callback(fExternalPatchbayHost, fExternalPatchbayOsc, ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, - groupNameToId.group, - icon, - pluginId, + groupData.id, + groupData.icon, + groupData.pluginId, 0, 0.0f, - groupNameToId.name); - - fUsedGroups.list.append(groupNameToId); + groupData.strVal); } - addPatchbayJackPort(fExternalPatchbayHost, fExternalPatchbayOsc, - groupId, jackPortHints, shortPortName, portName); + callback(fExternalPatchbayHost, fExternalPatchbayOsc, + ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, + portData.group, + static_cast(portData.port), + static_cast(portData.flags), + 0, 0.0f, + portData.strVal); } void handleJackPortUnregistrationCallback(const char* const portName) @@ -2597,18 +2830,28 @@ protected: if (! fExternalPatchbayHost) return; #endif - const PortNameToId& portNameToId(fUsedPorts.getPortNameToId(portName)); + uint groupId, portId; + + { + const CarlaMutexLocker cml(fUsedPorts.mutex); - /* NOTE: Due to JACK2 async behaviour the port we get here might be the same of a previous rename-plugin request. - See the comment on CarlaEngineJack::renamePlugin() for more information. */ - if (portNameToId.group <= 0 || portNameToId.port <= 0) return; + const PortNameToId& portNameToId(fUsedPorts.getPortNameToId(portName)); + + /* NOTE: Due to JACK2 async behaviour the port we get here might be the same of a previous rename-plugin request. + See the comment on CarlaEngineJack::renamePlugin() for more information. */ + if (portNameToId.group <= 0 || portNameToId.port <= 0) return; + + groupId = portNameToId.group; + portId = portNameToId.port; + + fUsedPorts.list.removeOne(portNameToId); + } callback(fExternalPatchbayHost, fExternalPatchbayOsc, ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED, - portNameToId.group, - static_cast(portNameToId.port), + groupId, + static_cast(portId), 0, 0, 0.0f, nullptr); - fUsedPorts.list.removeOne(portNameToId); } void handleJackPortConnectCallback(const char* const portNameA, const char* const portNameB) @@ -2620,27 +2863,42 @@ protected: if (! fExternalPatchbayHost) return; #endif - const PortNameToId& portNameToIdA(fUsedPorts.getPortNameToId(portNameA)); - const PortNameToId& portNameToIdB(fUsedPorts.getPortNameToId(portNameB)); + char strBuf[STR_MAX]; + uint connectionId; + + { + const CarlaMutexLocker cml1(fUsedPorts.mutex); + + const PortNameToId& portNameToIdA(fUsedPorts.getPortNameToId(portNameA)); + const PortNameToId& portNameToIdB(fUsedPorts.getPortNameToId(portNameB)); + + /* NOTE: Due to JACK2 async behaviour the port we get here might be the same of a previous rename-plugin request. + See the comment on CarlaEngineJack::renamePlugin() for more information. */ + if (portNameToIdA.group <= 0 || portNameToIdA.port <= 0) return; + if (portNameToIdB.group <= 0 || portNameToIdB.port <= 0) return; - /* NOTE: Due to JACK2 async behaviour the port we get here might be the same of a previous rename-plugin request. - See the comment on CarlaEngineJack::renamePlugin() for more information. */ - if (portNameToIdA.group <= 0 || portNameToIdA.port <= 0) return; - if (portNameToIdB.group <= 0 || portNameToIdB.port <= 0) return; + const CarlaMutexLocker cml2(fUsedConnections.mutex); - char strBuf[STR_MAX+1]; - std::snprintf(strBuf, STR_MAX, "%i:%i:%i:%i", portNameToIdA.group, portNameToIdA.port, portNameToIdB.group, portNameToIdB.port); - strBuf[STR_MAX] = '\0'; + std::snprintf(strBuf, STR_MAX-1, "%i:%i:%i:%i", + portNameToIdA.group, portNameToIdA.port, + portNameToIdB.group, portNameToIdB.port); + strBuf[STR_MAX-1] = '\0'; - ConnectionToId connectionToId; - connectionToId.setData(++fUsedConnections.lastId, portNameToIdA.group, portNameToIdA.port, portNameToIdB.group, portNameToIdB.port); + connectionId = ++fUsedConnections.lastId; + + ConnectionToId connectionToId; + connectionToId.setData(connectionId, + portNameToIdA.group, portNameToIdA.port, + portNameToIdB.group, portNameToIdB.port); + + fUsedConnections.list.append(connectionToId); + } callback(fExternalPatchbayHost, fExternalPatchbayOsc, ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED, - connectionToId.id, + connectionId, 0, 0, 0, 0.0f, strBuf); - fUsedConnections.list.append(connectionToId); } void handleJackPortDisconnectCallback(const char* const portNameA, const char* const portNameB) @@ -2652,39 +2910,40 @@ protected: if (! fExternalPatchbayHost) return; #endif - const PortNameToId& portNameToIdA(fUsedPorts.getPortNameToId(portNameA)); - const PortNameToId& portNameToIdB(fUsedPorts.getPortNameToId(portNameB)); + uint connectionId = 0; + + { + const CarlaMutexLocker cml1(fUsedPorts.mutex); - /* NOTE: Due to JACK2 async behaviour the port we get here might be the same of a previous rename-plugin request. - See the comment on CarlaEngineJack::renamePlugin() for more information. */ - if (portNameToIdA.group <= 0 || portNameToIdA.port <= 0) return; - if (portNameToIdB.group <= 0 || portNameToIdB.port <= 0) return; + const PortNameToId& portNameToIdA(fUsedPorts.getPortNameToId(portNameA)); + const PortNameToId& portNameToIdB(fUsedPorts.getPortNameToId(portNameB)); - ConnectionToId connectionToId = { 0, 0, 0, 0, 0 }; - bool found = false; + /* NOTE: Due to JACK2 async behaviour the port we get here might be the same of a previous rename-plugin request. + See the comment on CarlaEngineJack::renamePlugin() for more information. */ + if (portNameToIdA.group <= 0 || portNameToIdA.port <= 0) return; + if (portNameToIdB.group <= 0 || portNameToIdB.port <= 0) return; - { - const CarlaMutexLocker cml(fUsedConnections.mutex); + const CarlaMutexLocker cml2(fUsedConnections.mutex); for (LinkedList::Itenerator it = fUsedConnections.list.begin2(); it.valid(); it.next()) { - connectionToId = it.getValue(connectionToId); + const ConnectionToId& connectionToId = it.getValue(kConnectionToIdFallback); CARLA_SAFE_ASSERT_CONTINUE(connectionToId.id != 0); if (connectionToId.groupA == portNameToIdA.group && connectionToId.portA == portNameToIdA.port && connectionToId.groupB == portNameToIdB.group && connectionToId.portB == portNameToIdB.port) { - found = true; + connectionId = connectionToId.id; fUsedConnections.list.remove(it); break; } } } - if (found) { + if (connectionId != 0) { callback(fExternalPatchbayHost, fExternalPatchbayOsc, ENGINE_CALLBACK_PATCHBAY_CONNECTION_REMOVED, - connectionToId.id, + connectionId, 0, 0, 0, 0.0f, nullptr); } } @@ -2706,33 +2965,49 @@ protected: bool found; CarlaString groupName(newFullName); groupName.truncate(groupName.rfind(newShortName, &found)-1); - CARLA_SAFE_ASSERT_RETURN(found,); - const uint groupId(fUsedGroups.getGroupId(groupName)); - CARLA_SAFE_ASSERT_RETURN(groupId > 0,); + uint groupId, portId; + char portName[STR_MAX]; + found = false; - for (LinkedList::Itenerator it = fUsedPorts.list.begin2(); it.valid(); it.next()) { - static PortNameToId portNameFallback = { 0, 0, { '\0' }, { '\0' } }; + const CarlaMutexLocker cml1(fUsedGroups.mutex); - PortNameToId& portNameToId(it.getValue(portNameFallback)); - CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group != 0); + groupId = fUsedGroups.getGroupId(groupName); + CARLA_SAFE_ASSERT_RETURN(groupId != 0,); - if (std::strncmp(portNameToId.fullName, oldFullName, STR_MAX) == 0) + const CarlaMutexLocker cml2(fUsedPorts.mutex); + + for (LinkedList::Itenerator it = fUsedPorts.list.begin2(); it.valid(); it.next()) { - CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group == groupId); + PortNameToId& portNameToId(it.getValue(kPortNameToIdFallbackNC)); + CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group != 0); - portNameToId.rename(newShortName, newFullName); - callback(fExternalPatchbayHost, fExternalPatchbayOsc, - ENGINE_CALLBACK_PATCHBAY_PORT_CHANGED, - portNameToId.group, - static_cast(portNameToId.port), - 0, 0, 0.0f, - portNameToId.name); - break; + if (std::strncmp(portNameToId.fullName, oldFullName, STR_MAX) == 0) + { + CARLA_SAFE_ASSERT_CONTINUE(portNameToId.group == groupId); + + found = true; + portId = portNameToId.port; + std::strncpy(portName, newShortName, STR_MAX-1); + portName[STR_MAX-1] = '\0'; + + portNameToId.rename(newShortName, newFullName); + break; + } } } + + if (found) + { + callback(fExternalPatchbayHost, fExternalPatchbayOsc, + ENGINE_CALLBACK_PATCHBAY_PORT_CHANGED, + groupId, + static_cast(portId), + 0, 0, 0.0f, + portName); + } } #endif @@ -2895,170 +3170,278 @@ private: } } + // handy stuff only needed for initJackPatchbay + struct GroupToIdData { + uint id; + PatchbayIcon icon; + int pluginId; + char strVal[STR_MAX]; + }; + struct PortToIdData { + uint group; + uint port; + uint flags; + char strVal[STR_MAX]; + }; + struct ConnectionToIdData { + uint id; + char strVal[STR_MAX]; + }; + void initJackPatchbay(const bool sendHost, const bool sendOSC, const char* const ourName) { CARLA_SAFE_ASSERT_RETURN(pData->options.processMode != ENGINE_PROCESS_MODE_PATCHBAY || (fExternalPatchbayHost && sendHost) || (fExternalPatchbayOsc && sendOSC),); CARLA_SAFE_ASSERT_RETURN(ourName != nullptr && ourName[0] != '\0',); + uint id, carlaId; CarlaStringList parsedGroups; + LinkedList groupCallbackData; + LinkedList portsCallbackData; + LinkedList connCallbackData; - // add our client first { - parsedGroups.append(ourName); + const CarlaMutexLocker cml1(fUsedGroups.mutex); + const CarlaMutexLocker cml2(fUsedPorts.mutex); + const CarlaMutexLocker cml3(fUsedConnections.mutex); - GroupNameToId groupNameToId; - groupNameToId.setData(++fUsedGroups.lastId, ourName); - - callback(sendHost, sendOSC, - ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, - groupNameToId.group, - PATCHBAY_ICON_CARLA, - MAIN_CARLA_PLUGIN_ID, - 0, 0.0f, - groupNameToId.name); - fUsedGroups.list.append(groupNameToId); - } + // add our client first + { + carlaId = ++fUsedGroups.lastId; + parsedGroups.append(ourName); - // query all jack ports - { - const char** const ports = jackbridge_get_ports(fClient, nullptr, nullptr, 0); - CARLA_SAFE_ASSERT_RETURN(ports != nullptr,); + GroupNameToId groupNameToId; + groupNameToId.setData(carlaId, ourName); + fUsedGroups.list.append(groupNameToId); + } - for (int i=0; ports[i] != nullptr; ++i) + // query all jack ports { - const char* const fullPortName(ports[i]); - CARLA_SAFE_ASSERT_CONTINUE(fullPortName != nullptr && fullPortName[0] != '\0'); + const char** const ports = jackbridge_get_ports(fClient, nullptr, nullptr, 0); + CARLA_SAFE_ASSERT_RETURN(ports != nullptr,); - const jack_port_t* const jackPort(jackbridge_port_by_name(fClient, fullPortName)); - CARLA_SAFE_ASSERT_CONTINUE(jackPort != nullptr); + for (int i=0; ports[i] != nullptr; ++i) + { + const char* const fullPortName(ports[i]); + CARLA_SAFE_ASSERT_CONTINUE(fullPortName != nullptr && fullPortName[0] != '\0'); - const char* const shortPortName(jackbridge_port_short_name(jackPort)); - CARLA_SAFE_ASSERT_CONTINUE(shortPortName != nullptr && shortPortName[0] != '\0'); + const jack_port_t* const jackPort(jackbridge_port_by_name(fClient, fullPortName)); + CARLA_SAFE_ASSERT_CONTINUE(jackPort != nullptr); - const CarlaJackPortHints jackPortHints(CarlaJackPortHints::fromPort(jackPort)); + const char* const shortPortName(jackbridge_port_short_name(jackPort)); + CARLA_SAFE_ASSERT_CONTINUE(shortPortName != nullptr && shortPortName[0] != '\0'); - uint groupId = 0; + const CarlaJackPortHints jackPortHints(CarlaJackPortHints::fromPort(jackPort)); - bool found; - CarlaString groupName(fullPortName); - groupName.truncate(groupName.rfind(shortPortName, &found)-1); + uint groupId = 0; - CARLA_SAFE_ASSERT_CONTINUE(found); + bool found; + CarlaString groupName(fullPortName); + groupName.truncate(groupName.rfind(shortPortName, &found)-1); - if (parsedGroups.contains(groupName)) - { - groupId = fUsedGroups.getGroupId(groupName); - CARLA_SAFE_ASSERT_CONTINUE(groupId > 0); + CARLA_SAFE_ASSERT_CONTINUE(found); + + if (parsedGroups.contains(groupName)) + { + groupId = fUsedGroups.getGroupId(groupName); + CARLA_SAFE_ASSERT_CONTINUE(groupId > 0); + } + else + { + groupId = ++fUsedGroups.lastId; + parsedGroups.append(groupName); + + GroupNameToId groupNameToId; + groupNameToId.setData(groupId, groupName); + + int pluginId = -1; + PatchbayIcon icon = jackPortHints.isHardware ? PATCHBAY_ICON_HARDWARE : PATCHBAY_ICON_APPLICATION; + + findPluginIdAndIcon(groupName, pluginId, icon); + + fUsedGroups.list.append(groupNameToId); + + GroupToIdData groupData; + groupData.id = groupId; + groupData.icon = icon; + groupData.pluginId = pluginId; + std::strncpy(groupData.strVal, groupName, STR_MAX-1); + groupData.strVal[STR_MAX-1] = '\0'; + groupCallbackData.append(groupData); + } + + uint canvasPortFlags = 0x0; + canvasPortFlags |= jackPortHints.isInput ? PATCHBAY_PORT_IS_INPUT : 0x0; + + /**/ if (jackPortHints.isCV) + canvasPortFlags |= PATCHBAY_PORT_TYPE_CV; + else if (jackPortHints.isOSC) + canvasPortFlags |= PATCHBAY_PORT_TYPE_OSC; + else if (jackPortHints.isAudio) + canvasPortFlags |= PATCHBAY_PORT_TYPE_AUDIO; + else if (jackPortHints.isMIDI) + canvasPortFlags |= PATCHBAY_PORT_TYPE_MIDI; + + id = ++fUsedPorts.lastId; + + PortNameToId portNameToId; + portNameToId.setData(groupId, id, shortPortName, fullPortName); + fUsedPorts.list.append(portNameToId); + + PortToIdData portData; + portData.group = groupId; + portData.port = id; + portData.flags = canvasPortFlags; + std::strncpy(portData.strVal, shortPortName, STR_MAX-1); + portData.strVal[STR_MAX-1] = '\0'; + portsCallbackData.append(portData); } - else + + jackbridge_free(ports); + } + + // query connections, after all ports are in place + if (const char** const ports = jackbridge_get_ports(fClient, nullptr, nullptr, JackPortIsOutput)) + { + for (int i=0; ports[i] != nullptr; ++i) { - groupId = ++fUsedGroups.lastId; - parsedGroups.append(groupName); + const char* const fullPortName(ports[i]); + CARLA_SAFE_ASSERT_CONTINUE(fullPortName != nullptr && fullPortName[0] != '\0'); - GroupNameToId groupNameToId; - groupNameToId.setData(groupId, groupName); + const jack_port_t* const jackPort(jackbridge_port_by_name(fClient, fullPortName)); + CARLA_SAFE_ASSERT_CONTINUE(jackPort != nullptr); - int pluginId = -1; - PatchbayIcon icon = jackPortHints.isHardware ? PATCHBAY_ICON_HARDWARE : PATCHBAY_ICON_APPLICATION; + const PortNameToId& thisPort(fUsedPorts.getPortNameToId(fullPortName)); - findPluginIdAndIcon(groupName, pluginId, icon); + CARLA_SAFE_ASSERT_CONTINUE(thisPort.group > 0); + CARLA_SAFE_ASSERT_CONTINUE(thisPort.port > 0); - callback(sendHost, sendOSC, - ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, - groupNameToId.group, - icon, - pluginId, - 0, 0.0f, - groupNameToId.name); - fUsedGroups.list.append(groupNameToId); + if (const char** const connections = jackbridge_port_get_all_connections(fClient, jackPort)) + { + for (int j=0; connections[j] != nullptr; ++j) + { + const char* const connection(connections[j]); + CARLA_SAFE_ASSERT_CONTINUE(connection != nullptr && connection[0] != '\0'); + + const PortNameToId& targetPort(fUsedPorts.getPortNameToId(connection)); + + CARLA_SAFE_ASSERT_CONTINUE(targetPort.group > 0); + CARLA_SAFE_ASSERT_CONTINUE(targetPort.port > 0); + + id = ++fUsedConnections.lastId; + + ConnectionToId connectionToId; + connectionToId.setData(id, thisPort.group, thisPort.port, targetPort.group, targetPort.port); + fUsedConnections.list.append(connectionToId); + + ConnectionToIdData connData; + connData.id = id; + std::snprintf(connData.strVal, STR_MAX-1, "%i:%i:%i:%i", + thisPort.group, thisPort.port, targetPort.group, targetPort.port); + connData.strVal[STR_MAX-1] = '\0'; + connCallbackData.append(connData); + } + + jackbridge_free(connections); + } } - addPatchbayJackPort(sendHost, sendOSC, groupId, jackPortHints, shortPortName, fullPortName); + jackbridge_free(ports); } - - jackbridge_free(ports); } - // query connections, after all ports are in place - if (const char** const ports = jackbridge_get_ports(fClient, nullptr, nullptr, JackPortIsOutput)) - { - char strBuf[STR_MAX+1]; - - for (int i=0; ports[i] != nullptr; ++i) - { - const char* const fullPortName(ports[i]); - CARLA_SAFE_ASSERT_CONTINUE(fullPortName != nullptr && fullPortName[0] != '\0'); + const GroupToIdData groupFallback = { 0, PATCHBAY_ICON_PLUGIN, -1, { '\0' } }; + const PortToIdData portFallback = { 0, 0, 0, { '\0' } }; + const ConnectionToIdData connFallback = { 0, { '\0' } }; - const jack_port_t* const jackPort(jackbridge_port_by_name(fClient, fullPortName)); - CARLA_SAFE_ASSERT_CONTINUE(jackPort != nullptr); + callback(sendHost, sendOSC, + ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, + carlaId, + PATCHBAY_ICON_CARLA, + MAIN_CARLA_PLUGIN_ID, + 0, 0.0f, + ourName); - const PortNameToId& thisPort(fUsedPorts.getPortNameToId(fullPortName)); + for (LinkedList::Itenerator it = groupCallbackData.begin2(); it.valid(); it.next()) + { + const GroupToIdData& group(it.getValue(groupFallback)); - CARLA_SAFE_ASSERT_CONTINUE(thisPort.group > 0); - CARLA_SAFE_ASSERT_CONTINUE(thisPort.port > 0); + callback(sendHost, sendOSC, + ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED, + group.id, + group.icon, + group.pluginId, + 0, 0.0f, + group.strVal); - if (const char** const connections = jackbridge_port_get_all_connections(fClient, jackPort)) - { - for (int j=0; connections[j] != nullptr; ++j) - { - const char* const connection(connections[j]); - CARLA_SAFE_ASSERT_CONTINUE(connection != nullptr && connection[0] != '\0'); + const char* const uuidstr = jackbridge_get_uuid_for_client_name(fClient, group.strVal); + CARLA_SAFE_ASSERT_RETURN(uuidstr != nullptr && uuidstr[0] != '\0',); - const PortNameToId& targetPort(fUsedPorts.getPortNameToId(connection)); + jack_uuid_t uuid; + CARLA_SAFE_ASSERT_RETURN(jackbridge_uuid_parse(uuidstr, &uuid),); - CARLA_SAFE_ASSERT_CONTINUE(targetPort.group > 0); - CARLA_SAFE_ASSERT_CONTINUE(targetPort.port > 0); + char* value = nullptr; + char* type = nullptr; - std::snprintf(strBuf, STR_MAX, "%i:%i:%i:%i", thisPort.group, thisPort.port, targetPort.group, targetPort.port); - strBuf[STR_MAX] = '\0'; + if (jackbridge_get_property(uuid, URI_POSITION, &value, &type) + && value != nullptr + && type != nullptr + && std::strcmp(type, URI_TYPE_STRING) == 0) + { + if (char* sep1 = std::strstr(value, ":")) + { + int x1, y1 = 0, x2 = 0, y2 = 0; + *sep1++ = '\0'; + x1 = std::atoi(value); - ConnectionToId connectionToId; - connectionToId.setData(++fUsedConnections.lastId, thisPort.group, thisPort.port, targetPort.group, targetPort.port); + if (char* sep2 = std::strstr(sep1, ":")) + { + *sep2++ = '\0'; + y1 = std::atoi(sep1); - callback(sendHost, sendOSC, - ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED, - connectionToId.id, - 0, 0, 0, 0.0f, - strBuf); - fUsedConnections.list.append(connectionToId); + if (char* sep3 = std::strstr(sep2, ":")) + { + *sep3++ = '\0'; + x2 = std::atoi(sep2); + y2 = std::atoi(sep3); + } } - jackbridge_free(connections); + callback(sendHost, sendOSC, + ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED, + group.id, x1, y1, x2, static_cast(y2), + nullptr); } } - - jackbridge_free(ports); } - } - void addPatchbayJackPort(const bool sendHost, const bool sendOSC, - const uint groupId, const CarlaJackPortHints& jackPortHints, - const char* const shortPortName, const char* const fullPortName) - { - uint canvasPortFlags = 0x0; - canvasPortFlags |= jackPortHints.isInput ? PATCHBAY_PORT_IS_INPUT : 0x0; + for (LinkedList::Itenerator it = portsCallbackData.begin2(); it.valid(); it.next()) + { + const PortToIdData& port(it.getValue(portFallback)); - /**/ if (jackPortHints.isCV) - canvasPortFlags |= PATCHBAY_PORT_TYPE_CV; - else if (jackPortHints.isOSC) - canvasPortFlags |= PATCHBAY_PORT_TYPE_OSC; - else if (jackPortHints.isAudio) - canvasPortFlags |= PATCHBAY_PORT_TYPE_AUDIO; - else if (jackPortHints.isMIDI) - canvasPortFlags |= PATCHBAY_PORT_TYPE_MIDI; + callback(sendHost, sendOSC, + ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, + port.group, + static_cast(port.port), + static_cast(port.flags), + 0, 0.0f, + port.strVal); + } - PortNameToId portNameToId; - portNameToId.setData(groupId, ++fUsedPorts.lastId, shortPortName, fullPortName); + for (LinkedList::Itenerator it = connCallbackData.begin2(); it.valid(); it.next()) + { + const ConnectionToIdData& conn(it.getValue(connFallback)); - callback(sendHost, sendOSC, - ENGINE_CALLBACK_PATCHBAY_PORT_ADDED, - portNameToId.group, - static_cast(portNameToId.port), - static_cast(canvasPortFlags), - 0, 0.0f, - portNameToId.name); - fUsedPorts.list.append(portNameToId); + callback(sendHost, sendOSC, + ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED, + conn.id, + 0, 0, 0, 0.0f, + conn.strVal); + } + + groupCallbackData.clear(); + portsCallbackData.clear(); + connCallbackData.clear(); } #endif diff --git a/source/backend/engine/CarlaEngineNative.cpp b/source/backend/engine/CarlaEngineNative.cpp index 19e82c922..91209d4fd 100644 --- a/source/backend/engine/CarlaEngineNative.cpp +++ b/source/backend/engine/CarlaEngineNative.cpp @@ -1816,6 +1816,22 @@ bool CarlaEngineNativeUI::msgReceived(const char* const msg) noexcept ok = fEngine->patchbayDisconnect(external, connectionId); } CARLA_SAFE_EXCEPTION("patchbayDisconnect"); } + else if (std::strcmp(msg, "patchbay_set_group_pos") == 0) + { + bool external; + uint32_t groupId; + int x1, y1, x2, y2; + CARLA_SAFE_ASSERT_RETURN(readNextLineAsBool(external), true); + CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(groupId), true); + CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(x1), true); + CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(y1), true); + CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(x2), true); + CARLA_SAFE_ASSERT_RETURN(readNextLineAsInt(y2), true); + + try { + ok = fEngine->patchbaySetGroupPos(true, false, external, groupId, x1, y1, x2, y2); + } CARLA_SAFE_EXCEPTION("patchbaySetGroupPos"); + } else if (std::strcmp(msg, "patchbay_refresh") == 0) { bool external; diff --git a/source/frontend/carla_backend.py b/source/frontend/carla_backend.py index 8f41f3016..efcebcdf1 100644 --- a/source/frontend/carla_backend.py +++ b/source/frontend/carla_backend.py @@ -818,6 +818,14 @@ ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED = 45 # @a valueStr New mapped range as "%f:%f" syntax ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED = 46 +# A patchbay client position has changed. +# @a pluginId Client Id +# @a value1 X position 1 +# @a value2 Y position 1 +# @a value3 X position 2 +# @a valuef Y position 2 +ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED = 47 + # ------------------------------------------------------------------------------------------------------------ # NSM Callback Opcode # NSM callback opcodes. @@ -1678,6 +1686,14 @@ class CarlaHostMeta(object): def patchbay_disconnect(self, external, connectionId): raise NotImplementedError + # Set the position of a group. + # This is purely cached and saved in the project file, Carla backend does nothing with the value. + # When loading a project, callbacks are used to inform of the previously saved positions. + # @see ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED + @abstractmethod + def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2): + raise NotImplementedError + # Force the engine to resend all patchbay clients, ports and connections again. # @param external Wherever to show external/hardware ports instead of internal ones. # Only valid in patchbay engine mode, other modes will ignore this. @@ -2306,6 +2322,9 @@ class CarlaHostNull(CarlaHostMeta): def patchbay_disconnect(self, external, connectionId): return False + def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2): + return False + def patchbay_refresh(self, external): return False @@ -2624,6 +2643,9 @@ class CarlaHostDLL(CarlaHostMeta): self.lib.carla_patchbay_disconnect.argtypes = (c_void_p, c_bool, c_uint) self.lib.carla_patchbay_disconnect.restype = c_bool + self.lib.carla_patchbay_set_group_pos.argtypes = (c_void_p, c_bool, c_uint, c_int, c_int, c_int, c_int) + self.lib.carla_patchbay_set_group_pos.restype = c_bool + self.lib.carla_patchbay_refresh.argtypes = (c_void_p, c_bool) self.lib.carla_patchbay_refresh.restype = c_bool @@ -2933,6 +2955,9 @@ class CarlaHostDLL(CarlaHostMeta): def patchbay_disconnect(self, external, connectionId): return bool(self.lib.carla_patchbay_disconnect(self.handle, external, connectionId)) + def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2): + return bool(self.lib.carla_patchbay_set_group_pos(self.handle, external, groupId, x1, y1, x2, y2)) + def patchbay_refresh(self, external): return bool(self.lib.carla_patchbay_refresh(self.handle, external)) @@ -3325,6 +3350,9 @@ class CarlaHostPlugin(CarlaHostMeta): def patchbay_disconnect(self, external, connectionId): return self.sendMsgAndSetError(["patchbay_disconnect", external, connectionId]) + def patchbay_set_group_pos(self, external, groupId, x1, y1, x2, y2): + return self.sendMsgAndSetError(["patchbay_set_group_pos", external, groupId, x1, y1, x2, y2]) + def patchbay_refresh(self, external): return self.sendMsgAndSetError(["patchbay_refresh", external]) diff --git a/source/frontend/carla_backend_qt.py b/source/frontend/carla_backend_qt.py index 008521cea..152b29fa8 100644 --- a/source/frontend/carla_backend_qt.py +++ b/source/frontend/carla_backend_qt.py @@ -56,6 +56,7 @@ class CarlaHostSignals(QObject): PatchbayClientRemovedCallback = pyqtSignal(int) PatchbayClientRenamedCallback = pyqtSignal(int, str) PatchbayClientDataChangedCallback = pyqtSignal(int, int, int) + PatchbayClientPositionChangedCallback = pyqtSignal(int, int, int, int, int) PatchbayPortAddedCallback = pyqtSignal(int, int, int, int, str) PatchbayPortRemovedCallback = pyqtSignal(int, int) PatchbayPortChangedCallback = pyqtSignal(int, int, int, int, str) diff --git a/source/frontend/carla_host.py b/source/frontend/carla_host.py index 2c3c17e02..eab6b2238 100644 --- a/source/frontend/carla_host.py +++ b/source/frontend/carla_host.py @@ -505,7 +505,6 @@ class HostWindow(QMainWindow): self.ui.graphicsView.verticalScrollBar().valueChanged.connect(self.slot_verticalScrollBarChanged) self.ui.miniCanvasPreview.miniCanvasMoved.connect(self.slot_miniCanvasMoved) self.scene.scaleChanged.connect(self.slot_canvasScaleChanged) - self.scene.sceneGroupMoved.connect(self.slot_canvasItemMoved) self.scene.pluginSelected.connect(self.slot_canvasPluginSelected) self.scene.selectionChanged.connect(self.slot_canvasSelectionChanged) @@ -533,6 +532,7 @@ class HostWindow(QMainWindow): host.PatchbayClientRemovedCallback.connect(self.slot_handlePatchbayClientRemovedCallback) host.PatchbayClientRenamedCallback.connect(self.slot_handlePatchbayClientRenamedCallback) host.PatchbayClientDataChangedCallback.connect(self.slot_handlePatchbayClientDataChangedCallback) + host.PatchbayClientPositionChangedCallback.connect(self.slot_handlePatchbayClientPositionChangedCallback) host.PatchbayPortAddedCallback.connect(self.slot_handlePatchbayPortAddedCallback) host.PatchbayPortRemovedCallback.connect(self.slot_handlePatchbayPortRemovedCallback) host.PatchbayPortChangedCallback.connect(self.slot_handlePatchbayPortChangedCallback) @@ -713,9 +713,6 @@ class HostWindow(QMainWindow): # -------------------------------------------------------------------------------------------------------- # Files - def makeExtraFilename(self): - return self.fProjectFilename.rsplit(".",1)[0]+".json" - def loadProjectNow(self): if not self.fProjectFilename: return qCritical("ERROR: loading project without filename set") @@ -748,14 +745,6 @@ class HostWindow(QMainWindow): QMessageBox.Ok, QMessageBox.Ok) return - if not self.fWithCanvas: - return - - with open(self.makeExtraFilename(), 'w') as fh: - json.dump({ - 'canvas': patchcanvas.saveGroupPositions(), - }, fh) - def projectLoadingStarted(self): self.ui.rack.setEnabled(False) self.ui.graphicsView.setEnabled(False) @@ -769,7 +758,7 @@ class HostWindow(QMainWindow): QTimer.singleShot(1000, self.slot_canvasRefresh) - extrafile = self.makeExtraFilename() + extrafile = self.fProjectFilename.rsplit(".",1)[0]+".json" if not os.path.exists(extrafile): return @@ -1431,7 +1420,7 @@ class HostWindow(QMainWindow): pFeatures.group_rename = False pFeatures.port_info = False pFeatures.port_rename = False - pFeatures.handle_group_pos = True + pFeatures.handle_group_pos = False patchcanvas.setOptions(pOptions) patchcanvas.setFeatures(pFeatures) @@ -1567,10 +1556,6 @@ class HostWindow(QMainWindow): # -------------------------------------------------------------------------------------------------------- # Canvas (canvas callbacks) - @pyqtSlot(int, int, QPointF) - def slot_canvasItemMoved(self, group_id, split_mode, pos): - self.updateMiniCanvasLater() - @pyqtSlot() def slot_canvasSelectionChanged(self): self.updateMiniCanvasLater() @@ -1668,6 +1653,10 @@ class HostWindow(QMainWindow): patchcanvas.setGroupAsPlugin(clientId, pluginId, hasCustomUI, hasInlineDisplay) + @pyqtSlot(int, int, int, int, int) + def slot_handlePatchbayClientPositionChangedCallback(self, clientId, x1, y1, x2, y2): + patchcanvas.setGroupPosFull(clientId, x1, y1, x2, y2) + @pyqtSlot(int, int, int, int, str) def slot_handlePatchbayPortAddedCallback(self, clientId, portId, portFlags, portGroupId, portName): if portFlags & PATCHBAY_PORT_IS_INPUT: @@ -2720,6 +2709,9 @@ class HostWindow(QMainWindow): def canvasCallback(action, value1, value2, valueStr): host = gCarla.gui.host + if gCarla.gui.fCustomStopAction == HostWindow.CUSTOM_ACTION_APP_CLOSE: + return + if action == patchcanvas.ACTION_GROUP_INFO: pass @@ -2736,6 +2728,14 @@ def canvasCallback(action, value1, value2, valueStr): patchcanvas.joinGroup(groupId) gCarla.gui.updateMiniCanvasLater() + elif action == patchcanvas.ACTION_GROUP_POSITION: + if gCarla.gui.fIsProjectLoading or not host.is_engine_running(): + return + groupId = value1 + x1, y1, x2, y2 = tuple(int(i) for i in valueStr.split(":")) + host.patchbay_set_group_pos(gCarla.gui.fExternalPatchbay, groupId, x1, y1, x2, y2) + gCarla.gui.updateMiniCanvasLater() + elif action == patchcanvas.ACTION_PORT_INFO: pass @@ -2743,7 +2743,7 @@ def canvasCallback(action, value1, value2, valueStr): pass elif action == patchcanvas.ACTION_PORTS_CONNECT: - gOut, pOut, gIn, pIn = [int(i) for i in valueStr.split(":")] + gOut, pOut, gIn, pIn = tuple(int(i) for i in valueStr.split(":")) if not host.patchbay_connect(gCarla.gui.fExternalPatchbay, gOut, pOut, gIn, pIn): print("Connection failed:", host.get_last_error()) @@ -2875,6 +2875,8 @@ def engineCallback(host, action, pluginId, value1, value2, value3, valuef, value host.PatchbayClientRenamedCallback.emit(pluginId, valueStr) elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_DATA_CHANGED: host.PatchbayClientDataChangedCallback.emit(pluginId, value1, value2) + elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED: + host.PatchbayClientPositionChangedCallback.emit(pluginId, value1, value2, value3, int(round(valuef))) elif action == ENGINE_CALLBACK_PATCHBAY_PORT_ADDED: host.PatchbayPortAddedCallback.emit(pluginId, value1, value2, value3, valueStr) elif action == ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED: diff --git a/source/frontend/patchcanvas/__init__.py b/source/frontend/patchcanvas/__init__.py index c7d90f6dc..cada20c53 100644 --- a/source/frontend/patchcanvas/__init__.py +++ b/source/frontend/patchcanvas/__init__.py @@ -49,18 +49,19 @@ ACTION_GROUP_INFO = 0 # group_id, N, N ACTION_GROUP_RENAME = 1 # group_id, N, N ACTION_GROUP_SPLIT = 2 # group_id, N, N ACTION_GROUP_JOIN = 3 # group_id, N, N -ACTION_PORT_INFO = 4 # group_id, port_id, N -ACTION_PORT_RENAME = 5 # group_id, port_id, N -ACTION_PORTS_CONNECT = 6 # N, N, "outG:outP:inG:inP" -ACTION_PORTS_DISCONNECT = 7 # conn_id, N, N -ACTION_PLUGIN_CLONE = 8 # plugin_id, N, N -ACTION_PLUGIN_EDIT = 9 # plugin_id, N, N -ACTION_PLUGIN_RENAME = 10 # plugin_id, N, N -ACTION_PLUGIN_REPLACE = 11 # plugin_id, N, N -ACTION_PLUGIN_REMOVE = 12 # plugin_id, N, N -ACTION_PLUGIN_SHOW_UI = 13 # plugin_id, N, N -ACTION_BG_RIGHT_CLICK = 14 # N, N, N -ACTION_INLINE_DISPLAY = 15 # plugin_id, N, N +ACTION_GROUP_POSITION = 4 # group_id, N, N, "x1:y1:x2:y2" +ACTION_PORT_INFO = 5 # group_id, port_id, N +ACTION_PORT_RENAME = 6 # group_id, port_id, N +ACTION_PORTS_CONNECT = 7 # N, N, "outG:outP:inG:inP" +ACTION_PORTS_DISCONNECT = 8 # conn_id, N, N +ACTION_PLUGIN_CLONE = 9 # plugin_id, N, N +ACTION_PLUGIN_EDIT = 10 # plugin_id, N, N +ACTION_PLUGIN_RENAME = 11 # plugin_id, N, N +ACTION_PLUGIN_REPLACE = 12 # plugin_id, N, N +ACTION_PLUGIN_REMOVE = 13 # plugin_id, N, N +ACTION_PLUGIN_SHOW_UI = 14 # plugin_id, N, N +ACTION_BG_RIGHT_CLICK = 15 # N, N, N +ACTION_INLINE_DISPLAY = 16 # plugin_id, N, N # Icon ICON_APPLICATION = 0 @@ -134,6 +135,7 @@ class Canvas(object): self.connection_list = [] self.animation_list = [] self.group_plugin_map = {} + self.old_group_pos = {} self.callback = self.callback self.debug = False diff --git a/source/frontend/patchcanvas/canvasbox.py b/source/frontend/patchcanvas/canvasbox.py index 4cabace00..2b731288f 100644 --- a/source/frontend/patchcanvas/canvasbox.py +++ b/source/frontend/patchcanvas/canvasbox.py @@ -22,9 +22,9 @@ from sip import voidptr from struct import pack -from PyQt5.QtCore import qCritical, Qt, QPointF, QRectF, QTimer +from PyQt5.QtCore import pyqtSignal, pyqtSlot, qCritical, Qt, QPointF, QRectF, QTimer from PyQt5.QtGui import QCursor, QFont, QFontMetrics, QImage, QLinearGradient, QPainter, QPen -from PyQt5.QtWidgets import QGraphicsItem, QMenu +from PyQt5.QtWidgets import QGraphicsItem, QGraphicsObject, QMenu # ------------------------------------------------------------------------------------------------------------ # Imports (Custom) @@ -75,13 +75,17 @@ class cb_line_t(object): # ------------------------------------------------------------------------------------------------------------ -class CanvasBox(QGraphicsItem): +class CanvasBox(QGraphicsObject): + # signals + positionChanged = pyqtSignal(int, bool, int, int) + + # enums INLINE_DISPLAY_DISABLED = 0 INLINE_DISPLAY_ENABLED = 1 INLINE_DISPLAY_CACHED = 2 def __init__(self, group_id, group_name, icon, parent=None): - QGraphicsItem.__init__(self) + QGraphicsObject.__init__(self) self.setParentItem(parent) # Save Variables, useful for later @@ -109,6 +113,7 @@ class CanvasBox(QGraphicsItem): self.m_inline_data = None self.m_inline_image = None self.m_inline_scaling = 1.0 + self.m_will_signal_pos_change = False self.m_port_list_ids = [] self.m_connection_lines = [] @@ -153,6 +158,10 @@ class CanvasBox(QGraphicsItem): self.updatePositions() + self.visibleChanged.connect(self.slot_signalPositionChangedLater) + self.xChanged.connect(self.slot_signalPositionChangedLater) + self.yChanged.connect(self.slot_signalPositionChangedLater) + canvas.scene.addItem(self) QTimer.singleShot(0, self.fixPos) @@ -270,21 +279,31 @@ class CanvasBox(QGraphicsItem): return qCritical("PatchCanvas::CanvasBox.removeLineFromGroup(%i) - unable to find line to remove" % connection_id) - def checkItemPos(self): - if not canvas.size_rect.isNull(): - pos = self.scenePos() - if not (canvas.size_rect.contains(pos) and - canvas.size_rect.contains(pos + QPointF(self.p_width, self.p_height))): - if pos.x() < canvas.size_rect.x(): - self.setPos(canvas.size_rect.x(), pos.y()) - elif pos.x() + self.p_width > canvas.size_rect.width(): - self.setPos(canvas.size_rect.width() - self.p_width, pos.y()) - - pos = self.scenePos() - if pos.y() < canvas.size_rect.y(): - self.setPos(pos.x(), canvas.size_rect.y()) - elif pos.y() + self.p_height > canvas.size_rect.height(): - self.setPos(pos.x(), canvas.size_rect.height() - self.p_height) + def checkItemPos(self, blockSignals): + if canvas.size_rect.isNull(): + return + + pos = self.scenePos() + if (canvas.size_rect.contains(pos) and + canvas.size_rect.contains(pos + QPointF(self.p_width, self.p_height))): + return + + if blockSignals: + self.blockSignals(True) + + if pos.x() < canvas.size_rect.x(): + self.setPos(canvas.size_rect.x(), pos.y()) + elif pos.x() + self.p_width > canvas.size_rect.width(): + self.setPos(canvas.size_rect.width() - self.p_width, pos.y()) + + pos = self.scenePos() + if pos.y() < canvas.size_rect.y(): + self.setPos(pos.x(), canvas.size_rect.y()) + elif pos.y() + self.p_height > canvas.size_rect.height(): + self.setPos(pos.x(), canvas.size_rect.height() - self.p_height) + + if blockSignals: + self.blockSignals(False) def removeIconFromScene(self): if self.icon_svg is None: @@ -389,6 +408,17 @@ class CanvasBox(QGraphicsItem): connection.widget.setZValue(z_value) + def triggerSignalPositionChanged(self): + self.positionChanged.emit(self.m_group_id, self.m_splitted, self.x(), self.y()) + self.m_will_signal_pos_change = False + + @pyqtSlot() + def slot_signalPositionChangedLater(self): + if self.m_will_signal_pos_change: + return + self.m_will_signal_pos_change = True + QTimer.singleShot(0, self.triggerSignalPositionChanged) + def type(self): return CanvasBoxType @@ -570,14 +600,14 @@ class CanvasBox(QGraphicsItem): event.accept() canvas.callback(ACTION_PLUGIN_REMOVE, self.m_plugin_id, 0, "") return - QGraphicsItem.keyPressEvent(self, event) + QGraphicsObject.keyPressEvent(self, event) def hoverEnterEvent(self, event): if options.auto_select_items: if len(canvas.scene.selectedItems()) > 0: canvas.scene.clearSelection() self.setSelected(True) - QGraphicsItem.hoverEnterEvent(self, event) + QGraphicsObject.hoverEnterEvent(self, event) def mouseDoubleClickEvent(self, event): if self.m_plugin_id >= 0: @@ -585,7 +615,7 @@ class CanvasBox(QGraphicsItem): canvas.callback(ACTION_PLUGIN_SHOW_UI if self.m_plugin_ui else ACTION_PLUGIN_EDIT, self.m_plugin_id, 0, "") return - QGraphicsItem.mouseDoubleClickEvent(self, event) + QGraphicsObject.mouseDoubleClickEvent(self, event) def mousePressEvent(self, event): canvas.last_z_value += 1 @@ -612,7 +642,7 @@ class CanvasBox(QGraphicsItem): else: self.m_mouse_down = False - QGraphicsItem.mousePressEvent(self, event) + QGraphicsObject.mousePressEvent(self, event) def mouseMoveEvent(self, event): if self.m_mouse_down: @@ -620,7 +650,7 @@ class CanvasBox(QGraphicsItem): self.setCursor(QCursor(Qt.SizeAllCursor)) self.m_cursor_moving = True self.repaintLines() - QGraphicsItem.mouseMoveEvent(self, event) + QGraphicsObject.mouseMoveEvent(self, event) def mouseReleaseEvent(self, event): if self.m_cursor_moving: @@ -628,11 +658,13 @@ class CanvasBox(QGraphicsItem): QTimer.singleShot(0, self.fixPos) self.m_mouse_down = False self.m_cursor_moving = False - QGraphicsItem.mouseReleaseEvent(self, event) + QGraphicsObject.mouseReleaseEvent(self, event) def fixPos(self): + self.blockSignals(True) self.setX(round(self.x())) self.setY(round(self.y())) + self.blockSignals(False) def boundingRect(self): return QRectF(0, 0, self.p_width, self.p_height) diff --git a/source/frontend/patchcanvas/patchcanvas.py b/source/frontend/patchcanvas/patchcanvas.py index 04cab3b6f..435aafdf0 100644 --- a/source/frontend/patchcanvas/patchcanvas.py +++ b/source/frontend/patchcanvas/patchcanvas.py @@ -138,6 +138,36 @@ class CanvasObject(QObject): CanvasCallback(ACTION_PORTS_DISCONNECT, connectionId, 0, "") + @pyqtSlot(int, bool, int, int) + def boxPositionChanged(self, groupId, split, x, y): + x2 = y2 = 0 + + if split: + for group in canvas.group_list: + if group.group_id == groupId: + if group.split: + pos = group.widgets[1].pos() + x2 = pos.x() + y2 = pos.y() + break + + valueStr = "%i:%i:%i:%i" % (x, y, x2, y2) + CanvasCallback(ACTION_GROUP_POSITION, groupId, 0, valueStr) + + @pyqtSlot(int, bool, int, int) + def sboxPositionChanged(self, groupId, split, x2, y2): + x = y = 0 + + for group in canvas.group_list: + if group.group_id == groupId: + pos = group.widgets[0].pos() + x = pos.x() + y = pos.y() + break + + valueStr = "%i:%i:%i:%i" % (x, y, x2, y2) + CanvasCallback(ACTION_GROUP_POSITION, groupId, 0, valueStr) + # ------------------------------------------------------------------------------------------------------------ def getStoredCanvasPosition(key, fallback_pos): @@ -201,11 +231,17 @@ def clear(): if canvas.debug: print("PatchCanvas::clear()") + group_pos = {} group_list_ids = [] port_list_ids = [] connection_list_ids = [] for group in canvas.group_list: + group_pos[group.group_name] = ( + group.split, + group.widgets[0].pos(), + group.widgets[1].pos() if group.split else None, + ) group_list_ids.append(group.group_id) for port in canvas.port_list: @@ -230,6 +266,7 @@ def clear(): canvas.port_list = [] canvas.connection_list = [] canvas.group_plugin_map = {} + canvas.old_group_pos = group_pos canvas.scene.clearSelection() @@ -278,6 +315,8 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION): group_id, group_name.encode(), split2str(split), icon2str(icon))) return + old_matching_group = canvas.old_group_pos.pop(group_name, None) + if split == SPLIT_UNDEF: isHardware = bool(icon == ICON_HARDWARE) @@ -285,8 +324,11 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION): split = getStoredCanvasSplit(group_name, SPLIT_YES if isHardware else split) elif isHardware: split = SPLIT_YES + elif old_matching_group is not None and old_matching_group[0]: + split = SPLIT_YES group_box = CanvasBox(group_id, group_name, icon) + group_box.positionChanged.connect(canvas.qobject.boxPositionChanged) group_dict = group_dict_t() group_dict.group_id = group_id @@ -301,20 +343,29 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION): if split == SPLIT_YES: group_box.setSplit(True, PORT_MODE_OUTPUT) + group_box.blockSignals(True) if features.handle_group_pos: group_box.setPos(getStoredCanvasPosition(group_name + "_OUTPUT", CanvasGetNewGroupPos(False))) + elif old_matching_group is not None: + group_box.setPos(old_matching_group[1]) else: group_box.setPos(CanvasGetNewGroupPos(False)) + group_box.blockSignals(False) group_sbox = CanvasBox(group_id, group_name, icon) group_sbox.setSplit(True, PORT_MODE_INPUT) + group_sbox.positionChanged.connect(canvas.qobject.sboxPositionChanged) group_dict.widgets[1] = group_sbox + group_sbox.blockSignals(True) if features.handle_group_pos: group_sbox.setPos(getStoredCanvasPosition(group_name + "_INPUT", CanvasGetNewGroupPos(True))) + elif old_matching_group is not None and old_matching_group[0]: + group_sbox.setPos(old_matching_group[2]) else: group_sbox.setPos(CanvasGetNewGroupPos(True)) + group_sbox.blockSignals(False) canvas.last_z_value += 1 group_sbox.setZValue(canvas.last_z_value) @@ -322,19 +373,21 @@ def addGroup(group_id, group_name, split=SPLIT_UNDEF, icon=ICON_APPLICATION): if options.eyecandy == EYECANDY_FULL and not options.auto_hide_groups: CanvasItemFX(group_sbox, True, False) - group_sbox.checkItemPos() + group_sbox.checkItemPos(True) else: group_box.setSplit(False) if features.handle_group_pos: group_box.setPos(getStoredCanvasPosition(group_name, CanvasGetNewGroupPos(False))) + elif old_matching_group is not None: + group_box.setPos(old_matching_group[1]) else: # Special ladish fake-split groups horizontal = bool(icon == ICON_HARDWARE or icon == ICON_LADISH_ROOM) group_box.setPos(CanvasGetNewGroupPos(horizontal)) - group_box.checkItemPos() + group_box.checkItemPos(True) canvas.last_z_value += 1 group_box.setZValue(canvas.last_z_value) @@ -630,10 +683,14 @@ def restoreGroupPositions(dataList): if group is None: continue + group.widgets[0].blockSignals(True) group.widgets[0].setPos(data['pos1x'], data['pos1y']) + group.widgets[0].blockSignals(False) if group.split and group.widgets[1]: + group.widgets[1].blockSignals(True) group.widgets[1].setPos(data['pos2x'], data['pos2y']) + group.widgets[1].blockSignals(False) def setGroupPos(group_id, group_pos_x, group_pos_y): setGroupPosFull(group_id, group_pos_x, group_pos_y, group_pos_x, group_pos_y) @@ -645,10 +702,14 @@ def setGroupPosFull(group_id, group_pos_x_o, group_pos_y_o, group_pos_x_i, group for group in canvas.group_list: if group.group_id == group_id: + group.widgets[0].blockSignals(True) group.widgets[0].setPos(group_pos_x_o, group_pos_y_o) + group.widgets[0].blockSignals(False) if group.split and group.widgets[1]: + group.widgets[1].blockSignals(True) group.widgets[1].setPos(group_pos_x_i, group_pos_y_i) + group.widgets[1].blockSignals(False) QTimer.singleShot(0, canvas.scene.update) return diff --git a/source/frontend/patchcanvas/scene.py b/source/frontend/patchcanvas/scene.py index 19f716743..99f26df12 100644 --- a/source/frontend/patchcanvas/scene.py +++ b/source/frontend/patchcanvas/scene.py @@ -58,7 +58,6 @@ class RubberbandRect(QGraphicsRectItem): class PatchScene(QGraphicsScene): scaleChanged = pyqtSignal(float) - sceneGroupMoved = pyqtSignal(int, int, QPointF) pluginSelected = pyqtSignal(list) def __init__(self, parent, view): @@ -367,8 +366,7 @@ class PatchScene(QGraphicsScene): items_list = self.selectedItems() for item in items_list: if item and item.isVisible() and item.type() == CanvasBoxType: - item.checkItemPos() - self.sceneGroupMoved.emit(item.getGroupId(), item.getSplittedMode(), item.scenePos()) + item.checkItemPos(False) if len(items_list) > 1: canvas.scene.update() diff --git a/source/utils/CarlaBackendUtils.hpp b/source/utils/CarlaBackendUtils.hpp index d37711154..2feaee2e2 100644 --- a/source/utils/CarlaBackendUtils.hpp +++ b/source/utils/CarlaBackendUtils.hpp @@ -315,6 +315,8 @@ const char* EngineCallbackOpcode2Str(const EngineCallbackOpcode opcode) noexcept return "ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED"; case ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED: return "ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED"; + case ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED: + return "ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED"; } carla_stderr("CarlaBackend::EngineCallbackOpcode2Str(%i) - invalid opcode", opcode); diff --git a/source/utils/CarlaPatchbayUtils.hpp b/source/utils/CarlaPatchbayUtils.hpp index e4b5d1a82..3c0880541 100644 --- a/source/utils/CarlaPatchbayUtils.hpp +++ b/source/utils/CarlaPatchbayUtils.hpp @@ -1,6 +1,6 @@ /* * Carla patchbay utils - * Copyright (C) 2011-2017 Filipe Coelho + * Copyright (C) 2011-2020 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -27,7 +27,7 @@ struct GroupNameToId { uint group; - char name[STR_MAX+1]; // globally unique + char name[STR_MAX]; // globally unique void clear() noexcept { @@ -43,15 +43,15 @@ struct GroupNameToId { void rename(const char n[]) noexcept { - std::strncpy(name, n, STR_MAX); - name[STR_MAX] = '\0'; + std::strncpy(name, n, STR_MAX-1); + name[STR_MAX-1] = '\0'; } bool operator==(const GroupNameToId& groupNameToId) const noexcept { if (groupNameToId.group != group) return false; - if (std::strncmp(groupNameToId.name, name, STR_MAX) != 0) + if (std::strncmp(groupNameToId.name, name, STR_MAX-1) != 0) return false; return true; } @@ -65,10 +65,12 @@ struct GroupNameToId { struct PatchbayGroupList { uint lastId; LinkedList list; + CarlaMutex mutex; PatchbayGroupList() noexcept : lastId(0), - list() {} + list(), + mutex() {} void clear() noexcept { @@ -87,8 +89,8 @@ struct PatchbayGroupList { struct PortNameToId { uint group; uint port; - char name[STR_MAX+1]; // locally unique (within the same group) - char fullName[STR_MAX+1]; // globally unique + char name[STR_MAX]; // locally unique (within the same group) + char fullName[STR_MAX]; // globally unique void clear() noexcept { @@ -107,26 +109,26 @@ struct PortNameToId { void setFullName(const char fn[]) noexcept { - std::strncpy(fullName, fn, STR_MAX); - fullName[STR_MAX] = '\0'; + std::strncpy(fullName, fn, STR_MAX-1); + fullName[STR_MAX-1] = '\0'; } void rename(const char n[], const char fn[]) noexcept { - std::strncpy(name, n, STR_MAX); - name[STR_MAX] = '\0'; + std::strncpy(name, n, STR_MAX-1); + name[STR_MAX-1] = '\0'; - std::strncpy(fullName, fn, STR_MAX); - fullName[STR_MAX] = '\0'; + std::strncpy(fullName, fn, STR_MAX-1); + fullName[STR_MAX-1] = '\0'; } bool operator==(const PortNameToId& portNameToId) noexcept { if (portNameToId.group != group || portNameToId.port != port) return false; - if (std::strncmp(portNameToId.name, name, STR_MAX) != 0) + if (std::strncmp(portNameToId.name, name, STR_MAX-1) != 0) return false; - if (std::strncmp(portNameToId.fullName, fullName, STR_MAX) != 0) + if (std::strncmp(portNameToId.fullName, fullName, STR_MAX-1) != 0) return false; return true; } @@ -140,10 +142,12 @@ struct PortNameToId { struct PatchbayPortList { uint lastId; LinkedList list; + CarlaMutex mutex; PatchbayPortList() noexcept : lastId(0), - list() {} + list(), + mutex() {} void clear() noexcept {