Signed-off-by: falkTX <falktx@gmail.com>tags/v2.1-alpha2
@@ -6,8 +6,8 @@ | |||
<rect> | |||
<x>0</x> | |||
<y>0</y> | |||
<width>471</width> | |||
<height>369</height> | |||
<width>496</width> | |||
<height>438</height> | |||
</rect> | |||
</property> | |||
<property name="windowTitle"> | |||
@@ -27,39 +27,23 @@ | |||
<string>Application</string> | |||
</property> | |||
<layout class="QGridLayout" name="gridLayout"> | |||
<item row="1" column="0"> | |||
<widget class="QLabel" name="label"> | |||
<property name="text"> | |||
<string>Command:</string> | |||
</property> | |||
<property name="alignment"> | |||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> | |||
</property> | |||
</widget> | |||
</item> | |||
<item row="1" column="1" colspan="3"> | |||
<widget class="QLineEdit" name="le_command"/> | |||
</item> | |||
<item row="2" column="3"> | |||
<spacer name="horizontalSpacer_3"> | |||
<item row="0" column="0" rowspan="3"> | |||
<spacer name="horizontalSpacer_4"> | |||
<property name="orientation"> | |||
<enum>Qt::Horizontal</enum> | |||
</property> | |||
<property name="sizeType"> | |||
<enum>QSizePolicy::Ignored</enum> | |||
<enum>QSizePolicy::Fixed</enum> | |||
</property> | |||
<property name="sizeHint" stdset="0"> | |||
<size> | |||
<width>87</width> | |||
<height>1</height> | |||
<width>20</width> | |||
<height>60</height> | |||
</size> | |||
</property> | |||
</spacer> | |||
</item> | |||
<item row="0" column="1" colspan="3"> | |||
<widget class="QLineEdit" name="le_name"/> | |||
</item> | |||
<item row="0" column="0"> | |||
<item row="0" column="1"> | |||
<widget class="QLabel" name="label_10"> | |||
<property name="text"> | |||
<string>Name:</string> | |||
@@ -69,22 +53,124 @@ | |||
</property> | |||
</widget> | |||
</item> | |||
<item row="3" column="0"> | |||
<spacer name="horizontalSpacer_4"> | |||
<item row="0" column="2" colspan="2"> | |||
<widget class="QLineEdit" name="le_name"/> | |||
</item> | |||
<item row="0" column="4" rowspan="3"> | |||
<spacer name="horizontalSpacer_3"> | |||
<property name="orientation"> | |||
<enum>Qt::Horizontal</enum> | |||
</property> | |||
<property name="sizeType"> | |||
<enum>QSizePolicy::Ignored</enum> | |||
<enum>QSizePolicy::Fixed</enum> | |||
</property> | |||
<property name="sizeHint" stdset="0"> | |||
<size> | |||
<width>1</width> | |||
<height>1</height> | |||
<width>20</width> | |||
<height>60</height> | |||
</size> | |||
</property> | |||
</spacer> | |||
</item> | |||
<item row="1" column="1"> | |||
<widget class="QLabel" name="label_5"> | |||
<property name="text"> | |||
<string>Application:</string> | |||
</property> | |||
<property name="alignment"> | |||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> | |||
</property> | |||
</widget> | |||
</item> | |||
<item row="1" column="2"> | |||
<widget class="QRadioButton" name="rb_template"> | |||
<property name="enabled"> | |||
<bool>false</bool> | |||
</property> | |||
<property name="sizePolicy"> | |||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> | |||
<horstretch>0</horstretch> | |||
<verstretch>0</verstretch> | |||
</sizepolicy> | |||
</property> | |||
<property name="text"> | |||
<string>From template</string> | |||
</property> | |||
</widget> | |||
</item> | |||
<item row="1" column="3"> | |||
<widget class="QRadioButton" name="rb_custom"> | |||
<property name="sizePolicy"> | |||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> | |||
<horstretch>0</horstretch> | |||
<verstretch>0</verstretch> | |||
</sizepolicy> | |||
</property> | |||
<property name="text"> | |||
<string>Custom</string> | |||
</property> | |||
<property name="checked"> | |||
<bool>true</bool> | |||
</property> | |||
</widget> | |||
</item> | |||
<item row="2" column="1" colspan="3"> | |||
<widget class="QStackedWidget" name="stackedWidget"> | |||
<property name="currentIndex"> | |||
<number>1</number> | |||
</property> | |||
<widget class="QWidget" name="page_template"> | |||
<layout class="QHBoxLayout" name="horizontalLayout_2"> | |||
<property name="margin"> | |||
<number>0</number> | |||
</property> | |||
<item> | |||
<widget class="QLabel" name="l_template"> | |||
<property name="text"> | |||
<string>Template:</string> | |||
</property> | |||
<property name="alignment"> | |||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> | |||
</property> | |||
</widget> | |||
</item> | |||
<item> | |||
<widget class="QComboBox" name="cb_template"> | |||
<property name="sizePolicy"> | |||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> | |||
<horstretch>0</horstretch> | |||
<verstretch>0</verstretch> | |||
</sizepolicy> | |||
</property> | |||
<property name="editable"> | |||
<bool>false</bool> | |||
</property> | |||
</widget> | |||
</item> | |||
</layout> | |||
</widget> | |||
<widget class="QWidget" name="page_command"> | |||
<layout class="QHBoxLayout" name="horizontalLayout_3"> | |||
<property name="margin"> | |||
<number>0</number> | |||
</property> | |||
<item> | |||
<widget class="QLabel" name="l_command"> | |||
<property name="text"> | |||
<string>Command:</string> | |||
</property> | |||
<property name="alignment"> | |||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> | |||
</property> | |||
</widget> | |||
</item> | |||
<item> | |||
<widget class="QLineEdit" name="le_command"/> | |||
</item> | |||
</layout> | |||
</widget> | |||
</widget> | |||
</item> | |||
</layout> | |||
</widget> | |||
</item> | |||
@@ -122,7 +208,7 @@ | |||
<item> | |||
<widget class="QComboBox" name="cb_session_mgr"> | |||
<property name="currentIndex"> | |||
<number>1</number> | |||
<number>0</number> | |||
</property> | |||
<item> | |||
<property name="text"> | |||
@@ -131,12 +217,12 @@ | |||
</item> | |||
<item> | |||
<property name="text"> | |||
<string>Auto</string> | |||
<string>LADISH (SIGUSR1)</string> | |||
</property> | |||
</item> | |||
<item> | |||
<property name="text"> | |||
<string>LADISH (SIGUSR1)</string> | |||
<string>NSM</string> | |||
</property> | |||
</item> | |||
</widget> | |||
@@ -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) | |||
@@ -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 | |||
@@ -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) | |||
@@ -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) | |||
@@ -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), | |||
@@ -223,6 +223,7 @@ struct CarlaEngine::ProtectedData { | |||
#ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH | |||
bool loadingProject; | |||
CarlaString currentProjectFilename; | |||
#endif | |||
uint hints; | |||
@@ -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; | |||
@@ -1,6 +1,6 @@ | |||
/* | |||
* Carla Plugin JACK | |||
* Copyright (C) 2016-2018 Filipe Coelho <falktx@falktx.com> | |||
* Copyright (C) 2016-2019 Filipe Coelho <falktx@falktx.com> | |||
* | |||
* 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<uint>(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<ChildProcess> 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) | |||
}; | |||
@@ -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; | |||
@@ -2,7 +2,7 @@ | |||
# -*- coding: utf-8 -*- | |||
# Carla Backend code | |||
# Copyright (C) 2011-2018 Filipe Coelho <falktx@falktx.com> | |||
# Copyright (C) 2011-2019 Filipe Coelho <falktx@falktx.com> | |||
# | |||
# 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]) | |||
@@ -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): | |||
@@ -733,6 +733,7 @@ class HostWindow(QMainWindow): | |||
self.pluginRemoveAll() | |||
self.fProjectFilename = "" | |||
self.setProperWindowTitle() | |||
self.host.clear_project_filename() | |||
@pyqtSlot() | |||
def slot_fileOpen(self): | |||
@@ -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); | |||
@@ -37,6 +37,7 @@ | |||
#endif | |||
#include <cerrno> | |||
#include <map> | |||
#ifdef __SSE2_MATH__ | |||
# include <xmmintrin.h> | |||
@@ -164,6 +165,8 @@ struct JackClientState { | |||
LinkedList<JackPortState*> midiIns; | |||
LinkedList<JackPortState*> midiOuts; | |||
std::map<std::string, JackPortState*> portNameMapping; | |||
JackShutdownCallback shutdownCb; | |||
void* shutdownCbPtr; | |||
@@ -195,6 +198,7 @@ struct JackClientState { | |||
audioOuts(), | |||
midiIns(), | |||
midiOuts(), | |||
portNameMapping(), | |||
shutdownCb(nullptr), | |||
shutdownCbPtr(nullptr), | |||
infoShutdownCb(nullptr), | |||
@@ -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; | |||
@@ -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 | |||
@@ -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; | |||
@@ -107,6 +107,7 @@ public: | |||
result in undefined behaviour. | |||
*/ | |||
bool kill(); | |||
bool terminate(); | |||
uint32 getPID() const noexcept; | |||