From e2a2c45f22854894b1ff21df826938d103e4e184 Mon Sep 17 00:00:00 2001 From: Filipe Coelho Date: Thu, 7 Feb 2019 17:34:51 +0100 Subject: [PATCH] NSM support for JACK Applications (#829) Signed-off-by: falkTX --- resources/ui/carla_add_jack.ui | 150 ++++++-- source/backend/CarlaEngine.hpp | 16 +- source/backend/CarlaHost.h | 5 + source/backend/CarlaStandalone.cpp | 13 +- source/backend/engine/CarlaEngine.cpp | 32 +- source/backend/engine/CarlaEngineInternal.cpp | 1 + source/backend/engine/CarlaEngineInternal.hpp | 1 + source/backend/engine/CarlaEngineNative.cpp | 8 +- source/backend/plugin/CarlaPluginJack.cpp | 338 +++++++++++++++++- .../bridges-plugin/CarlaBridgeSingleLV2.cpp | 2 +- source/frontend/carla_backend.py | 19 +- source/frontend/carla_database.py | 31 +- source/frontend/carla_host.py | 1 + source/libjack/libjack.cpp | 4 +- source/libjack/libjack.hpp | 4 + source/libjack/libjack_port-searching.cpp | 33 +- source/libjack/libjack_ports.cpp | 41 ++- source/modules/water/threads/ChildProcess.cpp | 15 + source/modules/water/threads/ChildProcess.h | 1 + 19 files changed, 627 insertions(+), 88 deletions(-) diff --git a/resources/ui/carla_add_jack.ui b/resources/ui/carla_add_jack.ui index 921f7af38..3d7f81662 100644 --- a/resources/ui/carla_add_jack.ui +++ b/resources/ui/carla_add_jack.ui @@ -6,8 +6,8 @@ 0 0 - 471 - 369 + 496 + 438 @@ -27,39 +27,23 @@ Application - - - - Command: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - + + Qt::Horizontal - QSizePolicy::Ignored + QSizePolicy::Fixed - 87 - 1 + 20 + 60 - - - - + Name: @@ -69,22 +53,124 @@ - - + + + + + Qt::Horizontal - QSizePolicy::Ignored + QSizePolicy::Fixed - 1 - 1 + 20 + 60 + + + + Application: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + + 0 + 0 + + + + From template + + + + + + + + 0 + 0 + + + + Custom + + + true + + + + + + + 1 + + + + + 0 + + + + + Template: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + false + + + + + + + + + 0 + + + + + Command: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + @@ -122,7 +208,7 @@ - 1 + 0 @@ -131,12 +217,12 @@ - Auto + LADISH (SIGUSR1) - LADISH (SIGUSR1) + NSM diff --git a/source/backend/CarlaEngine.hpp b/source/backend/CarlaEngine.hpp index d119e5bc0..f2f950c4c 100644 --- a/source/backend/CarlaEngine.hpp +++ b/source/backend/CarlaEngine.hpp @@ -895,12 +895,24 @@ public: * Load a project file. * @note Already loaded plugins are not removed; call removeAllPlugins() first if needed. */ - bool loadProject(const char* const filename); + bool loadProject(const char* const filename, const bool setAsCurrentProject); /*! * Save current project to a file. */ - bool saveProject(const char* const filename); + bool saveProject(const char* const filename, const bool setAsCurrentProject); + +#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH + /*! + * Get the currently set project filename. + */ + const char* getCurrentProjectFilename() const noexcept; + + /*! + * Clear the currently set project filename. + */ + void clearCurrentProjectFilename() noexcept; +#endif // ------------------------------------------------------------------- // Information (base) diff --git a/source/backend/CarlaHost.h b/source/backend/CarlaHost.h index 4bc6e664f..c683be601 100644 --- a/source/backend/CarlaHost.h +++ b/source/backend/CarlaHost.h @@ -410,6 +410,11 @@ CARLA_EXPORT bool carla_load_project(const char* filename); CARLA_EXPORT bool carla_save_project(const char* filename); #ifndef BUILD_BRIDGE +/*! + * Clear the currently set project filename. + */ +CARLA_EXPORT void carla_clear_project_filename(); + /*! * Connect two patchbay ports. * @param groupIdA Output group diff --git a/source/backend/CarlaStandalone.cpp b/source/backend/CarlaStandalone.cpp index 1e41ca040..35381fe3d 100644 --- a/source/backend/CarlaStandalone.cpp +++ b/source/backend/CarlaStandalone.cpp @@ -812,7 +812,7 @@ bool carla_load_project(const char* filename) carla_debug("carla_load_project(\"%s\")", filename); - return gStandalone.engine->loadProject(filename); + return gStandalone.engine->loadProject(filename, true); } bool carla_save_project(const char* filename) @@ -822,10 +822,19 @@ bool carla_save_project(const char* filename) carla_debug("carla_save_project(\"%s\")", filename); - return gStandalone.engine->saveProject(filename); + return gStandalone.engine->saveProject(filename, true); } #ifndef BUILD_BRIDGE +void carla_clear_project_filename() +{ + CARLA_SAFE_ASSERT_RETURN(gStandalone.engine != nullptr,); + + carla_debug("carla_clear_project_filename()"); + + gStandalone.engine->clearCurrentProjectFilename(); +} + // -------------------------------------------------------------------------------------------------------------------- bool carla_patchbay_connect(uint groupIdA, uint portIdA, uint groupIdB, uint portIdB) diff --git a/source/backend/engine/CarlaEngine.cpp b/source/backend/engine/CarlaEngine.cpp index 2722588db..3cdc62716 100644 --- a/source/backend/engine/CarlaEngine.cpp +++ b/source/backend/engine/CarlaEngine.cpp @@ -990,7 +990,7 @@ bool CarlaEngine::loadFile(const char* const filename) // NOTE: please keep in sync with carla_get_supported_file_extensions!! if (extension == "carxp" || extension == "carxs") - return loadProject(filename); + return loadProject(filename, false); // ------------------------------------------------------------------- @@ -1126,7 +1126,7 @@ bool CarlaEngine::loadFile(const char* const filename) return false; } -bool CarlaEngine::loadProject(const char* const filename) +bool CarlaEngine::loadProject(const char* const filename, const bool setAsCurrentProject) { CARLA_SAFE_ASSERT_RETURN_ERR(pData->isIdling == 0, "An operation is still being processed, please wait for it to finish"); CARLA_SAFE_ASSERT_RETURN_ERR(filename != nullptr && filename[0] != '\0', "Invalid filename"); @@ -1136,11 +1136,18 @@ bool CarlaEngine::loadProject(const char* const filename) File file(jfilename); CARLA_SAFE_ASSERT_RETURN_ERR(file.existsAsFile(), "Requested file does not exist or is not a readable file"); + if (setAsCurrentProject) + { +#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH + pData->currentProjectFilename = filename; +#endif + } + XmlDocument xml(file); return loadProjectInternal(xml); } -bool CarlaEngine::saveProject(const char* const filename) +bool CarlaEngine::saveProject(const char* const filename, const bool setAsCurrentProject) { CARLA_SAFE_ASSERT_RETURN_ERR(filename != nullptr && filename[0] != '\0', "Invalid filename"); carla_debug("CarlaEngine::saveProject(\"%s\")", filename); @@ -1151,6 +1158,13 @@ bool CarlaEngine::saveProject(const char* const filename) const String jfilename = String(CharPointer_UTF8(filename)); File file(jfilename); + if (setAsCurrentProject) + { +#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH + pData->currentProjectFilename = filename; +#endif + } + if (file.replaceWithData(out.getData(), out.getDataSize())) return true; @@ -1158,6 +1172,18 @@ bool CarlaEngine::saveProject(const char* const filename) return false; } +#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH +const char* CarlaEngine::getCurrentProjectFilename() const noexcept +{ + return pData->currentProjectFilename; +} + +void CarlaEngine::clearCurrentProjectFilename() noexcept +{ + pData->currentProjectFilename.clear(); +} +#endif + // ----------------------------------------------------------------------- // Information (base) diff --git a/source/backend/engine/CarlaEngineInternal.cpp b/source/backend/engine/CarlaEngineInternal.cpp index be4182636..0c8448136 100644 --- a/source/backend/engine/CarlaEngineInternal.cpp +++ b/source/backend/engine/CarlaEngineInternal.cpp @@ -375,6 +375,7 @@ CarlaEngine::ProtectedData::ProtectedData(CarlaEngine* const engine) noexcept actionCanceled(false), #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH loadingProject(false), + currentProjectFilename(), #endif hints(0x0), bufferSize(0), diff --git a/source/backend/engine/CarlaEngineInternal.hpp b/source/backend/engine/CarlaEngineInternal.hpp index 5ca2c39ab..7785386b4 100644 --- a/source/backend/engine/CarlaEngineInternal.hpp +++ b/source/backend/engine/CarlaEngineInternal.hpp @@ -223,6 +223,7 @@ struct CarlaEngine::ProtectedData { #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH bool loadingProject; + CarlaString currentProjectFilename; #endif uint hints; diff --git a/source/backend/engine/CarlaEngineNative.cpp b/source/backend/engine/CarlaEngineNative.cpp index 9f7f62e51..693e48d1d 100644 --- a/source/backend/engine/CarlaEngineNative.cpp +++ b/source/backend/engine/CarlaEngineNative.cpp @@ -175,7 +175,7 @@ protected: CARLA_SAFE_ASSERT_RETURN(readNextLineAsString(filename), true); try { - ok = fEngine->loadProject(filename); + ok = fEngine->loadProject(filename, true); } CARLA_SAFE_EXCEPTION("loadProject"); delete[] filename; @@ -187,11 +187,15 @@ protected: CARLA_SAFE_ASSERT_RETURN(readNextLineAsString(filename), true); try { - ok = fEngine->saveProject(filename); + ok = fEngine->saveProject(filename, true); } CARLA_SAFE_EXCEPTION("saveProject"); delete[] filename; } + else if (std::strcmp(msg, "clear_project_filename") == 0) + { + fEngine->clearCurrentProjectFilename(); + } else if (std::strcmp(msg, "patchbay_connect") == 0) { uint32_t groupA, portA, groupB, portB; diff --git a/source/backend/plugin/CarlaPluginJack.cpp b/source/backend/plugin/CarlaPluginJack.cpp index 5945840d9..dce42db92 100644 --- a/source/backend/plugin/CarlaPluginJack.cpp +++ b/source/backend/plugin/CarlaPluginJack.cpp @@ -1,6 +1,6 @@ /* * Carla Plugin JACK - * Copyright (C) 2016-2018 Filipe Coelho + * Copyright (C) 2016-2019 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 @@ -28,6 +28,11 @@ #include "CarlaShmUtils.hpp" #include "CarlaThread.hpp" +#ifdef HAVE_LIBLO +# include "CarlaOscUtils.hpp" +#endif + +#include "water/files/File.h" #include "water/misc/Time.h" #include "water/text/StringArray.h" #include "water/threads/ChildProcess.h" @@ -47,6 +52,28 @@ using water::Time; CARLA_BACKEND_START_NAMESPACE +enum { + FLAG_CONTROL_WINDOW = 0x01, + FLAG_CAPTURE_FIRST_WINDOW = 0x02, + FLAG_BUFFERS_ADDITION_MODE = 0x10, +}; + +enum { + SESSION_MGR_NONE = 0, + SESSION_MGR_AUTO = 1, + SESSION_MGR_JACK = 2, + SESSION_MGR_LADISH = 3, + SESSION_MGR_NSM = 4, +}; + +static size_t safe_rand(const size_t limit) +{ + const int r = std::rand(); + CARLA_SAFE_ASSERT_RETURN(r >= 0, 0); + + return static_cast(r) % limit; +} + // ------------------------------------------------------------------------------------------------------------------- // Fallback data @@ -62,17 +89,22 @@ public: kEngine(engine), kPlugin(plugin), fShmIds(), - fNumPorts(), + fSetupLabel(), +#ifdef HAVE_LIBLO + fOscClientAddress(nullptr), + fOscServer(nullptr), + fProject(), +#endif fProcess() {} - void setData(const char* const shmIds, const char* const numPorts) noexcept + void setData(const char* const shmIds, const char* const setupLabel) noexcept { CARLA_SAFE_ASSERT_RETURN(shmIds != nullptr && shmIds[0] != '\0',); - CARLA_SAFE_ASSERT_RETURN(numPorts != nullptr && numPorts[0] != '\0',); + CARLA_SAFE_ASSERT_RETURN(setupLabel != nullptr && setupLabel[0] != '\0',); CARLA_SAFE_ASSERT(! isThreadRunning()); - fShmIds = shmIds; - fNumPorts = numPorts; + fShmIds = shmIds; + fSetupLabel = setupLabel; } uintptr_t getProcessID() const noexcept @@ -82,9 +114,156 @@ public: return (uintptr_t)fProcess->getPID(); } + void sendTerminate() const noexcept + { + CARLA_SAFE_ASSERT_RETURN(fProcess != nullptr,); + + fProcess->terminate(); + } + +#ifdef HAVE_LIBLO + void nsmSave(const char* const setupLabel) + { + if (fOscClientAddress == nullptr) + return; + + if (fSetupLabel != setupLabel) + fSetupLabel = setupLabel; + + maybeOpenFirstTime(); + + lo_send_from(fOscClientAddress, fOscServer, LO_TT_IMMEDIATE, "/nsm/client/save", ""); + } + + void nsmShowGui(const bool yesNo) + { + if (fOscClientAddress == nullptr) + return; + + lo_send_from(fOscClientAddress, fOscServer, LO_TT_IMMEDIATE, + yesNo ? "/nsm/client/show_optional_gui" + : "/nsm/client/hide_optional_gui", ""); + } +#endif + protected: +#ifdef HAVE_LIBLO + static void _osc_error_handler(int num, const char* msg, const char* path) + { + carla_stderr2("CarlaPluginJackThread::_osc_error_handler(%i, \"%s\", \"%s\")", num, msg, path); + } + + static int _broadcast_handler(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* data) + { + CARLA_SAFE_ASSERT_RETURN(data != nullptr, 0); + carla_stdout("CarlaPluginJackThread::_broadcast_handler(%s, %s, %p, %i)", path, types, argv, argc); + + return ((CarlaPluginJackThread*)data)->handleBroadcast(path, types, argv, msg); + } + + void maybeOpenFirstTime() + { + if (fProject.path.isNotEmpty()) + return; + if (fSetupLabel.length() <= 6) + return; + + if (fProject.init(kEngine->getCurrentProjectFilename(), &fSetupLabel[6])) + { + carla_stdout("Sending first open signal %s %s %s", + fProject.path.buffer(), fProject.display.buffer(), fProject.clientName.buffer()); + + lo_send_from(fOscClientAddress, fOscServer, LO_TT_IMMEDIATE, "/nsm/client/open", "sss", + fProject.path.buffer(), fProject.display.buffer(), fProject.clientName.buffer()); + } + } + + int handleBroadcast(const char* path, const char* types, lo_arg** argv, lo_message msg) + { + if (std::strcmp(path, "/nsm/server/announce") == 0) + { + CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "sssiii") == 0, 0); + + const lo_address msgAddress(lo_message_get_source(msg)); + CARLA_SAFE_ASSERT_RETURN(msgAddress != nullptr, 0); + + char* const msgURL(lo_address_get_url(msgAddress)); + CARLA_SAFE_ASSERT_RETURN(msgURL != nullptr, 0); + + if (fOscClientAddress != nullptr) + lo_address_free(fOscClientAddress); + + fOscClientAddress = lo_address_new_from_url(msgURL); + CARLA_SAFE_ASSERT_RETURN(fOscClientAddress != nullptr, 0); + + fProject.appName = &argv[0]->s; + + static const char* const method = "/nsm/server/announce"; + static const char* const message = "Howdy, what took you so long?"; + static const char* const smName = "Carla"; + static const char* const features = ":server-control:optional-gui:"; + + lo_send_from(fOscClientAddress, fOscServer, LO_TT_IMMEDIATE, "/reply", "ssss", + method, message, smName, features); + + maybeOpenFirstTime(); + } + + else if (std::strcmp(path, "/reply") == 0) + { + CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "ss") == 0, 0); + + const char* const method = &argv[0]->s; + const char* const message = &argv[1]->s; + + carla_stdout("Got reply of '%s' as '%s'", method, message); + + if (std::strcmp(method, "/nsm/client/open") == 0) + { + carla_stdout("Sending 'Session is loaded' to %s", fProject.appName.buffer()); + lo_send_from(fOscClientAddress, fOscServer, LO_TT_IMMEDIATE, "/nsm/client/session_is_loaded", ""); + } + } + + else if (std::strcmp(path, "/nsm/client/gui_is_shown") == 0) + { + CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "") == 0, 0); + + kEngine->callback(ENGINE_CALLBACK_UI_STATE_CHANGED, kPlugin->getId(), 1, 0, 0.0f, nullptr); + } + + else if (std::strcmp(path, "/nsm/client/gui_is_hidden") == 0) + { + CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "") == 0, 0); + + kEngine->callback(ENGINE_CALLBACK_UI_STATE_CHANGED, kPlugin->getId(), 0, 0, 0.0f, nullptr); + } + + return 0; + } +#endif + void run() { +#ifdef HAVE_LIBLO + if (fOscClientAddress != nullptr) + { + lo_address_free(fOscClientAddress); + fOscClientAddress = nullptr; + } + + const int sessionManager = fSetupLabel[4] - '0'; + + if (sessionManager == SESSION_MGR_NSM) + { + // NSM support + fOscServer = lo_server_new_with_proto(nullptr, LO_UDP, _osc_error_handler); + CARLA_SAFE_ASSERT_RETURN(fOscServer != nullptr,); + + lo_server_add_method(fOscServer, nullptr, nullptr, _broadcast_handler, this); + } +#endif + if (fProcess == nullptr) { fProcess = new ChildProcess(); @@ -127,6 +306,7 @@ protected: const ScopedEngineEnvironmentLocker _seel(kEngine); + const ScopedEnvVar sev3("NSM_URL", lo_server_get_url(fOscServer)); const ScopedEnvVar sev2("LD_LIBRARY_PATH", libjackdir.buffer()); const ScopedEnvVar sev1("LD_PRELOAD", ldpreload.isNotEmpty() ? ldpreload.buffer() : nullptr); @@ -135,7 +315,7 @@ protected: else carla_unsetenv("CARLA_FRONTEND_WIN_ID"); - carla_setenv("CARLA_LIBJACK_SETUP", fNumPorts.buffer()); + carla_setenv("CARLA_LIBJACK_SETUP", fSetupLabel.buffer()); carla_setenv("CARLA_SHM_IDS", fShmIds.buffer()); started = fProcess->start(arguments); @@ -149,7 +329,32 @@ protected: } for (; fProcess->isRunning() && ! shouldThreadExit();) - carla_msleep(50); + { +#ifdef HAVE_LIBLO + if (sessionManager == SESSION_MGR_NSM) + { + lo_server_recv_noblock(fOscServer, 50); + } + else +#endif + { + carla_msleep(50); + } + } + +#ifdef HAVE_LIBLO + if (sessionManager == SESSION_MGR_NSM) + { + lo_server_free(fOscServer); + fOscServer = nullptr; + + if (fOscClientAddress != nullptr) + { + lo_address_free(fOscClientAddress); + fOscClientAddress = nullptr; + } + } +#endif // we only get here if bridge crashed or thread asked to exit if (fProcess->isRunning() && shouldThreadExit()) @@ -184,7 +389,42 @@ private: CarlaPlugin* const kPlugin; CarlaString fShmIds; - CarlaString fNumPorts; + CarlaString fSetupLabel; + +#ifdef HAVE_LIBLO + lo_address fOscClientAddress; + lo_server fOscServer; + + struct ProjectData { + CarlaString appName; + CarlaString path; + CarlaString display; + CarlaString clientName; + + ProjectData() + : appName(), + path(), + display(), + clientName() {} + + bool init(const char* const engineProjectFilename, const char* const uniqueCodeID) + { + CARLA_SAFE_ASSERT_RETURN(engineProjectFilename != nullptr && engineProjectFilename[0] != '\0', false); + CARLA_SAFE_ASSERT_RETURN(uniqueCodeID != nullptr && uniqueCodeID[0] != '\0', false); + CARLA_SAFE_ASSERT_RETURN(appName.isNotEmpty(), false); + + const File file(File(engineProjectFilename).withFileExtension(uniqueCodeID)); + + path = file.getFullPathName().toRawUTF8(); + display = file.getFileNameWithoutExtension().toRawUTF8(); + clientName = appName + "." + uniqueCodeID; + + return true; + } + + CARLA_DECLARE_NON_COPY_STRUCT(ProjectData) + } fProject; +#endif ScopedPointer fProcess; @@ -247,6 +487,8 @@ public: fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientQuit); fShmNonRtClientControl.commitWrite(); + fBridgeThread.sendTerminate(); + if (! fTimedOut) waitForClient("stopping", 3000); } @@ -326,6 +568,13 @@ public: void prepareForSave() noexcept override { +#ifdef HAVE_LIBLO + if (fInfo.setupLabel.length() == 6) + setupUniqueProjectID(); + + fBridgeThread.nsmSave(fInfo.setupLabel); +#endif + { const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); @@ -402,6 +651,10 @@ public: CARLA_SAFE_ASSERT_RETURN(restartBridgeThread(),); } +#ifdef HAVE_LIBLO + fBridgeThread.nsmShowGui(yesNo); +#endif + const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); fShmNonRtClientControl.writeOpcode(yesNo ? kPluginBridgeNonRtClientShowUI : kPluginBridgeNonRtClientHideUI); @@ -1236,7 +1489,7 @@ public: // --------------------------------------------------------------- // check setup - if (std::strlen(label) != 6) + if (std::strlen(label) < 6) { pData->engine->setLastError("invalid application setup received"); return false; @@ -1256,7 +1509,11 @@ public: fInfo.setupLabel = label; - const int setupHints = label[5] - '0'; + // --------------------------------------------------------------- + // set project unique id + + if (label[6] == '\0') + setupUniqueProjectID(); // --------------------------------------------------------------- // set info @@ -1306,6 +1563,8 @@ public: // --------------------------------------------------------------- // setup hints and options + const int setupHints = label[5] - '0'; + // FIXME dryWet broken pData->hints = PLUGIN_IS_BRIDGE | PLUGIN_OPTION_FIXED_BUFFERS; #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH @@ -1313,7 +1572,7 @@ public: #endif //fInfo.optionsAvailable = optionAv; - if (setupHints & 0x1) + if (setupHints & FLAG_CONTROL_WINDOW) pData->hints |= PLUGIN_HAS_CUSTOM_UI; // --------------------------------------------------------------- @@ -1328,7 +1587,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(shmIdsStr, label); + fBridgeThread.setData(shmIdsStr, fInfo.setupLabel); } if (! restartBridgeThread()) @@ -1415,16 +1674,45 @@ private: waitForClient("resize-pool", 5000); } - void waitForClient(const char* const action, const uint msecs) + void setupUniqueProjectID() { - CARLA_SAFE_ASSERT_RETURN(! fTimedOut,); - CARLA_SAFE_ASSERT_RETURN(! fTimedError,); + const char* const engineProjectFilename = pData->engine->getCurrentProjectFilename(); + carla_stdout("setupUniqueProjectID %s", engineProjectFilename); - if (fShmRtClientControl.waitForClient(msecs)) + if (engineProjectFilename == nullptr || engineProjectFilename[0] == '\0') return; - fTimedOut = true; - carla_stderr2("waitForClient(%s) timed out", action); + const File file(engineProjectFilename); + CARLA_SAFE_ASSERT_RETURN(file.existsAsFile(),); + CARLA_SAFE_ASSERT_RETURN(file.getFileExtension().isNotEmpty(),); + + char code[6]; + code[5] = '\0'; + + for (;;) + { + static const char* const kValidChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789"; + + static const size_t kValidCharsLen(std::strlen(kValidChars)-1U); + + code[0] = kValidChars[safe_rand(kValidCharsLen)]; + code[1] = kValidChars[safe_rand(kValidCharsLen)]; + code[2] = kValidChars[safe_rand(kValidCharsLen)]; + code[3] = kValidChars[safe_rand(kValidCharsLen)]; + code[4] = kValidChars[safe_rand(kValidCharsLen)]; + + const File newFile(file.withFileExtension(code)); + + if (newFile.existsAsFile()) + continue; + + fInfo.setupLabel += code; + carla_stdout("new label %s", fInfo.setupLabel.buffer()); + break; + } } bool restartBridgeThread() @@ -1515,6 +1803,18 @@ private: return true; } + void waitForClient(const char* const action, const uint msecs) + { + CARLA_SAFE_ASSERT_RETURN(! fTimedOut,); + CARLA_SAFE_ASSERT_RETURN(! fTimedError,); + + if (fShmRtClientControl.waitForClient(msecs)) + return; + + fTimedOut = true; + carla_stderr2("waitForClient(%s) timed out", action); + } + CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginJack) }; diff --git a/source/bridges-plugin/CarlaBridgeSingleLV2.cpp b/source/bridges-plugin/CarlaBridgeSingleLV2.cpp index 21a17ba75..ff04ad869 100644 --- a/source/bridges-plugin/CarlaBridgeSingleLV2.cpp +++ b/source/bridges-plugin/CarlaBridgeSingleLV2.cpp @@ -93,7 +93,7 @@ public: using water::File; const File pluginFile(File::getSpecialLocation(File::currentExecutableFile).withFileExtension("xml")); - if (! loadProject(pluginFile.getFullPathName().toRawUTF8())) + if (! loadProject(pluginFile.getFullPathName().toRawUTF8(), true)) { carla_stderr2("Failed to init plugin, possible reasons: %s", getLastError()); return; diff --git a/source/frontend/carla_backend.py b/source/frontend/carla_backend.py index b486af7f0..1b6ae2b0d 100644 --- a/source/frontend/carla_backend.py +++ b/source/frontend/carla_backend.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Carla Backend code -# Copyright (C) 2011-2018 Filipe Coelho +# Copyright (C) 2011-2019 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 @@ -1386,6 +1386,11 @@ class CarlaHostMeta(object): def save_project(self, filename): raise NotImplementedError + # Clear the currently set project filename. + @abstractmethod + def clear_project_filename(self): + raise NotImplementedError + # Connect two patchbay ports. # @param groupIdA Output group # @param portIdA Output port @@ -1968,6 +1973,9 @@ class CarlaHostNull(CarlaHostMeta): def save_project(self, filename): return False + def clear_project_filename(self): + return + def patchbay_connect(self, groupIdA, portIdA, groupIdB, portIdB): return False @@ -2256,6 +2264,9 @@ class CarlaHostDLL(CarlaHostMeta): self.lib.carla_save_project.argtypes = [c_char_p] self.lib.carla_save_project.restype = c_bool + self.lib.carla_clear_project_filename.argtypes = None + self.lib.carla_clear_project_filename.restype = None + self.lib.carla_patchbay_connect.argtypes = [c_uint, c_uint, c_uint, c_uint] self.lib.carla_patchbay_connect.restype = c_bool @@ -2536,6 +2547,9 @@ class CarlaHostDLL(CarlaHostMeta): def save_project(self, filename): return bool(self.lib.carla_save_project(filename.encode("utf-8"))) + def clear_project_filename(self): + self.lib.carla_clear_project_filename() + def patchbay_connect(self, groupIdA, portIdA, groupIdB, portIdB): return bool(self.lib.carla_patchbay_connect(groupIdA, portIdA, groupIdB, portIdB)) @@ -2879,6 +2893,9 @@ class CarlaHostPlugin(CarlaHostMeta): def save_project(self, filename): return self.sendMsgAndSetError(["save_project", filename]) + def clear_project_filename(self): + return self.sendMsgAndSetError(["clear_project_filename"]) + def patchbay_connect(self, groupIdA, portIdA, groupIdB, portIdB): return self.sendMsgAndSetError(["patchbay_connect", groupIdA, portIdA, groupIdB, portIdB]) diff --git a/source/frontend/carla_database.py b/source/frontend/carla_database.py index 63a7cfba0..41e649168 100755 --- a/source/frontend/carla_database.py +++ b/source/frontend/carla_database.py @@ -1934,6 +1934,10 @@ class JackApplicationW(QDialog): SESSION_MGR_LADISH = 3 SESSION_MGR_NSM = 4 + UI_SESSION_NONE = 0 + UI_SESSION_LADISH = 1 + UI_SESSION_NSM = 2 + FLAG_CONTROL_WINDOW = 0x01 FLAG_CAPTURE_FIRST_WINDOW = 0x02 FLAG_BUFFERS_ADDITION_MODE = 0x10 @@ -1959,6 +1963,7 @@ class JackApplicationW(QDialog): # Set-up connections self.finished.connect(self.slot_saveSettings) + self.ui.cb_session_mgr.currentIndexChanged.connect(self.slot_sessionManagerChanged) self.ui.le_command.textChanged.connect(self.slot_commandChanged) # ------------------------------------------------------------------------------------------------------------------ @@ -1970,16 +1975,13 @@ class JackApplicationW(QDialog): flags = 0x0 if not name: - name = os.path.basename(command.split(" ",1)[0]) + name = os.path.basename(command.split(" ",1)[0]).title() - # TODO finalize flag definitions uiSessionMgrIndex = self.ui.cb_session_mgr.currentIndex() - if uiSessionMgrIndex == 1: - smgr = self.SESSION_MGR_AUTO - elif uiSessionMgrIndex == 2: + if uiSessionMgrIndex == self.UI_SESSION_LADISH: smgr = self.SESSION_MGR_LADISH - #elif uiSessionMgrIndex == 2: - #smgr = self.SESSION_MGR_NSM + elif uiSessionMgrIndex == self.UI_SESSION_NSM: + smgr = self.SESSION_MGR_NSM if self.ui.cb_manage_window.isChecked(): flags |= self.FLAG_CONTROL_WINDOW @@ -1997,6 +1999,15 @@ class JackApplicationW(QDialog): chr(baseIntVal+flags)) return (command, name, labelSetup) + def checkIfButtonBoxShouldBeEnabled(self, index, text): + enabled = len(text) > 0 + + # NSM applications must not be abstract or absolute paths, and must not contain arguments + if enabled and index == self.UI_SESSION_NSM: + enabled = text[0] not in (".", "/") and " " not in text + + self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enabled) + def loadSettings(self): settings = QSettings("falkTX", "CarlaAddJackApp") @@ -2015,7 +2026,11 @@ class JackApplicationW(QDialog): @pyqtSlot(str) def slot_commandChanged(self, text): - self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(len(text) > 0) + self.checkIfButtonBoxShouldBeEnabled(self.ui.cb_session_mgr.currentIndex(), text) + + @pyqtSlot(int) + def slot_sessionManagerChanged(self, index): + self.checkIfButtonBoxShouldBeEnabled(index, self.ui.le_command.text()) @pyqtSlot() def slot_saveSettings(self): diff --git a/source/frontend/carla_host.py b/source/frontend/carla_host.py index 68aacfd6b..84f26e122 100644 --- a/source/frontend/carla_host.py +++ b/source/frontend/carla_host.py @@ -733,6 +733,7 @@ class HostWindow(QMainWindow): self.pluginRemoveAll() self.fProjectFilename = "" self.setProperWindowTitle() + self.host.clear_project_filename() @pyqtSlot() def slot_fileOpen(self): diff --git a/source/libjack/libjack.cpp b/source/libjack/libjack.cpp index 8cf6e9271..851b3e11e 100644 --- a/source/libjack/libjack.cpp +++ b/source/libjack/libjack.cpp @@ -133,7 +133,7 @@ public: CARLA_SAFE_ASSERT_INT2_RETURN(shmIds != nullptr && std::strlen(shmIds) == 6*4, std::strlen(shmIds), 6*4,); const char* const libjackSetup(std::getenv("CARLA_LIBJACK_SETUP")); - CARLA_SAFE_ASSERT_RETURN(libjackSetup != nullptr && std::strlen(libjackSetup) == 6,); + CARLA_SAFE_ASSERT_RETURN(libjackSetup != nullptr && std::strlen(libjackSetup) >= 6,); // make sure we don't get loaded again carla_unsetenv("CARLA_SHM_IDS"); @@ -943,7 +943,7 @@ bool CarlaJackAppClient::handleNonRtData() case kPluginBridgeNonRtClientPrepareForSave: { - if (fSessionManager == 1) // auto + if (fSessionManager == 1 && std::getenv("NSM_URL") == nullptr) // auto { struct sigaction sig; carla_zeroStruct(sig); diff --git a/source/libjack/libjack.hpp b/source/libjack/libjack.hpp index a560c77d4..12b8e6fa5 100644 --- a/source/libjack/libjack.hpp +++ b/source/libjack/libjack.hpp @@ -37,6 +37,7 @@ #endif #include +#include #ifdef __SSE2_MATH__ # include @@ -164,6 +165,8 @@ struct JackClientState { LinkedList midiIns; LinkedList midiOuts; + std::map portNameMapping; + JackShutdownCallback shutdownCb; void* shutdownCbPtr; @@ -195,6 +198,7 @@ struct JackClientState { audioOuts(), midiIns(), midiOuts(), + portNameMapping(), shutdownCb(nullptr), shutdownCbPtr(nullptr), infoShutdownCb(nullptr), diff --git a/source/libjack/libjack_port-searching.cpp b/source/libjack/libjack_port-searching.cpp index 542eeff06..c9b227fca 100644 --- a/source/libjack/libjack_port-searching.cpp +++ b/source/libjack/libjack_port-searching.cpp @@ -104,26 +104,26 @@ const char** jack_get_ports(jack_client_t* client, const char* a, const char* b, CARLA_EXPORT jack_port_t* jack_port_by_name(jack_client_t* client, const char* name) { - carla_stdout("%s(%p, %s) WIP", __FUNCTION__, client, name); + carla_debug("%s(%p, %s)", __FUNCTION__, client, name); JackClientState* const jclient = (JackClientState*)client; CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 0); - const JackServerState& jserver(jclient->server); - const int commonFlags = JackPortIsPhysical|JackPortIsTerminal; - - static JackPortState retPort( - /* name */ nullptr, - /* fullname */ nullptr, - /* index */ 0, - /* flags */ 0x0, - /* isMidi */ false, - /* isSystem */ true, - /* isConnected */ false - ); - if (std::strncmp(name, "system:", 7) == 0) { + static JackPortState retPort( + /* name */ nullptr, + /* fullname */ nullptr, + /* index */ 0, + /* flags */ 0x0, + /* isMidi */ false, + /* isSystem */ true, + /* isConnected */ false + ); + + const JackServerState& jserver(jclient->server); + const int commonFlags = JackPortIsPhysical|JackPortIsTerminal; + std::free(retPort.fullname); retPort.fullname = strdup(name); @@ -188,6 +188,11 @@ jack_port_t* jack_port_by_name(jack_client_t* client, const char* name) return (jack_port_t*)&retPort; } + else + { + if (JackPortState* const port = jclient->portNameMapping[name]) + return (jack_port_t*)port; + } carla_stderr2("jack_port_by_name: invalid port name '%s'", name); return nullptr; diff --git a/source/libjack/libjack_ports.cpp b/source/libjack/libjack_ports.cpp index a31ece5bc..361eaa32e 100644 --- a/source/libjack/libjack_ports.cpp +++ b/source/libjack/libjack_ports.cpp @@ -48,6 +48,7 @@ jack_port_t* jack_port_register(jack_client_t* client, const char* port_name, co jclient->audioIns.append(port); } + jclient->portNameMapping[port->fullname] = port; return (jack_port_t*)port; } @@ -62,6 +63,7 @@ jack_port_t* jack_port_register(jack_client_t* client, const char* port_name, co jclient->audioOuts.append(port); } + jclient->portNameMapping[port->fullname] = port; return (jack_port_t*)port; } @@ -82,6 +84,7 @@ jack_port_t* jack_port_register(jack_client_t* client, const char* port_name, co jclient->midiIns.append(port); } + jclient->portNameMapping[port->fullname] = port; return (jack_port_t*)port; } @@ -96,6 +99,7 @@ jack_port_t* jack_port_register(jack_client_t* client, const char* port_name, co jclient->midiOuts.append(port); } + jclient->portNameMapping[port->fullname] = port; return (jack_port_t*)port; } @@ -130,12 +134,14 @@ int jack_port_unregister(jack_client_t* client, jack_port_t* port) if (jport->flags & JackPortIsInput) { CARLA_SAFE_ASSERT_RETURN(jclient->midiIns.removeOne(jport), ENOENT); + jclient->portNameMapping.erase(jport->fullname); return 0; } if (jport->flags & JackPortIsOutput) { CARLA_SAFE_ASSERT_RETURN(jclient->midiOuts.removeOne(jport), ENOENT); + jclient->portNameMapping.erase(jport->fullname); return 0; } } @@ -144,12 +150,14 @@ int jack_port_unregister(jack_client_t* client, jack_port_t* port) if (jport->flags & JackPortIsInput) { CARLA_SAFE_ASSERT_RETURN(jclient->audioIns.removeOne(jport), ENOENT); + jclient->portNameMapping.erase(jport->fullname); return 0; } if (jport->flags & JackPortIsOutput) { CARLA_SAFE_ASSERT_RETURN(jclient->audioOuts.removeOne(jport), ENOENT); + jclient->portNameMapping.erase(jport->fullname); return 0; } } @@ -325,8 +333,37 @@ int jack_port_set_name(jack_port_t *port, const char *port_name) CARLA_EXPORT int jack_port_rename(jack_client_t* client, jack_port_t *port, const char *port_name) { - carla_stderr2("%s(%p, %p, %s)", __FUNCTION__, client, port, port_name); - return ENOSYS; + carla_stderr2("%s(%p, %p, %s) WIP", __FUNCTION__, client, port, port_name); + + JackClientState* const jclient = (JackClientState*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, EINVAL); + + CARLA_SAFE_ASSERT_RETURN(port_name != nullptr && port_name[0] != '\0', EINVAL); + + JackPortState* const jport = (JackPortState*)port; + CARLA_SAFE_ASSERT_RETURN(jport != nullptr, EINVAL); + CARLA_SAFE_ASSERT_RETURN(! jport->isSystem, EINVAL); + + // TODO: verify uniqueness + + char* const name = strdup(port_name); + CARLA_SAFE_ASSERT_RETURN(name != nullptr, ENOMEM); + + char* const fullname = (char*)std::malloc(STR_MAX); + CARLA_SAFE_ASSERT_RETURN(name != nullptr, ENOMEM); + + std::free(jport->name); + jport->name = strdup(port_name); + + std::snprintf(fullname, STR_MAX, "%s:%s", jclient->name, port_name); + fullname[STR_MAX-1] = '\0'; + + std::free(jport->fullname); + jport->fullname = fullname; + + // TODO: port rename callback + + return 0; } CARLA_EXPORT diff --git a/source/modules/water/threads/ChildProcess.cpp b/source/modules/water/threads/ChildProcess.cpp index d1200296b..58a2f3cb8 100644 --- a/source/modules/water/threads/ChildProcess.cpp +++ b/source/modules/water/threads/ChildProcess.cpp @@ -125,6 +125,11 @@ public: return TerminateProcess (processInfo.hProcess, 0) != FALSE; } + bool terminateProcess() const noexcept + { + return TerminateProcess (processInfo.hProcess, 0) != FALSE; + } + uint32 getExitCode() const noexcept { DWORD exitCode = 0; @@ -238,6 +243,11 @@ public: return ::kill (childPID, SIGKILL) == 0; } + bool terminateProcess() const noexcept + { + return ::kill (childPID, SIGTERM) == 0; + } + uint32 getExitCode() const noexcept { if (childPID != 0) @@ -287,6 +297,11 @@ bool ChildProcess::kill() return activeProcess == nullptr || activeProcess->killProcess(); } +bool ChildProcess::terminate() +{ + return activeProcess == nullptr || activeProcess->terminateProcess(); +} + uint32 ChildProcess::getExitCode() const { return activeProcess != nullptr ? activeProcess->getExitCode() : 0; diff --git a/source/modules/water/threads/ChildProcess.h b/source/modules/water/threads/ChildProcess.h index cf38422c6..891322aa7 100644 --- a/source/modules/water/threads/ChildProcess.h +++ b/source/modules/water/threads/ChildProcess.h @@ -107,6 +107,7 @@ public: result in undefined behaviour. */ bool kill(); + bool terminate(); uint32 getPID() const noexcept;