From eb09b718a88c3e8347494f9e5c05d694bb6e3d2b Mon Sep 17 00:00:00 2001 From: falkTX Date: Thu, 14 Mar 2024 00:57:46 +0100 Subject: [PATCH] Less hacky approach Signed-off-by: falkTX --- src/plugin/ChildProcess.hpp | 9 - src/plugin/DesktopPlugin.cpp | 214 ++++++++--------- src/plugin/DesktopUI.cpp | 81 +++++-- src/plugin/DistrhoPluginInfo.h | 8 +- src/plugin/Makefile | 8 +- src/plugin/NanoButton.cpp | 106 +++++++++ src/plugin/NanoButton.hpp | 59 +++++ src/plugin/SharedMemory.hpp | 10 + src/plugin/WebView.mm | 7 +- src/plugin/utils.cpp | 413 +++++++++++++++++++++++++++++++++ src/plugin/utils.hpp | 30 +++ src/systray/utils.cpp | 8 +- 12 files changed, 803 insertions(+), 150 deletions(-) create mode 100644 src/plugin/NanoButton.cpp create mode 100644 src/plugin/NanoButton.hpp create mode 100644 src/plugin/utils.cpp create mode 100644 src/plugin/utils.hpp diff --git a/src/plugin/ChildProcess.hpp b/src/plugin/ChildProcess.hpp index 1d298b8..d92988e 100644 --- a/src/plugin/ChildProcess.hpp +++ b/src/plugin/ChildProcess.hpp @@ -26,15 +26,6 @@ START_NAMESPACE_DISTRHO -// FIXME -#if defined(DISTRHO_OS_MAC) -# define P "/Users/falktx/Source/MOD/mod-app/build/mod-desktop.app/Contents" -#elif defined(DISTRHO_OS_WINDOWS) -# define P "Z:\\home\\falktx\\Source\\MOD\\mod-app\\build" -#else -# define P "/home/falktx/Source/MOD/mod-app/build" -#endif - // ----------------------------------------------------------------------------------------------------------- class ChildProcess diff --git a/src/plugin/DesktopPlugin.cpp b/src/plugin/DesktopPlugin.cpp index bd09373..b2f0693 100644 --- a/src/plugin/DesktopPlugin.cpp +++ b/src/plugin/DesktopPlugin.cpp @@ -3,120 +3,55 @@ #include "DistrhoPlugin.hpp" -#include "Time.hpp" - #include "ChildProcess.hpp" #include "SharedMemory.hpp" +#include "extra/Runner.hpp" + +#include "utils.hpp" START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------------------------------------------- -class DesktopPlugin : public Plugin +class DesktopPlugin : public Plugin, + public Runner { ChildProcess jackd; ChildProcess mod_ui; SharedMemory shm; bool processing = false; bool firstTimeProcessing = true; - float parameters[24] = {}; + float parameters[kParameterCount] = {}; float* tmpBuffers[2] = {}; uint32_t numFramesInShmBuffer = 0; uint32_t numFramesInTmpBuffer = 0; #ifdef DISTRHO_OS_WINDOWS - const CHAR* envp; + const WCHAR* envp; #else - char** envp; + char* const* envp; #endif public: DesktopPlugin() - : Plugin(0, 0, 0) + : Plugin(kParameterCount, 0, 0), + envp(nullptr) { if (isDummyInstance()) return; - #ifdef DISTRHO_OS_WINDOWS - envp = ( - "MOD_DATA_DIR=" P "\\data\0" - "MOD_FACTORY_PEDALBOARDS_DIR=" P "\\pedalboards\0" - "LV2_PATH=" P "\\plugins\0" - "\0" - ); - #else - uint envsize = 0; - while (environ[envsize] != nullptr) - ++envsize; - - envp = new char*[envsize + 8]; - - for (uint i = 0; i < envsize; ++i) - envp[i] = strdup(environ[i]); - - envp[envsize] = strdup("MOD_DESKTOP=1"); - envp[envsize + 1] = strdup("LANG=en_US.UTF-8"); - #ifdef DISTRHO_OS_MAC - envp[envsize + 2] = strdup("DYLD_LIBRARY_PATH=" P "/MacOS"); - envp[envsize + 3] = strdup("JACK_DRIVER_DIR=" P "/MacOS/jack"); - envp[envsize + 4] = strdup("MOD_DATA_DIR=/Users/falktx/Documents/MOD Desktop"); - envp[envsize + 5] = strdup("MOD_FACTORY_PEDALBOARDS_DIR=" P "/Resources/pedalboards"); - envp[envsize + 6] = strdup("LV2_PATH=" P "/LV2"); - envp[envsize + 7] = nullptr; - #else - envp[envsize + 2] = strdup("LD_LIBRARY_PATH=" P); - envp[envsize + 3] = strdup("JACK_DRIVER_DIR=" P "/jack"); - envp[envsize + 4] = strdup("MOD_DATA_DIR=" P "/data"); - envp[envsize + 5] = strdup("MOD_FACTORY_PEDALBOARDS_DIR=" P "/pedalboards"); - envp[envsize + 6] = strdup("LV2_PATH=" P "/plugins"); - envp[envsize + 7] = nullptr; - #endif - #endif - - const String sampleRateStr(static_cast(getSampleRate())); - const char* const jackd_args[] = { - #if defined(DISTRHO_OS_MAC) - P "/MacOS/jackd", - #elif defined(DISTRHO_OS_WINDOWS) - P "\\jackd.exe", - #else - P "/jackd", - #endif - "-R", - "-S", - "-n", "mod-desktop", "-C", - #if defined(DISTRHO_OS_MAC) - P "/MacOS/jack/jack-session.conf", - #elif defined(DISTRHO_OS_WINDOWS) - P "\\jack\\jack-session.conf", - #else - P "/jack/jack-session.conf", - #endif - "-d", "desktop", "-r", sampleRateStr.buffer(), nullptr - }; - const char* const mod_ui_args[] = { - #if defined(DISTRHO_OS_MAC) - P "/MacOS/mod-ui", - #elif defined(DISTRHO_OS_WINDOWS) - P "\\mod-ui.exe", - #else - P "/mod-ui", - #endif - nullptr - }; + envp = getEvironment(); - if (shm.init() && jackd.start(jackd_args, envp)) - { - processing = true; - bufferSizeChanged(getBufferSize()); + if (shm.init() && run()) + startRunner(500); - d_msleep(500); - mod_ui.start(mod_ui_args, envp); - } + bufferSizeChanged(getBufferSize()); } ~DesktopPlugin() { + stopRunner(); + if (processing && jackd.isRunning()) shm.stopWait(); @@ -128,16 +63,76 @@ class DesktopPlugin : public Plugin delete[] tmpBuffers[0]; delete[] tmpBuffers[1]; + if (envp != nullptr) + { + #ifndef DISTRHO_OS_WINDOWS + for (uint i = 0; envp[i] != nullptr; ++i) + std::free(envp[i]); + #endif + + delete[] envp; + } + } + +protected: + /* -------------------------------------------------------------------------------------------------------- + * Keep services running */ + + bool run() override + { #ifdef DISTRHO_OS_WINDOWS + #define APP_EXT ".ext" #else - for (uint i = 0; envp[i] != nullptr; ++i) - std::free(envp[i]); - - delete[] envp; + #define APP_EXT "" #endif + + if (! jackd.isRunning()) + { + 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"); + const String sampleRateStr(static_cast(getSampleRate())); + + const char* const jackd_args[] = { + jackdStr.buffer(), + "-R", + "-S", + "-n", "mod-desktop", + "-C", + jacksessionStr.buffer(), + "-d", "desktop", + "-r", sampleRateStr.buffer(), + nullptr + }; + + return jackd.start(jackd_args, envp); + } + + if (! processing) + { + if (shm.sync()) + processing = true; + + return true; + } + + if (! mod_ui.isRunning()) + { + const String appDir(getAppDir()); + const String moduiStr(appDir + DISTRHO_OS_SEP_STR "mod-ui" APP_EXT); + + const char* const mod_ui_args[] = { + moduiStr.buffer(), + nullptr + }; + + return mod_ui.start(mod_ui_args, envp); + } + + parameters[kParameterBasePortNumber] = 1; + return true; } -protected: /* -------------------------------------------------------------------------------------------------------- * Information */ @@ -213,10 +208,16 @@ class DesktopPlugin : public Plugin */ void initParameter(uint32_t index, Parameter& parameter) override { - parameter.hints = kParameterIsAutomatable; - switch (index) { + case kParameterBasePortNumber: + parameter.hints = kParameterIsOutput | kParameterIsInteger; + parameter.name = "base port number"; + parameter.symbol = "base_port_num"; + parameter.ranges.min = 0.f; + parameter.ranges.max = 512.f; + parameter.ranges.def = 0.f; + break; } } @@ -245,7 +246,7 @@ class DesktopPlugin : public Plugin */ void setParameterValue(const uint32_t index, const float value) override { - parameters[index] = value; + // parameters[index] = value; } /** @@ -459,35 +460,16 @@ class DesktopPlugin : public Plugin std::memset(tmpBuffers[1], 0, sizeof(float) * (bufferSize + 256)); } - void sampleRateChanged(const double sampleRate) override + void sampleRateChanged(double) override { - if (! processing) - return; - - const String sampleRateStr(static_cast(sampleRate)); - const char* const jackd_args[] = { - #if defined(DISTRHO_OS_MAC) - P "/MacOS/jackd", - #elif defined(DISTRHO_OS_WINDOWS) - P "\\jackd.exe", - #else - P "/jackd", - #endif - "-R", - "-S", - "-n", "mod-desktop", "-C", - #if defined(DISTRHO_OS_MAC) - P "/MacOS/jack/jack-session.conf", - #elif defined(DISTRHO_OS_WINDOWS) - P "\\jack\\jack-session.conf", - #else - P "/jack/jack-session.conf", - #endif - "-d", "desktop", "-r", sampleRateStr.buffer(), nullptr - }; + if (jackd.isRunning()) + { + stopRunner(); + jackd.stop(); - jackd.stop(); - jackd.start(jackd_args); + if (run()) + startRunner(500); + } } // ------------------------------------------------------------------------------------------------------- diff --git a/src/plugin/DesktopUI.cpp b/src/plugin/DesktopUI.cpp index 2323f56..70d33bc 100644 --- a/src/plugin/DesktopUI.cpp +++ b/src/plugin/DesktopUI.cpp @@ -2,24 +2,60 @@ // SPDX-License-Identifier: AGPL-3.0-or-later #include "DistrhoUI.hpp" +#include "DistrhoPluginUtils.hpp" +#include "NanoButton.hpp" #include "WebView.hpp" +#include "utils.hpp" + START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------------------------------------------- -class DesktopUI : public UI +class DesktopUI : public UI, + public ButtonEventHandler::Callback { + Button buttonRefresh; + Button buttonOpenWebGui; + Button buttonOpenUserFilesDir; + String label; void* webview = nullptr; public: DesktopUI() - : UI(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT) + : UI(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT), + buttonRefresh(this, this), + buttonOpenWebGui(this, this), + buttonOpenUserFilesDir(this, this) { - webview = addWebView(getWindow().getNativeWindowHandle()); + loadSharedResources(); const double scaleFactor = getScaleFactor(); + buttonRefresh.setId(1); + buttonRefresh.setLabel("Refresh"); + buttonRefresh.setFontScale(scaleFactor); + buttonRefresh.setAbsolutePos(2 * scaleFactor, 2 * scaleFactor); + buttonRefresh.setSize(70 * scaleFactor, 26 * scaleFactor); + + buttonOpenWebGui.setId(2); + buttonOpenWebGui.setLabel("Open in Web Browser"); + buttonOpenWebGui.setFontScale(scaleFactor); + buttonOpenWebGui.setAbsolutePos(74 * scaleFactor, 2 * scaleFactor); + buttonOpenWebGui.setSize(150 * scaleFactor, 26 * scaleFactor); + + buttonOpenUserFilesDir.setId(3); + buttonOpenUserFilesDir.setLabel("Open User Files Dir"); + buttonOpenUserFilesDir.setFontScale(scaleFactor); + buttonOpenUserFilesDir.setAbsolutePos(226 * scaleFactor, 2 * scaleFactor); + buttonOpenUserFilesDir.setSize(140 * scaleFactor, 26 * scaleFactor); + + label = "MOD Desktop "; + label += getPluginFormatName(); + label += " v" VERSION; + + webview = addWebView(getWindow().getNativeWindowHandle()); + if (d_isNotEqual(scaleFactor, 1.0)) { setGeometryConstraints((DISTRHO_UI_DEFAULT_WIDTH - 100) * scaleFactor, @@ -45,8 +81,13 @@ class DesktopUI : public UI A parameter has changed on the plugin side. This is called by the host to inform the UI about parameter changes. */ - void parameterChanged(uint32_t, float) override + void parameterChanged(const uint32_t index, const float value) override { + if (index == kParameterBasePortNumber) + { + // TODO set port + reloadWebView(webview); + } } /** @@ -64,29 +105,43 @@ class DesktopUI : public UI /** The drawing function. */ - void onDisplay() override + void onNanoDisplay() override { + const double scaleFactor = getScaleFactor(); + + fillColor(255, 255, 255, 255); + fontSize(14 * scaleFactor); + textAlign(ALIGN_CENTER | ALIGN_MIDDLE); + text(getWidth() / 2, kVerticalOffset * scaleFactor / 2, label, nullptr); } - bool onMouse(const MouseEvent& ev) override + void buttonClicked(SubWidget* const widget, int button) override { - if (UI::onMouse(ev)) - return true; - - reloadWebView(webview); - return true; + switch (widget->getId()) + { + case 1: + reloadWebView(webview); + break; + case 2: + openWebGui(); + break; + case 3: + openUserFilesDir(); + break; + } } void onResize(const ResizeEvent& ev) override { UI::onResize(ev); - uint offset = kVerticalOffset; + const double scaleFactor = getScaleFactor(); + + uint offset = kVerticalOffset * scaleFactor; uint width = ev.size.getWidth(); uint height = ev.size.getHeight() - offset; #ifdef DISTRHO_OS_MAC - const double scaleFactor = getScaleFactor(); offset /= scaleFactor; width /= scaleFactor; height /= scaleFactor; diff --git a/src/plugin/DistrhoPluginInfo.h b/src/plugin/DistrhoPluginInfo.h index 2350a0e..275cfe5 100644 --- a/src/plugin/DistrhoPluginInfo.h +++ b/src/plugin/DistrhoPluginInfo.h @@ -21,6 +21,12 @@ #define DISTRHO_UI_FILE_BROWSER 0 #define DISTRHO_UI_DEFAULT_WIDTH 1170 #define DISTRHO_UI_DEFAULT_HEIGHT 600 +#define DISTRHO_UI_USE_NANOVG 1 #define DISTRHO_UI_USER_RESIZABLE 1 -static const constexpr unsigned int kVerticalOffset = 25; +static const constexpr unsigned int kVerticalOffset = 30; + +enum Parameters { + kParameterBasePortNumber, + kParameterCount +}; diff --git a/src/plugin/Makefile b/src/plugin/Makefile index 7d6f2e0..3fee3c7 100644 --- a/src/plugin/Makefile +++ b/src/plugin/Makefile @@ -10,8 +10,8 @@ NAME = MOD-Desktop # --------------------------------------------------------------------------------------------------------------------- # Files to build -FILES_DSP = DesktopPlugin.cpp -FILES_UI = DesktopUI.cpp +FILES_DSP = DesktopPlugin.cpp utils.cpp +FILES_UI = DesktopUI.cpp NanoButton.cpp ifeq ($(MACOS),true) FILES_UI += WebView.mm @@ -27,8 +27,10 @@ DPF_TARGET_DIR = ../../build-plugin include ../DPF/Makefile.plugins.mk +BUILD_CXX_FLAGS += -DVERSION='"$(shell cat ../../VERSION)"' + ifeq ($(MACOS),true) -LINK_FLAGS += -framework WebKit +LINK_FLAGS += -framework IOKit -framework WebKit else ifeq ($(WINDOWS),true) LINK_FLAGS += -lwinmm else ifeq ($(LINUX),true) diff --git a/src/plugin/NanoButton.cpp b/src/plugin/NanoButton.cpp new file mode 100644 index 0000000..406dd9a --- /dev/null +++ b/src/plugin/NanoButton.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018-2019 Rob van den Berg + * Copyright (C) 2020-2021 Filipe Coelho + * + * This file is part of CharacterCompressor + * + * Nnjas2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CharacterCompressor is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CharacterCompressor. If not, see . + */ + +#include "NanoButton.hpp" +#include "Window.hpp" + +START_NAMESPACE_DGL + +Button::Button(Widget* const parent, ButtonEventHandler::Callback* const cb) + : NanoWidget(parent), + ButtonEventHandler(this), + backgroundColor(32, 32, 32), + labelColor(255, 255, 255), + label("button"), + fontScale(1.0f) +{ +#ifdef DGL_NO_SHARED_RESOURCES + createFontFromFile("sans", "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf"); +#else + loadSharedResources(); +#endif + ButtonEventHandler::setCallback(cb); +} + +Button::~Button() +{ +} + +void Button::setBackgroundColor(const Color color) +{ + backgroundColor = color; +} + +void Button::setFontScale(const float scale) +{ + fontScale = scale; +} + +void Button::setLabel(const std::string& label2) +{ + label = label2; +} + +void Button::setLabelColor(const Color color) +{ + labelColor = color; +} + +void Button::onNanoDisplay() +{ + const uint w = getWidth(); + const uint h = getHeight(); + const float margin = 1.0f; + + // Background + beginPath(); + fillColor(backgroundColor); + strokeColor(labelColor); + rect(margin, margin, w - 2 * margin, h - 2 * margin); + fill(); + stroke(); + closePath(); + + // Label + beginPath(); + fontSize(14 * fontScale); + fillColor(labelColor); + Rectangle bounds; + textBounds(0, 0, label.c_str(), NULL, bounds); + float tx = w / 2.0f ; + float ty = h / 2.0f; + textAlign(ALIGN_CENTER | ALIGN_MIDDLE); + + fillColor(255, 255, 255, 255); + text(tx, ty, label.c_str(), NULL); + closePath(); +} + +bool Button::onMouse(const MouseEvent& ev) +{ + return ButtonEventHandler::mouseEvent(ev); +} + +bool Button::onMotion(const MotionEvent& ev) +{ + return ButtonEventHandler::motionEvent(ev); +} + +END_NAMESPACE_DGL diff --git a/src/plugin/NanoButton.hpp b/src/plugin/NanoButton.hpp new file mode 100644 index 0000000..20eac38 --- /dev/null +++ b/src/plugin/NanoButton.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018-2019 Rob van den Berg + * Copyright (C) 2020-2021 Filipe Coelho + * + * This file is part of CharacterCompressor + * + * Nnjas2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CharacterCompressor is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CharacterCompressor. If not, see . + */ + +#ifndef NANO_BUTTON_HPP_INCLUDED +#define NANO_BUTTON_HPP_INCLUDED + +#include "NanoVG.hpp" +#include "EventHandlers.hpp" + +#include + +START_NAMESPACE_DGL + +class Button : public NanoSubWidget, + public ButtonEventHandler +{ +public: + explicit Button(Widget* parent, ButtonEventHandler::Callback* cb); + ~Button() override; + + void setBackgroundColor(Color color); + void setFontScale(float scale); + void setLabel(const std::string& label); + void setLabelColor(Color color); + +protected: + void onNanoDisplay() override; + bool onMouse(const MouseEvent& ev) override; + bool onMotion(const MotionEvent& ev) override; + +private: + Color backgroundColor; + Color labelColor; + std::string label; + float fontScale; + + DISTRHO_LEAK_DETECTOR(Button) +}; + +END_NAMESPACE_DGL + +#endif // NANO_BUTTON_HPP_INCLUDED diff --git a/src/plugin/SharedMemory.hpp b/src/plugin/SharedMemory.hpp index 35da376..7601799 100644 --- a/src/plugin/SharedMemory.hpp +++ b/src/plugin/SharedMemory.hpp @@ -204,6 +204,16 @@ class SharedMemory std::memset(data->audio, 0, sizeof(float) * 128 * 2); } + bool sync() + { + if (data == nullptr) + return false; + + reset(); + post(); + return wait(); + } + void stopWait() { if (data == nullptr) diff --git a/src/plugin/WebView.mm b/src/plugin/WebView.mm index f1b30be..9c4810c 100644 --- a/src/plugin/WebView.mm +++ b/src/plugin/WebView.mm @@ -3,6 +3,8 @@ #include "WebView.hpp" +#include "DistrhoPluginInfo.h" + #import #import @@ -14,12 +16,12 @@ { NSView* const view = reinterpret_cast(viewptr); - cosnt CGRect rect = CGRectMake(0, + const CGRect rect = CGRectMake(0, kVerticalOffset, DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT - kVerticalOffset); - WKWebView* const webview = [[[WKWebView alloc] initWithFrame: rect] retain]; + WKWebView* const webview = [[WKWebView alloc] initWithFrame: rect]; [[[webview configuration] preferences] setValue: @(true) forKey: @"developerExtrasEnabled"]; @@ -34,7 +36,6 @@ void destroyWebView(void* const webviewptr) WKWebView* const webview = static_cast(webviewptr); [webview setHidden:YES]; - [[webview dealloc] release]; } void reloadWebView(void* const webviewptr) diff --git a/src/plugin/utils.cpp b/src/plugin/utils.cpp new file mode 100644 index 0000000..37f49c8 --- /dev/null +++ b/src/plugin/utils.cpp @@ -0,0 +1,413 @@ +// SPDX-FileCopyrightText: 2023 MOD Audio UG +// SPDX-License-Identifier: AGPL-3.0-or-later + +#include "utils.hpp" + +#include + +#ifdef _WIN32 +#else +#include +// #include +#endif + +#if defined(__APPLE__) +#include +#include +#include +#elif defined(__linux__) +#include +#endif + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------------------------------------------- + +constexpr const uint8_t mod_desktop_hash[] = { + 0xe4, 0xf7, 0x0d, 0xe9, 0x77, 0xb8, 0x47, 0xe0, 0xba, 0x2e, 0x70, 0x14, 0x93, 0x7a, 0xce, 0xa7 +}; + +constexpr uint8_t char2u8(const uint8_t c) +{ + return c >= '0' && c <= '9' ? c - '0' + : c >= 'a' && c <= 'f' ? 0xa + c - 'a' + : c >= 'A' && c <= 'F' ? 0xa + c - 'A' + : 0; +} + +// ----------------------------------------------------------------------------------------------------------- + +#ifdef _WIN32 +static const WCHAR* getAppDirW() +{ + static WCHAR appDir[MAX_PATH] = {}; + + if (appDir[0] == 0) + { + // TODO + SHGetSpecialFolderPathW(nullptr, dataDir, CSIDL_MYDOCUMENTS, false); + std::wcsncat(dataDir, L"\\MOD Desktop", MAX_PATH - 1); + } + + return appDir; +} +#endif + +const char* getAppDir() +{ + #ifdef DISTRHO_OS_MAC + return "/Applications/MOD Desktop.app/Contents/MacOS"; + #else + static char appDir[PATH_MAX] = {}; + + if (appDir[0] == 0) + { + #if defined(_WIN32) + #else + char appDir[PATH_MAX] = {}; + + // TODO + std::strncpy(appDir, filename, PATH_MAX - 1); + + if (char* const c = std::strrchr(appDir, '/')) + *c = 0; + #endif + } + + return appDir; + #endif +} + +// ----------------------------------------------------------------------------------------------------------- + +static const char* gMOD_USER_FILES_DIR = nullptr; + +static void set_envp_value(char** envp, const char* const fullvalue) +{ + const size_t keylen = std::strchr(fullvalue, '=') - fullvalue; + + for (; *envp != nullptr; ++envp) + { + if (std::strncmp(*envp, fullvalue, keylen + 1) == 0) + { + const size_t fulllen = std::strlen(fullvalue); + *envp = static_cast(std::realloc(*envp, fulllen + 1)); + std::memcpy(*envp + (keylen + 1), fullvalue + (keylen + 1), fulllen - keylen); + return; + } + } + + *envp = strdup(fullvalue); +} + +static const char* set_envp_value(char** envp, const char* const key, const char* const value) +{ + const size_t keylen = std::strlen(key); + const size_t valuelen = std::strlen(value); + + for (; *envp != nullptr; ++envp) + { + if (std::strncmp(*envp, key, keylen) == 0 && (*envp)[keylen] == '=') + { + *envp = static_cast(std::realloc(*envp, keylen + valuelen + 2)); + std::memcpy(*envp + (keylen + 1), value, valuelen + 1); + return *envp + (keylen + 1); + } + } + + *envp = static_cast(std::malloc(keylen + valuelen + 2)); + std::memcpy(*envp, key, keylen); + std::memcpy(*envp + (keylen + 1), value, valuelen + 1); + (*envp)[keylen] = '='; + return *envp + (keylen + 1); +} + +// ----------------------------------------------------------------------------------------------------------- + +// NOTE this needs to match initEvironment from systray side +char* const* getEvironment() +{ + #ifdef DISTRHO_OS_MAC + const char* const* const* const environptr = _NSGetEnviron(); + DISTRHO_SAFE_ASSERT_RETURN(environptr != nullptr, nullptr); + + const char* const* const environ = *environptr; + DISTRHO_SAFE_ASSERT_RETURN(environ != nullptr, nullptr); + #endif + + uint envsize = 0; + #ifdef _WIN32 + envp = ( + "MOD_DATA_DIR=" P "\\data\0" + "MOD_FACTORY_PEDALBOARDS_DIR=" P "\\pedalboards\0" + "LV2_PATH=" P "\\plugins\0" + "\0" + ); + #else + while (environ[envsize] != nullptr) + ++envsize; + + char** const envp = new char*[envsize + 13]; + + for (uint i = 0; i < envsize; ++i) + envp[i] = strdup(environ[i]); + + for (uint i = 0; i < 13; ++i) + envp[envsize + i] = nullptr; + + // base environment details + set_envp_value(envp, "LANG=en_US.UTF-8"); + set_envp_value(envp, "MOD_DESKTOP=1"); + set_envp_value(envp, "MOD_DESKTOP_PLUGIN=1"); + 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 + WCHAR dataDir[MAX_PATH] = {}; + SHGetSpecialFolderPathW(nullptr, dataDir, CSIDL_MYDOCUMENTS, false); + + _wmkdir(dataDir); + std::wcsncat(dataDir, L"\\MOD Desktop", MAX_PATH - 1); + _wmkdir(dataDir); + + set_envp_value(envp, L"MOD_DATA_DIR", dataDir); + #else + char dataDir[PATH_MAX] = {}; + + // NOTE a generic implementation is a bit complex, let Qt do it for us this time + // const QByteArray docsDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).toUtf8()); + // std::strncpy(dataDir, docsDir.constData(), PATH_MAX - 1); + // TODO + std::strncpy(dataDir, getenv("HOME"), PATH_MAX - 1); + std::strncat(dataDir, "/Documents", PATH_MAX - 1); + + mkdir(dataDir, 0777); + std::strncat(dataDir, "/MOD Desktop", PATH_MAX - 1); + mkdir(dataDir, 0777); + + set_envp_value(envp, "MOD_DATA_DIR", dataDir); + #endif + + // reusable + #ifdef _WIN32 + WCHAR path[MAX_PATH] = {}; + const size_t appDirLen = std::wcslen(appDir); + const size_t dataDirLen = std::wcslen(dataDir); + #else + char path[PATH_MAX] = {}; + const size_t appDirLen = std::strlen(appDir); + const size_t dataDirLen = std::strlen(dataDir); + #endif + + // generate UID + uint8_t key[16] = {}; + #if defined(__APPLE__) + if (const CFMutableDictionaryRef deviceRef = IOServiceMatching("IOPlatformExpertDevice")) + { + const io_service_t service = IOServiceGetMatchingService(MACH_PORT_NULL, deviceRef); + + if (service != IO_OBJECT_NULL) + { + const CFTypeRef uuidRef = IORegistryEntryCreateCFProperty(service, CFSTR("IOPlatformUUID"), kCFAllocatorDefault, 0); + const CFStringRef uuidStr = reinterpret_cast(uuidRef); + + if (CFGetTypeID(uuidRef) == CFStringGetTypeID() && CFStringGetLength(uuidStr) >= 36) + { + CFStringGetCString(uuidStr, path, PATH_MAX - 1, kCFStringEncodingUTF8); + + for (int i=0, j=0; i+j<36; ++i) + { + if (path[i*2+j] == '-') + ++j; + key[i] = char2u8(path[i*2+j]) << 4; + key[i] |= char2u8(path[i*2+j+1]) << 0; + } + } + + CFRelease(uuidRef); + IOObjectRelease(service); + } + } + #elif defined(_WIN32) + // TODO + #else + if (FILE* const f = fopen("/etc/machine-id", "r")) + { + if (fread(path, PATH_MAX - 1, 1, f) == 0 && strlen(path) >= 33) + { + for (int i=0; i<16; ++i) + { + key[i] = char2u8(path[i*2]) << 4; + key[i] |= char2u8(path[i*2+1]) << 0; + } + } + fclose(f); + } + #endif + + /* + { + QMessageAuthenticationCode qhash(QCryptographicHash::Sha256); + qhash.setKey(QByteArray::fromRawData(reinterpret_cast(key), sizeof(key))); + qhash.addData(reinterpret_cast(mod_desktop_hash), sizeof(mod_desktop_hash)); + + QByteArray qresult(qhash.result().toHex(':').toUpper()); + qresult.truncate(32 + 15); + + #ifdef _WIN32 + set_envp_value(envp, L"MOD_DEVICE_UID", qresult.constData()); + #else + set_envp_value(envp, "MOD_DEVICE_UID", qresult.constData(), 1); + #endif + } + */ + + // set path to factory pedalboards + #ifdef _WIN32 + std::memcpy(path, appDir, appDirLen * sizeof(WCHAR)); + std::wcsncpy(path + appDirLen, L"\\pedalboards", MAX_PATH - appDirLen - 1); + set_envp_value(envp, L"MOD_FACTORY_PEDALBOARDS_DIR", path); + #else + std::memcpy(path, appDir, appDirLen); + #ifdef __APPLE__ + std::strncpy(path + appDirLen - 5, "Resources/pedalboards", PATH_MAX - appDirLen - 1); + #else + std::strncpy(path + appDirLen, "/pedalboards", PATH_MAX - appDirLen - 1); + #endif + set_envp_value(envp, "MOD_FACTORY_PEDALBOARDS_DIR", path); + #endif + + // set path to plugin loadable "user-files"; also make sure it exists + #ifdef _WIN32 + std::memcpy(path, dataDir, dataDirLen * sizeof(WCHAR)); + std::wcsncpy(path + dataDirLen, L"\\user-files", MAX_PATH - dataDirLen - 1); + _wmkdir(path); + gMOD_USER_FILES_DIR = set_envp_value(envp, L"MOD_USER_FILES_DIR", path); + #else + std::memcpy(path, dataDir, dataDirLen); + std::strncpy(path + dataDirLen, "/user-files", PATH_MAX - dataDirLen - 1); + mkdir(path, 0777); + gMOD_USER_FILES_DIR = set_envp_value(envp, "MOD_USER_FILES_DIR", path); + #endif + + // set path to MOD keys (plugin licenses) + // NOTE must terminate with a path separator + #ifdef _WIN32 + std::memcpy(path, dataDir, dataDirLen * sizeof(WCHAR)); + std::wcsncpy(path + dataDirLen, L"\\keys\\", MAX_PATH - dataDirLen - 1); + set_envp_value(envp, L"MOD_KEYS_PATH", path); + #else + std::memcpy(path, dataDir, dataDirLen); + std::strncpy(path + dataDirLen, "/keys/", PATH_MAX - dataDirLen - 1); + set_envp_value(envp, "MOD_KEYS_PATH", path); + #endif + + // set path to MOD LV2 plugins (first local/user, then app/system) + #ifdef _WIN32 + std::memcpy(path, dataDir, dataDirLen * sizeof(WCHAR)); + std::wcsncpy(path + dataDirLen, L"\\lv2;", MAX_PATH - dataDirLen - 1); + std::wcsncat(path, appDir, MAX_PATH - 1); + std::wcsncat(path, L"\\plugins", MAX_PATH - 1); + set_envp_value(envp, L"LV2_PATH", path); + #else + std::memcpy(path, dataDir, dataDirLen); + std::strncpy(path + dataDirLen, "/lv2:", PATH_MAX - dataDirLen - 1); + std::strncat(path, appDir, PATH_MAX - 1); + #ifdef __APPLE__ + path[dataDirLen + 5 + appDirLen - 5] = '\0'; // remove "MacOS" + std::strncat(path, "LV2", PATH_MAX - 1); + #else + std::strncat(path, "/plugins", PATH_MAX - 1); + #endif + set_envp_value(envp, "LV2_PATH", path); + #endif + + #if !(defined(__APPLE__) || defined(_WIN32)) + // add our custom lib path on top of LD_LIBRARY_PATH to make sure jackd can run + if (const char* const ldpath = getenv("LD_LIBRARY_PATH")) + { + std::memcpy(path, appDir, appDirLen); + path[appDirLen] = ':'; + std::strncpy(path + appDirLen + 1, ldpath, PATH_MAX - appDirLen - 2); + set_envp_value(envp, "LD_LIBRARY_PATH", path); + } + else + { + set_envp_value(envp, "LD_LIBRARY_PATH", appDir); + } + #endif + + // other + #ifndef _WIN32 + std::memcpy(path, appDir, appDirLen); + std::strncpy(path + appDirLen, "/jack", PATH_MAX - appDirLen - 1); + set_envp_value(envp, "JACK_DRIVER_DIR", path); + #endif + + return envp; +} + +// ----------------------------------------------------------------------------------------------------------- + +static void* _openWebGui(void*) +{ + #if defined(_WIN32) + ShellExecuteW(nullptr, L"open", L"http://127.0.0.1:18181", nullptr, nullptr, SW_SHOWDEFAULT); + #elif defined(__APPLE__) + std::system("open \"http://127.0.0.1:18181\""); + #else + std::system("xdg-open \"http://127.0.0.1:18181\""); + #endif + return nullptr; +} + +void openWebGui() +{ + pthread_t thread; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, 1); + pthread_create(&thread, &attr, _openWebGui, nullptr); + pthread_attr_destroy(&attr); +} + +// ----------------------------------------------------------------------------------------------------------- + +static void* _openUserFilesDir(void*) +{ + #if defined(_WIN32) + ShellExecuteW(NULL, L"explore", gMOD_USER_FILES_DIR, nullptr, nullptr, SW_SHOWDEFAULT); + #elif defined(__APPLE__) + String cmd("open \""); + cmd += gMOD_USER_FILES_DIR; + cmd += "\""; + std::system(cmd); + #else + String cmd("xdg-open \""); + cmd += gMOD_USER_FILES_DIR; + cmd += "\""; + #endif + return nullptr; +} + +void openUserFilesDir() +{ + pthread_t thread; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, 1); + pthread_create(&thread, &attr, _openUserFilesDir, nullptr); + pthread_attr_destroy(&attr); +} + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/src/plugin/utils.hpp b/src/plugin/utils.hpp new file mode 100644 index 0000000..03fb5d4 --- /dev/null +++ b/src/plugin/utils.hpp @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 MOD Audio UG +// SPDX-License-Identifier: AGPL-3.0-or-later + +#pragma once + +#include "DistrhoPluginUtils.hpp" + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------------------------------------------- + +/* Get the MOD Desktop application directory. + */ +const char* getAppDir(); + +/* Get environment to be used for a child process. + */ +char* const* getEvironment(); + +/* Open a web browser with the mod-ui URL as address. + */ +void openWebGui(); + +/* Open the "user files" directory in a file manager/explorer. + */ +void openUserFilesDir(); + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/src/systray/utils.cpp b/src/systray/utils.cpp index 5c008fd..7a4d852 100644 --- a/src/systray/utils.cpp +++ b/src/systray/utils.cpp @@ -47,6 +47,7 @@ constexpr uint8_t char2u8(const uint8_t c) : 0; } +// NOTE this needs to match getEvironment from plugin side void initEvironment() { // base environment details @@ -264,7 +265,6 @@ void initEvironment() #if !(defined(__APPLE__) || defined(_WIN32)) // special handling for PipeWire JACK, need to find full path to shared lib - bool usingPipeWire = false; if (void* const pwlib = dlmopen(LM_ID_NEWLM, "libjack.so.0", RTLD_NOW|RTLD_LOCAL)) { typedef int (*jacksym)(void); @@ -276,7 +276,6 @@ void initEvironment() Dl_info info = {}; dladdr(sym2, &info); setenv("JACKBRIDGE_FILENAME", info.dli_fname, 1); - usingPipeWire = true; fprintf(stdout, "MOD Desktop DEBUG: jacklib syms %p %p | %d | using pipewire with filename '%s'\n", sym1, sym2, sym1(), info.dli_fname); } else @@ -288,7 +287,7 @@ void initEvironment() dlclose(pwlib); } - // if LD_LIBRARY_PATH is set, add our custom lib path on top to make sure jackd can run + // add our custom lib path on top of LD_LIBRARY_PATH to make sure jackd can run if (const char* const ldpath = getenv("LD_LIBRARY_PATH")) { std::memcpy(path, appDir, appDirLen); @@ -296,8 +295,7 @@ void initEvironment() std::strncpy(path + appDirLen + 1, ldpath, PATH_MAX - appDirLen - 2); setenv("LD_LIBRARY_PATH", path, 1); } - // always set path in case of PipeWire - else if (usingPipeWire) + else { setenv("LD_LIBRARY_PATH", appDir, 1); }