diff --git a/src/plugin/ChildProcess.hpp b/src/plugin/ChildProcess.hpp index d92988e..d85dba0 100644 --- a/src/plugin/ChildProcess.hpp +++ b/src/plugin/ChildProcess.hpp @@ -223,10 +223,10 @@ class ChildProcess #else if (pid <= 0) return false; - + const pid_t ret = ::waitpid(pid, nullptr, WNOHANG); - if (ret == -1 && errno == ECHILD) + if (ret == pid || (ret == -1 && errno == ECHILD)) { pid = 0; return false; diff --git a/src/plugin/DesktopPlugin.cpp b/src/plugin/DesktopPlugin.cpp index 0b12223..2d70cdf 100644 --- a/src/plugin/DesktopPlugin.cpp +++ b/src/plugin/DesktopPlugin.cpp @@ -19,6 +19,8 @@ class DesktopPlugin : public Plugin, ChildProcess jackd; ChildProcess mod_ui; SharedMemory shm; + bool startingJackd = false; + bool startingModUI = false; bool processing = false; bool firstTimeProcessing = true; float parameters[kParameterCount] = {}; @@ -42,9 +44,17 @@ class DesktopPlugin : public Plugin, return; // TODO check available ports - portBaseNum = 1; + int availablePortNum = 1; - envp = getEvironment(portBaseNum); + envp = getEvironment(availablePortNum); + + if (envp == nullptr) + { + parameters[kParameterBasePortNumber] = -kErrorAppDirNotFound; + return; + } + + portBaseNum = availablePortNum; if (shm.init() && run()) startRunner(500); @@ -92,6 +102,13 @@ class DesktopPlugin : public Plugin, if (! jackd.isRunning()) { + if (startingJackd) + { + startingJackd = false; + parameters[kParameterBasePortNumber] = -kErrorJackdExecFailed; + return false; + } + const String appDir(getAppDir()); const String jackdStr(appDir + DISTRHO_OS_SEP_STR "jackd" APP_EXT); const String jacksessionStr(appDir + DISTRHO_OS_SEP_STR "jack" DISTRHO_OS_SEP_STR "jack-session.conf"); @@ -109,9 +126,16 @@ class DesktopPlugin : public Plugin, nullptr }; - return jackd.start(jackd_args, envp); + startingJackd = true; + if (jackd.start(jackd_args, envp)) + return true; + + parameters[kParameterBasePortNumber] = -kErrorJackdExecFailed; + return false; } + startingJackd = false; + if (! processing) { if (shm.sync()) @@ -122,6 +146,13 @@ class DesktopPlugin : public Plugin, if (! mod_ui.isRunning()) { + if (startingModUI) + { + startingModUI = false; + parameters[kParameterBasePortNumber] = -kErrorModUiExecFailed; + return false; + } + const String appDir(getAppDir()); const String moduiStr(appDir + DISTRHO_OS_SEP_STR "mod-ui" APP_EXT); @@ -130,9 +161,16 @@ class DesktopPlugin : public Plugin, nullptr }; - return mod_ui.start(mod_ui_args, envp); + startingModUI = true; + if (mod_ui.start(mod_ui_args, envp)) + return true; + + parameters[kParameterBasePortNumber] = -kErrorModUiExecFailed; + return false; } + startingModUI = false; + parameters[kParameterBasePortNumber] = portBaseNum; return true; } @@ -218,7 +256,7 @@ class DesktopPlugin : public Plugin, parameter.hints = kParameterIsOutput | kParameterIsInteger; parameter.name = "base port number"; parameter.symbol = "base_port_num"; - parameter.ranges.min = 0.f; + parameter.ranges.min = -kErrorUndefined; parameter.ranges.max = 512.f; parameter.ranges.def = 0.f; break; diff --git a/src/plugin/DesktopUI.cpp b/src/plugin/DesktopUI.cpp index 740ea3e..84d2910 100644 --- a/src/plugin/DesktopUI.cpp +++ b/src/plugin/DesktopUI.cpp @@ -19,6 +19,8 @@ class DesktopUI : public UI, Button buttonOpenWebGui; Button buttonOpenUserFilesDir; String label; + String error; + String errorDetail; uint port = 0; void* webview = nullptr; @@ -85,6 +87,41 @@ class DesktopUI : public UI, { if (index == kParameterBasePortNumber) { + if (d_isZero(value)) + return; + + if (value < 0.f) + { + switch (-d_roundToIntNegative(value)) + { + case kErrorAppDirNotFound: + error = "Error: MOD Desktop application directory not found"; + errorDetail = "Make sure to install the standalone and run it at least once"; + break; + case kErrorJackdExecFailed: + error = "Error: Failed to start jackd"; + errorDetail = ""; + break; + case kErrorModUiExecFailed: + error = "Error: Failed to start mod-ui"; + errorDetail = ""; + break; + case kErrorUndefined: + error = "Error initializing MOD Desktop plugin"; + errorDetail = ""; + break; + } + repaint(); + return; + } + + if (error.isNotEmpty()) + { + error.clear(); + errorDetail.clear(); + repaint(); + } + if (webview != nullptr) { destroyWebView(webview); @@ -119,9 +156,21 @@ class DesktopUI : public UI, const double scaleFactor = getScaleFactor(); fillColor(255, 255, 255, 255); - fontSize(14 * scaleFactor); + fontSize(18 * scaleFactor); textAlign(ALIGN_CENTER | ALIGN_MIDDLE); text(getWidth() / 2, kVerticalOffset * scaleFactor / 2, label, nullptr); + + if (error.isNotEmpty()) + { + fontSize(36 * scaleFactor); + text(getWidth() / 2, getHeight() / 2 - 18 * scaleFactor, error, nullptr); + + if (errorDetail.isNotEmpty()) + { + fontSize(18 * scaleFactor); + text(getWidth() / 2, getHeight() / 2 + 18 * scaleFactor, errorDetail, nullptr); + } + } } void buttonClicked(SubWidget* const widget, int button) override diff --git a/src/plugin/DistrhoPluginInfo.h b/src/plugin/DistrhoPluginInfo.h index e261900..926c8be 100644 --- a/src/plugin/DistrhoPluginInfo.h +++ b/src/plugin/DistrhoPluginInfo.h @@ -27,6 +27,13 @@ static const constexpr unsigned int kVerticalOffset = 30; static const constexpr unsigned int kPortNumOffset = 18190; +enum Error { + kErrorAppDirNotFound = 1, + kErrorJackdExecFailed, + kErrorModUiExecFailed, + kErrorUndefined +}; + enum Parameters { kParameterBasePortNumber, kParameterCount diff --git a/src/plugin/Makefile b/src/plugin/Makefile index 3fee3c7..cfca0d8 100644 --- a/src/plugin/Makefile +++ b/src/plugin/Makefile @@ -1,5 +1,9 @@ #!/usr/bin/make -f +export DISTRHO_NAMESPACE = DesktopDISTRHO +export DGL_NAMESPACE = DesktopDGL +export NVG_FONT_TEXTURE_FLAGS = NVG_IMAGE_NEAREST + include ../DPF/Makefile.base.mk # --------------------------------------------------------------------------------------------------------------------- @@ -50,7 +54,7 @@ $(TARGET_DIR)/MOD-Desktop-WebView: $(BUILD_DIR)/WebViewQt.cpp.o $(BUILD_DIR)/WebViewQt.cpp.o: BUILD_CXX_FLAGS += -std=gnu++14 $(shell $(PKG_CONFIG) --cflags Qt5WebEngineWidgets) -include $(BUILD_DIR)/WebViewQt.cpp.d +-include $(BUILD_DIR)/WebViewQt.cpp.d endif diff --git a/src/plugin/WebViewQt.cpp b/src/plugin/WebViewQt.cpp index 7630cfe..1269255 100644 --- a/src/plugin/WebViewQt.cpp +++ b/src/plugin/WebViewQt.cpp @@ -36,7 +36,7 @@ int main(int argc, char* argv[]) printf("'%s' '%s'\n", argv[1], argv[2]); - if (argc == 3 && std::strcmp(argv[1], "-xembed") == 0) + if (argc == 4 && std::strcmp(argv[1], "-xembed") == 0) { const uintptr_t parentId = std::atoll(argv[2]); QWindow* const parentWindow = QWindow::fromWinId(parentId); @@ -46,7 +46,7 @@ int main(int argc, char* argv[]) webview.setFixedSize(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT - kVerticalOffset); webview.winId(); webview.windowHandle()->setParent(parentWindow); - webview.setUrl(QUrl("http://127.0.0.1:18181/")); + webview.setUrl(QUrl(QString::fromLocal8Bit("http://127.0.0.1:") + QString::fromLocal8Bit(argv[3]))); webview.show(); return app.exec(); } diff --git a/src/plugin/WebViewX11.cpp b/src/plugin/WebViewX11.cpp index a1e2f9d..7c16f28 100644 --- a/src/plugin/WebViewX11.cpp +++ b/src/plugin/WebViewX11.cpp @@ -24,7 +24,7 @@ struct WebViewIPC { // ----------------------------------------------------------------------------------------------------------- -void* addWebView(uintptr_t viewptr) +void* addWebView(const uintptr_t parentWinId, const uint port) { char webviewTool[PATH_MAX] = {}; { @@ -60,10 +60,17 @@ void* addWebView(uintptr_t viewptr) WebViewIPC* const ipc = new WebViewIPC(); ipc->display = display; ipc->childWindow = 0; - ipc->ourWindow = viewptr; - - const String viewStr(viewptr); - const char* const args[] = { webviewTool, "-platform", "xcb", "-xembed", viewStr.buffer(), nullptr }; + ipc->ourWindow = parentWinId; + + const String embedIdStr(parentWinId); + const String portStr(port); + const char* const args[] = { + webviewTool, + "-platform", "xcb", + "-xembed", embedIdStr.buffer(), + portStr.buffer(), + nullptr + }; ipc->p.start(args); return ipc; diff --git a/src/plugin/utils.cpp b/src/plugin/utils.cpp index 4f7a500..4d9c91c 100644 --- a/src/plugin/utils.cpp +++ b/src/plugin/utils.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 MOD Audio UG +// SPDX-FileCopyrightText: 2023-2024 MOD Audio UG // SPDX-License-Identifier: AGPL-3.0-or-later #include "utils.hpp" @@ -8,7 +8,7 @@ #ifdef _WIN32 #else #include -// #include +#include #endif #if defined(__APPLE__) @@ -37,84 +37,106 @@ constexpr uint8_t char2u8(const uint8_t c) // ----------------------------------------------------------------------------------------------------------- +// FIXME share with systray #ifdef _WIN32 -static const WCHAR* getAppDirW() +static WCHAR char* getDataDirW() { - static WCHAR appDir[MAX_PATH] = {}; + static WCHAR dataDir[MAX_PATH] = {}; - if (appDir[0] == 0) + if (dataDir[0] == 0) { - // TODO SHGetSpecialFolderPathW(nullptr, dataDir, CSIDL_MYDOCUMENTS, false); + _wmkdir(dataDir); + std::wcsncat(dataDir, L"\\MOD Desktop", MAX_PATH - 1); + _wmkdir(dataDir); } - return appDir; + return dataDir; } -#endif - -const char* getAppDir() +#else +static const char* getDataDir() { - #ifdef DISTRHO_OS_MAC - return "/Applications/MOD Desktop.app/Contents/MacOS"; - #else - static char appDir[PATH_MAX] = {}; + static char dataDir[PATH_MAX] = {}; - if (appDir[0] == 0) + if (dataDir[0] == 0) { - #if defined(_WIN32) - #else - char appDir[PATH_MAX] = {}; - // TODO - std::strncpy(appDir, filename, PATH_MAX - 1); + std::strncpy(dataDir, getenv("HOME"), PATH_MAX - 1); + mkdir(dataDir, 0777); - if (char* const c = std::strrchr(appDir, '/')) - *c = 0; - #endif + std::strncat(dataDir, "/Documents", PATH_MAX - 1); + mkdir(dataDir, 0777); + + std::strncat(dataDir, "/MOD Desktop", PATH_MAX - 1); + mkdir(dataDir, 0777); } - return appDir; - #endif + return dataDir; } +#endif + +// ----------------------------------------------------------------------------------------------------------- #ifdef _WIN32 -WCHAR char* getDataDirW() +static const WCHAR* getAppDirW() { - static WCHAR dataDir[MAX_PATH] = {}; + static WCHAR appDir[MAX_PATH] = {}; - if (dataDir[0] == 0) + if (appDir[0] == 0) { + // TODO SHGetSpecialFolderPathW(nullptr, dataDir, CSIDL_MYDOCUMENTS, false); - _wmkdir(dataDir); - std::wcsncat(dataDir, L"\\MOD Desktop", MAX_PATH - 1); - _wmkdir(dataDir); } - return dataDir; + return appDir; } -#else -const char* getDataDir() +#endif + +const char* getAppDir() { - static char dataDir[PATH_MAX] = {}; + #ifdef DISTRHO_OS_MAC + return "/Applications/MOD Desktop.app/Contents/MacOS"; + #else + static char appDir[PATH_MAX] = {}; - if (dataDir[0] == 0) + if (appDir[0] == 0) { + #if defined(_WIN32) + const WCHAR* const appDirW = getAppDirW(); + // TODO - std::strncpy(dataDir, getenv("HOME"), PATH_MAX - 1); - mkdir(dataDir, 0777); + #else + std::strncpy(appDir, getDataDir(), PATH_MAX - 1); + std::strncat(appDir, "/.last-known-location", PATH_MAX - 1); - std::strncat(dataDir, "/Documents", PATH_MAX - 1); - mkdir(dataDir, 0777); + if (FILE* const f = std::fopen(appDir, "r")) + { + std::memset(appDir, 0, PATH_MAX - 1); - std::strncat(dataDir, "/MOD Desktop", PATH_MAX - 1); - mkdir(dataDir, 0777); + if (std::fread(appDir, PATH_MAX - 1, 1, f) != 0) + appDir[0] = 0; + + else if (access(appDir, F_OK) != 0) + appDir[0] = 0; + + std::fclose(f); + } + else + { + appDir[0] = 0; + return nullptr; + } + + if (appDir[0] == 0) + return nullptr; + #endif } - return dataDir; + return appDir; + #endif } -#endif // ----------------------------------------------------------------------------------------------------------- @@ -162,6 +184,17 @@ static void set_envp_value(char** envp, const char* const key, const char* const // NOTE this needs to match initEvironment from systray side char* const* getEvironment(const uint portBaseNum) { + // get directory of the mod-desktop application + #ifdef _WIN32 + const WCHAR* const appDir = getAppDirW(); + #else + const char* const appDir = getAppDir(); + #endif + + // can be null, in case of not found + if (appDir == nullptr) + return nullptr; + #ifdef DISTRHO_OS_MAC const char* const* const* const environptr = _NSGetEnviron(); DISTRHO_SAFE_ASSERT_RETURN(environptr != nullptr, nullptr); @@ -197,13 +230,6 @@ char* const* getEvironment(const uint portBaseNum) set_envp_value(envp, "PYTHONUNBUFFERED=1"); #endif - // get directory of the mod-desktop application - #ifdef _WIN32 - const WCHAR* const appDir = getAppDirW(); - #else - const char* const appDir = getAppDir(); - #endif - // get and set directory to our documents and settings, under "user documents"; also make sure it exists #ifdef _WIN32 const WCHAR* const dataDir = getDataDirW(); diff --git a/src/systray/utils.cpp b/src/systray/utils.cpp index bd6114c..b94e022 100644 --- a/src/systray/utils.cpp +++ b/src/systray/utils.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 MOD Audio UG +// SPDX-FileCopyrightText: 2023-2024 MOD Audio UG // SPDX-License-Identifier: AGPL-3.0-or-later #include "utils.hpp" @@ -146,6 +146,21 @@ void initEvironment() const size_t dataDirLen = std::strlen(dataDir); #endif + // write our location to disk so the plugin version knows where to find us + #ifdef __linux__ + if (access(dataDir, F_OK) == 0) + { + std::memcpy(path, dataDir, dataDirLen); + std::strncpy(path + dataDirLen, "/.last-known-location", PATH_MAX - dataDirLen - 1); + + if (FILE* const f = std::fopen(path, "w")) + { + std::fwrite(appDir, appDirLen, 1, f); + std::fclose(f); + } + } + #endif + // generate UID uint8_t key[16] = {}; #if defined(__APPLE__) @@ -178,9 +193,9 @@ void initEvironment() #elif defined(_WIN32) // TODO #else - if (FILE* const f = fopen("/etc/machine-id", "r")) + if (FILE* const f = std::fopen("/etc/machine-id", "r")) { - if (fread(path, PATH_MAX - 1, 1, f) == 0 && strlen(path) >= 33) + if (std::fread(path, PATH_MAX - 1, 1, f) == 0 && std::strlen(path) >= 33) { for (int i=0; i<16; ++i) { @@ -188,7 +203,7 @@ void initEvironment() key[i] |= char2u8(path[i*2+1]) << 0; } } - fclose(f); + std::fclose(f); } #endif