From bc06ebbfd890283c50864c3caa6295d79512dffc Mon Sep 17 00:00:00 2001 From: falkTX Date: Thu, 7 Sep 2023 11:10:34 +0200 Subject: [PATCH] Fix some wine bridge details Signed-off-by: falkTX --- source/backend/plugin/CarlaPluginBridge.cpp | 50 +- .../frontend/pluginlist/pluginlistdialog.cpp | 2104 +++++++++++++++++ 2 files changed, 2131 insertions(+), 23 deletions(-) create mode 100644 source/frontend/pluginlist/pluginlistdialog.cpp diff --git a/source/backend/plugin/CarlaPluginBridge.cpp b/source/backend/plugin/CarlaPluginBridge.cpp index 332d26be4..ead525031 100644 --- a/source/backend/plugin/CarlaPluginBridge.cpp +++ b/source/backend/plugin/CarlaPluginBridge.cpp @@ -289,7 +289,7 @@ protected: if (fWinePrefix.isNotEmpty()) { carla_setenv("WINEDEBUG", "-all"); - carla_setenv("WINEPREFIX", fWinePrefix.toRawUTF8()); + carla_setenv("WINEPREFIX", fWinePrefix.buffer()); if (options.wine.rtPrio) { @@ -306,7 +306,7 @@ protected: carla_setenv("WINE_SVR_RT", strBuf); carla_stdout("Using WINEPREFIX '%s', with base RT prio %i and server RT prio %i", - fWinePrefix.toRawUTF8(), options.wine.baseRtPrio, options.wine.serverRtPrio); + fWinePrefix.buffer(), options.wine.baseRtPrio, options.wine.serverRtPrio); } else { @@ -318,7 +318,7 @@ protected: carla_unsetenv("WINE_RT_PRIO"); carla_unsetenv("WINE_SVR_RT"); - carla_stdout("Using WINEPREFIX '%s', without RT priorities", fWinePrefix.toRawUTF8()); + carla_stdout("Using WINEPREFIX '%s', without RT priorities", fWinePrefix.buffer()); } } #endif @@ -394,9 +394,9 @@ private: String fBridgeBinary; String fLabel; String fShmIds; -#ifndef CARLA_OS_WIN - String fWinePrefix; -#endif + #ifndef CARLA_OS_WIN + CarlaString fWinePrefix; + #endif CarlaScopedPointer fProcess; @@ -2540,15 +2540,15 @@ public: if (fBinaryType == BINARY_WIN32 || fBinaryType == BINARY_WIN64) { const StringArray driveLetterSplit(StringArray::fromTokens(realBigValueFilePath, ":/", "")); - carla_stdout("big value save path BEFORE => %s", realBigValueFilePath.toRawUTF8()); + carla_stdout("big value save path BEFORE => '%s' | using wineprefix '%s'", realBigValueFilePath.toRawUTF8(), fWinePrefix.buffer()); - realBigValueFilePath = fWinePrefix; + realBigValueFilePath = fWinePrefix.buffer(); realBigValueFilePath += "/drive_"; realBigValueFilePath += driveLetterSplit[0].toLowerCase(); realBigValueFilePath += driveLetterSplit[1]; realBigValueFilePath = realBigValueFilePath.replace("\\", "/"); - carla_stdout("big value save path AFTER => %s", realBigValueFilePath.toRawUTF8()); + carla_stdout("big value save path AFTER => '%s'", realBigValueFilePath.toRawUTF8()); } #endif @@ -2588,15 +2588,15 @@ public: if (fBinaryType == BINARY_WIN32 || fBinaryType == BINARY_WIN64) { const StringArray driveLetterSplit(StringArray::fromTokens(realChunkFilePath, ":/", "")); - carla_stdout("chunk save path BEFORE => %s", realChunkFilePath.toRawUTF8()); + carla_stdout("chunk save path BEFORE => '%s' | using wineprefix '%s'", realChunkFilePath.toRawUTF8(), fWinePrefix.buffer()); - realChunkFilePath = fWinePrefix; + realChunkFilePath = fWinePrefix.buffer(); realChunkFilePath += "/drive_"; realChunkFilePath += driveLetterSplit[0].toLowerCase(); realChunkFilePath += driveLetterSplit[1]; realChunkFilePath = realChunkFilePath.replace("\\", "/"); - carla_stdout("chunk save path AFTER => %s", realChunkFilePath.toRawUTF8()); + carla_stdout("chunk save path AFTER => '%s'", realChunkFilePath.toRawUTF8()); } #endif @@ -2778,20 +2778,24 @@ public: { const EngineOptions& engineOptions(pData->engine->getOptions()); + water::String winePrefix; + if (engineOptions.wine.autoPrefix) - fWinePrefix = findWinePrefix(pData->filename); + winePrefix = findWinePrefix(pData->filename); - if (fWinePrefix.isEmpty()) + if (winePrefix.isEmpty()) { const char* const envWinePrefix(std::getenv("WINEPREFIX")); if (envWinePrefix != nullptr && envWinePrefix[0] != '\0') - fWinePrefix = envWinePrefix; + winePrefix = envWinePrefix; else if (engineOptions.wine.fallbackPrefix != nullptr && engineOptions.wine.fallbackPrefix[0] != '\0') - fWinePrefix = engineOptions.wine.fallbackPrefix; + winePrefix = engineOptions.wine.fallbackPrefix; else - fWinePrefix = File::getSpecialLocation(File::userHomeDirectory).getFullPathName() + "/.wine"; + winePrefix = File::getSpecialLocation(File::userHomeDirectory).getFullPathName() + "/.wine"; } + + fWinePrefix = winePrefix.toRawUTF8(); } #endif @@ -2808,9 +2812,9 @@ public: std::strncpy(shmIdsStr+6*3, &fShmNonRtServerControl.filename[fShmNonRtServerControl.filename.length()-6], 6); fBridgeThread.setData( -#ifndef CARLA_OS_WIN - fWinePrefix.toRawUTF8(), -#endif + #ifndef CARLA_OS_WIN + fWinePrefix, + #endif binaryArchName, bridgeBinary, label, shmIdsStr); } @@ -2930,9 +2934,9 @@ private: BridgeNonRtClientControl fShmNonRtClientControl; BridgeNonRtServerControl fShmNonRtServerControl; -#ifndef CARLA_OS_WIN - String fWinePrefix; -#endif + #ifndef CARLA_OS_WIN + CarlaString fWinePrefix; + #endif class ReceivingParamText { public: diff --git a/source/frontend/pluginlist/pluginlistdialog.cpp b/source/frontend/pluginlist/pluginlistdialog.cpp new file mode 100644 index 000000000..6d94e41eb --- /dev/null +++ b/source/frontend/pluginlist/pluginlistdialog.cpp @@ -0,0 +1,2104 @@ +/* + * Carla plugin host + * Copyright (C) 2011-2023 Filipe Coelho + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "pluginlistdialog.hpp" +#include "pluginrefreshdialog.hpp" + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy" +# pragma clang diagnostic ignored "-Wdeprecated-register" +#elif defined(__GNUC__) && __GNUC__ >= 8 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wclass-memaccess" +# pragma GCC diagnostic ignored "-Wdeprecated-copy" +#endif + +#include +#include +#include +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) && __GNUC__ >= 8 +# pragma GCC diagnostic pop +#endif + +#include "qcarlastring.hpp" +#include "qsafesettings.hpp" + +#include "CarlaBackendUtils.hpp" +#include "CarlaJuceUtils.hpp" +#include "CarlaUtils.h" + +#include "CarlaString.hpp" + +#include + +CARLA_BACKEND_USE_NAMESPACE + +// -------------------------------------------------------------------------------------------------------------------- +// getenv with a fallback value if unset + +static inline +const char* getEnvWithFallback(const char* const env, const char* const fallback) +{ + if (const char* const value = std::getenv(env)) + return value; + + return fallback; +} + +// -------------------------------------------------------------------------------------------------------------------- +// Plugin paths (from env vars first, then default locations) + +struct PluginPaths { + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + QCarlaString ladspa; + QCarlaString dssi; + #endif + QCarlaString lv2; + QCarlaString vst2; + QCarlaString vst3; + QCarlaString clap; + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + QCarlaString jsfx; + QCarlaString sf2; + QCarlaString sfz; + #endif + + PluginPaths() + { + // get common env vars + const QString HOME = QDir::toNativeSeparators(QDir::homePath()); + + #if defined(CARLA_OS_WIN) + const char *const envAPPDATA = std::getenv("APPDATA"); + const char *const envLOCALAPPDATA = getEnvWithFallback("LOCALAPPDATA", envAPPDATA); + const char *const envPROGRAMFILES = std::getenv("PROGRAMFILES"); + const char* const envPROGRAMFILESx86 = std::getenv("PROGRAMFILES(x86)"); + const char *const envCOMMONPROGRAMFILES = std::getenv("COMMONPROGRAMFILES"); + const char* const envCOMMONPROGRAMFILESx86 = std::getenv("COMMONPROGRAMFILES(x86)"); + + // Small integrity tests + if (envAPPDATA == nullptr) + { + qFatal("APPDATA variable not set, cannot continue"); + abort(); + } + + if (envPROGRAMFILES == nullptr) + { + qFatal("PROGRAMFILES variable not set, cannot continue"); + abort(); + } + + if (envCOMMONPROGRAMFILES == nullptr) + { + qFatal("COMMONPROGRAMFILES variable not set, cannot continue"); + abort(); + } + + const QCarlaString APPDATA(envAPPDATA); + const QCarlaString LOCALAPPDATA(envLOCALAPPDATA); + const QCarlaString PROGRAMFILES(envPROGRAMFILES); + const QCarlaString COMMONPROGRAMFILES(envCOMMONPROGRAMFILES); + #elif !defined(CARLA_OS_MAC) + const QCarlaString CONFIG_HOME(getEnvWithFallback("XDG_CONFIG_HOME", (HOME + "/.config").toUtf8())); + #endif + + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + // now set paths, listing format path spec if available + if (const char *const envLADSPA = std::getenv("LADSPA_PATH")) + { + ladspa = envLADSPA; + } + else + { + // no official spec, use common paths + #if defined(CARLA_OS_WIN) + ladspa = APPDATA + "\\LADSPA"; + ladspa += ";" + PROGRAMFILES + "\\LADSPA"; + #elif defined(CARLA_OS_HAIKU) + ladspa = HOME + "/.ladspa"; + ladspa += ":/system/add-ons/media/ladspaplugins"; + ladspa += ":/system/lib/ladspa"; + #elif defined(CARLA_OS_MAC) + ladspa = HOME + "/Library/Audio/Plug-Ins/LADSPA"; + ladspa += ":/Library/Audio/Plug-Ins/LADSPA"; + #else + ladspa = HOME + "/.ladspa"; + ladspa += ":/usr/local/lib/ladspa"; + ladspa += ":/usr/lib/ladspa"; + #endif + } + + if (const char *const envDSSI = std::getenv("DSSI_PATH")) + { + dssi = envDSSI; + } + else + { + // no official spec, use common paths + #if defined(CARLA_OS_WIN) + dssi = APPDATA + "\\DSSI"; + dssi += ";" + PROGRAMFILES + "\\DSSI"; + #elif defined(CARLA_OS_HAIKU) + dssi = HOME + "/.dssi"; + dssi += ":/system/add-ons/media/dssiplugins"; + dssi += ":/system/lib/dssi"; + #elif defined(CARLA_OS_MAC) + dssi = HOME + "/Library/Audio/Plug-Ins/DSSI"; + dssi += ":/Library/Audio/Plug-Ins/DSSI"; + #else + dssi = HOME + "/.dssi"; + dssi += ":/usr/local/lib/dssi"; + dssi += ":/usr/lib/dssi"; + #endif + } + #endif // !CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + + if (const char *const envLV2 = std::getenv("LV2_PATH")) + { + lv2 = envLV2; + } + else + { + // https://lv2plug.in/pages/filesystem-hierarchy-standard.html + #if defined(CARLA_OS_WIN) + lv2 = APPDATA + "\\LV2"; + lv2 += ";" + COMMONPROGRAMFILES + "\\LV2"; + #elif defined(CARLA_OS_HAIKU) + lv2 = HOME + "/.lv2"; + lv2 += ":/system/add-ons/media/lv2plugins"; + #elif defined(CARLA_OS_MAC) + lv2 = HOME + "/Library/Audio/Plug-Ins/LV2"; + lv2 += ":/Library/Audio/Plug-Ins/LV2"; + #else + lv2 = HOME + "/.lv2"; + lv2 += ":/usr/local/lib/lv2"; + lv2 += ":/usr/lib/lv2"; + #endif + } + + if (const char *const envVST2 = std::getenv("VST_PATH")) + { + vst2 = envVST2; + } + else + { + #if defined(CARLA_OS_WIN) + // https://helpcenter.steinberg.de/hc/en-us/articles/115000177084 + vst2 = PROGRAMFILES + "\\VSTPlugins"; + vst2 += ";" + PROGRAMFILES + "\\Steinberg\\VSTPlugins"; + vst2 += ";" + COMMONPROGRAMFILES + "\\VST2"; + vst2 += ";" + COMMONPROGRAMFILES + "\\Steinberg\\VST2"; + #elif defined(CARLA_OS_HAIKU) + vst2 = HOME + "/.vst"; + vst2 += ":/system/add-ons/media/vstplugins"; + #elif defined(CARLA_OS_MAC) + // https://helpcenter.steinberg.de/hc/en-us/articles/115000171310 + vst2 = HOME + "/Library/Audio/Plug-Ins/VST"; + vst2 += ":/Library/Audio/Plug-Ins/VST"; + #else + // no official spec, use common paths + vst2 = HOME + "/.vst"; + vst2 += ":" + HOME + "/.lxvst"; + vst2 += ":/usr/local/lib/vst"; + vst2 += ":/usr/local/lib/lxvst"; + vst2 += ":/usr/lib/vst"; + vst2 += ":/usr/lib/lxvst"; + #endif + } + + if (const char *const envVST3 = std::getenv("VST3_PATH")) + { + vst3 = envVST3; + } + else + { + // https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/Locations+Format/Plugin+Locations.html + #if defined(CARLA_OS_WIN) + vst3 = LOCALAPPDATA + "\\Programs\\Common\\VST3"; + vst3 += ";" + COMMONPROGRAMFILES + "\\VST3"; + #elif defined(CARLA_OS_HAIKU) + vst3 = HOME + "/.vst3"; + vst3 += ":/system/add-ons/media/vst3plugins"; + #elif defined(CARLA_OS_MAC) + vst3 = HOME + "/Library/Audio/Plug-Ins/VST3"; + vst3 += ":/Library/Audio/Plug-Ins/VST3"; + #else + vst3 = HOME + "/.vst3"; + vst3 += ":/usr/local/lib/vst3"; + vst3 += ":/usr/lib/vst3"; + #endif + } + + if (const char *const envCLAP = std::getenv("CLAP_PATH")) + { + clap = envCLAP; + } + else + { + // https://github.com/free-audio/clap/blob/main/include/clap/entry.h + #if defined(CARLA_OS_WIN) + clap = LOCALAPPDATA + "\\Programs\\Common\\CLAP"; + clap += ";" + COMMONPROGRAMFILES + "\\CLAP"; + #elif defined(CARLA_OS_HAIKU) + clap = HOME + "/.clap"; + clap += ":/system/add-ons/media/clapplugins"; + #elif defined(CARLA_OS_MAC) + clap = HOME + "/Library/Audio/Plug-Ins/CLAP"; + clap += ":/Library/Audio/Plug-Ins/CLAP"; + #else + clap = HOME + "/.clap"; + clap += ":/usr/local/lib/clap"; + clap += ":/usr/lib/clap"; + #endif + } + + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + if (const char *const envJSFX = std::getenv("JSFX_PATH")) + { + jsfx = envJSFX; + } + else + { + // REAPER user data directory + #if defined(CARLA_OS_WIN) + jsfx = APPDATA + "\\REAPER\\Effects"; + #elif defined(CARLA_OS_MAC) + jsfx = HOME + "/Library/Application Support/REAPER/Effects"; + #else + jsfx = CONFIG_HOME + "/REAPER/Effects"; + #endif + } + + if (const char *const envSF2 = std::getenv("SF2_PATH")) + { + sf2 = envSF2; + } + else + { + #if defined(CARLA_OS_WIN) + sf2 = APPDATA + "\\SF2"; + #else + sf2 = HOME + "/.sounds/sf2"; + sf2 += ":" + HOME + "/.sounds/sf3"; + sf2 += ":/usr/share/sounds/sf2"; + sf2 += ":/usr/share/sounds/sf3"; + sf2 += ":/usr/share/soundfonts"; + #endif + } + + if (const char *const envSFZ = std::getenv("SFZ_PATH")) + { + sfz = envSFZ; + } + else + { + #if defined(CARLA_OS_WIN) + sfz = APPDATA + "\\SFZ"; + #else + sfz = HOME + "/.sounds/sfz"; + sfz += ":/usr/share/sounds/sfz"; + #endif + } + #endif // !CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + + #ifdef CARLA_OS_WIN + if (envPROGRAMFILESx86 != nullptr) + { + const QCarlaString PROGRAMFILESx86(envPROGRAMFILESx86); + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + ladspa += ";" + PROGRAMFILESx86 + "\\LADSPA"; + dssi += ";" + PROGRAMFILESx86 + "\\DSSI"; + #endif + vst2 += ";" + PROGRAMFILESx86 + "\\VSTPlugins"; + vst2 += ";" + PROGRAMFILESx86 + "\\Steinberg\\VSTPlugins"; + } + + if (envCOMMONPROGRAMFILESx86 != nullptr) + { + const QCarlaString COMMONPROGRAMFILESx86(envCOMMONPROGRAMFILESx86); + vst3 += COMMONPROGRAMFILESx86 + "\\VST3"; + clap += COMMONPROGRAMFILESx86 + "\\CLAP"; + } + #elif !defined(CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS) + QCarlaString winePrefix; + + if (const char* const envWINEPREFIX = std::getenv("WINEPREFIX")) + winePrefix = envWINEPREFIX; + + if (winePrefix.isEmpty()) + winePrefix = HOME + "/.wine"; + + if (QDir(winePrefix).exists()) + { + vst2 += ":" + winePrefix + "/drive_c/Program Files/VstPlugins"; + vst2 += ":" + winePrefix + "/drive_c/Program Files/Steinberg/VstPlugins"; + vst2 += ":" + winePrefix + "/drive_c/Program Files/Common Files/VST2"; + vst3 += ":" + winePrefix + "/drive_c/Program Files/Common Files/VST3"; + clap += ":" + winePrefix + "/drive_c/Program Files/Common Files/CLAP"; + + #ifdef CARLA_OS_64BIT + if (QDir(winePrefix + "/drive_c/Program Files (x86)").exists()) + { + vst2 += ":" + winePrefix + "/drive_c/Program Files (x86)/VstPlugins"; + vst2 += ":" + winePrefix + "/drive_c/Program Files (x86)/Steinberg/VstPlugins"; + vst2 += ":" + winePrefix + "/drive_c/Program Files (x86)/Common Files/VST2"; + vst3 += ":" + winePrefix + "/drive_c/Program Files (x86)/Common Files/VST3"; + clap += ":" + winePrefix + "/drive_c/Program Files (x86)/Common Files/CLAP"; + } + #endif + } + #endif + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// Backwards-compatible horizontalAdvance/width call, depending on Qt version + +static inline +int fontMetricsHorizontalAdvance(const QFontMetrics& fontMetrics, const QString& string) +{ + #if QT_VERSION >= 0x50b00 + return fontMetrics.horizontalAdvance(string); + #else + return fontMetrics.width(string); + #endif +} + +// -------------------------------------------------------------------------------------------------------------------- +// Qt-compatible plugin info + +// base details, nicely packed and POD-only so we can directly use as binary +struct PluginInfoHeader { + uint16_t build; + uint16_t type; + uint32_t hints; + uint64_t uniqueId; + uint16_t audioIns; + uint16_t audioOuts; + uint16_t cvIns; + uint16_t cvOuts; + uint16_t midiIns; + uint16_t midiOuts; + uint16_t parameterIns; + uint16_t parameterOuts; +}; + +// full details, now with non-POD types +struct PluginInfo : PluginInfoHeader { + QString category; + QString filename; + QString name; + QString label; + QString maker; +}; + +// convert PluginInfo to Qt types +static QVariant asByteArray(const PluginInfo& info) +{ + QByteArray qdata; + + // start with the POD data, stored as-is + qdata.append(static_cast(static_cast(&info)), sizeof(PluginInfoHeader)); + + // then all the strings, with a null terminating byte + { + const QByteArray qcategory(info.category.toUtf8()); + qdata += qcategory.constData(); + qdata += '\0'; + } + + { + const QByteArray qfilename(info.filename.toUtf8()); + qdata += qfilename.constData(); + qdata += '\0'; + } + + { + const QByteArray qname(info.name.toUtf8()); + qdata += qname.constData(); + qdata += '\0'; + } + + { + const QByteArray qlabel(info.label.toUtf8()); + qdata += qlabel.constData(); + qdata += '\0'; + } + + { + const QByteArray qmaker(info.maker.toUtf8()); + qdata += qmaker.constData(); + qdata += '\0'; + } + + return qdata; +} + +static QVariant asVariant(const PluginInfo& info) +{ + return QVariant(asByteArray(info)); +} + +// convert Qt types to PluginInfo +static PluginInfo asPluginInfo(const QByteArray &qdata) +{ + // make sure data is big enough to fit POD data + 5 strings + CARLA_SAFE_ASSERT_RETURN(static_cast(qdata.size()) >= sizeof(PluginInfoHeader) + sizeof(char) * 5, {}); + + // read POD data first + const PluginInfoHeader* const data + = static_cast(static_cast(qdata.constData())); + PluginInfo info = {}; + info.build = data->build; + info.type = data->type; + info.hints = data->hints; + info.uniqueId = data->uniqueId; + info.audioIns = data->audioIns; + info.audioOuts = data->audioOuts; + info.cvIns = data->cvIns; + info.cvOuts = data->cvOuts; + info.midiIns = data->midiIns; + info.midiOuts = data->midiOuts; + info.parameterIns = data->parameterIns; + info.parameterOuts = data->parameterOuts; + + // then all the strings, keeping the same order as in `asVariant` + const char* sdata = static_cast(static_cast(data + 1)); + + info.category = QString::fromUtf8(sdata); + sdata += info.category.size() + 1; + + info.filename = QString::fromUtf8(sdata); + sdata += info.filename.size() + 1; + + info.name = QString::fromUtf8(sdata); + sdata += info.name.size() + 1; + + info.label = QString::fromUtf8(sdata); + sdata += info.label.size() + 1; + + info.maker = QString::fromUtf8(sdata); + sdata += info.maker.size() + 1; + + return info; +} + +static PluginInfo asPluginInfo(const QVariant& var) +{ + return asPluginInfo(var.toByteArray()); +} + +static QList asPluginInfoList(const QVariant& var) +{ + QCarlaByteArray qdata(var.toByteArray()); + + QList plist; + + while (!qdata.isEmpty()) + { + const PluginInfo info = asPluginInfo(qdata); + CARLA_SAFE_ASSERT_RETURN(info.build != BINARY_NONE, {}); + + plist.append(info); + qdata = qdata.sliced(sizeof(PluginInfoHeader) + + info.category.size() + info.filename.size() + info.name.size() + + info.label.size() + info.maker.size() + 5); + } + + return plist; +} + +// -------------------------------------------------------------------------------------------------------------------- +// Qt-compatible plugin favorite + +// base details, nicely packed and POD-only so we can directly use as binary +struct PluginFavoriteHeader { + uint16_t type; + uint64_t uniqueId; +}; + +// full details, now with non-POD types +struct PluginFavorite : PluginFavoriteHeader { + QString filename; + QString label; + + PluginFavorite() + { + type = PLUGIN_NONE; + uniqueId = 0; + } + + PluginFavorite(uint16_t t, uint64_t u, const QString& f, const QString& l) + : filename(f), label(l) + { + type = t; + uniqueId = u; + } + + bool operator==(const PluginFavorite& other) const + { + return type == other.type && uniqueId == other.uniqueId && filename == other.filename && label == other.label; + } +}; + +// convert PluginFavorite to Qt types +static QByteArray asByteArray(const PluginFavorite& fav) +{ + QByteArray qdata; + + // start with the POD data, stored as-is + qdata.append(static_cast(static_cast(&fav)), sizeof(PluginFavoriteHeader)); + + // then all the strings, with a null terminating byte + { + const QByteArray qfilename(fav.filename.toUtf8()); + qdata += qfilename.constData(); + qdata += '\0'; + } + + { + const QByteArray qlabel(fav.label.toUtf8()); + qdata += qlabel.constData(); + qdata += '\0'; + } + + return qdata; +} + +static QVariant asVariant(const QList& favlist) +{ + QByteArray qdata; + + for (const PluginFavorite &fav : favlist) + qdata += asByteArray(fav); + + return QVariant(qdata); +} + +// convert Qt types to PluginInfo +static PluginFavorite asPluginFavorite(const QByteArray& qdata) +{ + // make sure data is big enough to fit POD data + 3 strings + CARLA_SAFE_ASSERT_RETURN(static_cast(qdata.size()) >= sizeof(PluginFavoriteHeader) + sizeof(char) * 3, {}); + + // read POD data first + const PluginFavoriteHeader* const data + = static_cast(static_cast(qdata.constData())); + PluginFavorite fav = { data->type, data->uniqueId, {}, {} }; + + // then all the strings, keeping the same order as in `asVariant` + const char* sdata = static_cast(static_cast(data + 1)); + + fav.filename = QString::fromUtf8(sdata); + sdata += fav.filename.size() + 1; + + fav.label = QString::fromUtf8(sdata); + sdata += fav.label.size() + 1; + + return fav; +} + +static QList asPluginFavoriteList(const QVariant& var) +{ + QCarlaByteArray qdata(var.toByteArray()); + + QList favlist; + + while (!qdata.isEmpty()) + { + const PluginFavorite fav = asPluginFavorite(qdata); + CARLA_SAFE_ASSERT_RETURN(fav.type != PLUGIN_NONE, {}); + + favlist.append(fav); + qdata = qdata.sliced(sizeof(PluginFavoriteHeader) + fav.filename.size() + fav.label.size() + 2); + } + + return favlist; +} + +// create PluginFavorite from PluginInfo data +static PluginFavorite asPluginFavorite(const PluginInfo& info) +{ + return PluginFavorite(info.type, info.uniqueId, info.filename, info.label); +} + +// -------------------------------------------------------------------------------------------------------------------- +// discovery callbacks + +static void discoveryCallback(void* const ptr, const CarlaPluginDiscoveryInfo* const info, const char* const sha1sum) +{ + static_cast(ptr)->addPluginInfo(info, sha1sum); +} + +static bool checkCacheCallback(void* const ptr, const char* const filename, const char* const sha1sum) +{ + if (sha1sum == nullptr) + return false; + + return static_cast(ptr)->checkPluginCache(filename, sha1sum); +} + +// -------------------------------------------------------------------------------------------------------------------- + +struct PluginListDialog::PrivateData { + int lastTableWidgetIndex = 0; + int timerId = 0; + PluginInfo retPlugin; + + // To be changed by parent + bool hasLoadedLv2Plugins = false; + + struct Discovery { + BinaryType btype = BINARY_NATIVE; + PluginType ptype = PLUGIN_NONE; + bool firstInit = true; + bool ignoreCache = false; + bool checkInvalid = false; + CarlaPluginDiscoveryHandle handle = nullptr; + QCarlaString tool; + CarlaScopedPointer dialog; + Discovery() + { + restart(); + } + + ~Discovery() + { + if (handle != nullptr) + carla_plugin_discovery_stop(handle); + } + + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + bool nextTool() + { + if (handle != nullptr) + { + carla_plugin_discovery_stop(handle); + handle = nullptr; + } + + #ifdef CARLA_OS_WIN + #ifdef CARLA_OS_WIN64 + // look for win32 plugins on win64 + if (btype == BINARY_NATIVE) + { + btype = BINARY_WIN32; + ptype = PLUGIN_INTERNAL; + tool = carla_get_library_folder(); + tool += CARLA_OS_SEP_STR "carla-discovery-win32.exe"; + + if (QFile(tool).exists()) + return true; + } + #endif + + // no other types to try + return false; + #else // CARLA_OS_WIN + + #ifndef CARLA_OS_MAC + // try 32bit plugins on 64bit systems, skipping macOS where 32bit is no longer supported + if (btype == BINARY_NATIVE) + { + btype = BINARY_POSIX32; + ptype = PLUGIN_INTERNAL; + tool = carla_get_library_folder(); + tool += CARLA_OS_SEP_STR "carla-discovery-posix32"; + + if (QFile(tool).exists()) + return true; + } + #endif + + // try wine bridges + #ifdef CARLA_OS_64BIT + if (btype == BINARY_NATIVE || btype == BINARY_POSIX32) + { + btype = BINARY_WIN64; + ptype = PLUGIN_INTERNAL; + tool = carla_get_library_folder(); + tool += CARLA_OS_SEP_STR "carla-discovery-win64.exe"; + + if (QFile(tool).exists()) + return true; + } + #endif + + if (btype != BINARY_WIN32) + { + btype = BINARY_WIN32; + ptype = PLUGIN_INTERNAL; + tool = carla_get_library_folder(); + tool += CARLA_OS_SEP_STR "carla-discovery-win32.exe"; + + if (QFile(tool).exists()) + return true; + } + + return false; + #endif // CARLA_OS_WIN + } + #endif // CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + + void restart() + { + btype = BINARY_NATIVE; + ptype = PLUGIN_NONE; + tool = carla_get_library_folder(); + tool += CARLA_OS_SEP_STR "carla-discovery-native"; + #ifdef CARLA_OS_WIN + tool += ".exe"; + #endif + } + } discovery; + + PluginPaths paths; + + struct { + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + std::vector internal; + std::vector ladspa; + std::vector dssi; + #endif + std::vector lv2; + std::vector vst2; + std::vector vst3; + std::vector clap; + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + #ifdef CARLA_OS_MAC + std::vector au; + #endif + std::vector jsfx; + std::vector kits; + #endif + QMap> cache; + QList favorites; + + bool add(const PluginInfo& pinfo) + { + switch (pinfo.type) + { + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + case PLUGIN_INTERNAL: internal.push_back(pinfo); return true; + case PLUGIN_LADSPA: ladspa.push_back(pinfo); return true; + case PLUGIN_DSSI: dssi.push_back(pinfo); return true; + #endif + case PLUGIN_LV2: lv2.push_back(pinfo); return true; + case PLUGIN_VST2: vst2.push_back(pinfo); return true; + case PLUGIN_VST3: vst3.push_back(pinfo); return true; + case PLUGIN_CLAP: clap.push_back(pinfo); return true; + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + #ifdef CARLA_OS_MAC + case PLUGIN_AU: au.push_back(pinfo); return true; + #endif + case PLUGIN_JSFX: jsfx.push_back(pinfo); return true; + case PLUGIN_SF2: + case PLUGIN_SFZ: kits.push_back(pinfo); return true; + #endif + default: return false; + } + } + } plugins; +}; + +// -------------------------------------------------------------------------------------------------------------------- +// Plugin List Dialog + +PluginListDialog::PluginListDialog(QWidget* const parent, const HostSettings* const hostSettings) + : QDialog(parent), + p(new PrivateData) +{ + ui.setupUi(this); + + // ---------------------------------------------------------------------------------------------------------------- + // Set-up global discovery options + + #ifndef CARLA_OS_WIN + carla_plugin_discovery_set_option(ENGINE_OPTION_WINE_AUTO_PREFIX, hostSettings->wineAutoPrefix, nullptr); + carla_plugin_discovery_set_option(ENGINE_OPTION_WINE_EXECUTABLE, 0, hostSettings->wineExecutable); + carla_plugin_discovery_set_option(ENGINE_OPTION_WINE_FALLBACK_PREFIX, 0, hostSettings->wineFallbackPrefix); + #endif + + // ---------------------------------------------------------------------------------------------------------------- + // Set-up GUI + + ui.b_add->setEnabled(false); + + ui.tab_info->tabBar()->hide(); + ui.tab_reqs->tabBar()->hide(); + + #ifdef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + ui.ch_internal->hide(); + ui.ch_ladspa->hide(); + ui.ch_dssi->hide(); + ui.ch_au->hide(); + ui.ch_jsfx->hide(); + ui.ch_kits->hide(); + ui.ch_gui->hide(); + ui.ch_inline_display->hide(); + ui.toolBox->setItemEnabled(3, false); + #endif + + // do not resize info frame so much + const QLayout *const infoLayout = ui.tw_info->layout(); + const QMargins infoMargins = infoLayout->contentsMargins(); + ui.tab_info->setMinimumWidth(infoMargins.left() + infoMargins.right() + infoLayout->spacing() * 3 + + fontMetricsHorizontalAdvance(ui.la_id->fontMetrics(), "Has Custom GUI: 9999999999")); + + // start with no plugin selected + checkPlugin(-1); + + // custom action that listens for Ctrl+F shortcut + addAction(ui.act_focus_search); + + #ifdef CARLA_OS_64BIT + ui.ch_bridged->setText(tr("Bridged (32bit)")); + #else + ui.ch_bridged->setChecked(false); + ui.ch_bridged->setEnabled(false); + #endif + + #if !(defined(CARLA_OS_LINUX) || defined(CARLA_OS_MAC)) + ui.ch_bridged_wine->setChecked(false); + ui.ch_bridged_wine->setEnabled(false); + #endif + + #ifdef CARLA_OS_MAC + setWindowModality(Qt::WindowModal); + #else + ui.ch_au->setChecked(false); + ui.ch_au->setEnabled(false); + ui.ch_au->setVisible(false); + #endif + + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + // ---------------------------------------------------------------------------------------------------------------- + // Load settings + + loadSettings(); + + // ---------------------------------------------------------------------------------------------------------------- + // Disable bridges if not enabled in settings + +#if 0 + // NOTE: We Assume win32 carla build will not run win64 plugins + if (WINDOWS and not kIs64bit) or not host.showPluginBridges: + ui.ch_native.setChecked(True) + ui.ch_native.setEnabled(False) + ui.ch_native.setVisible(True) + ui.ch_bridged.setChecked(False) + ui.ch_bridged.setEnabled(False) + ui.ch_bridged.setVisible(False) + ui.ch_bridged_wine.setChecked(False) + ui.ch_bridged_wine.setEnabled(False) + ui.ch_bridged_wine.setVisible(False) + + elif not host.showWineBridges: + ui.ch_bridged_wine.setChecked(False) + ui.ch_bridged_wine.setEnabled(False) + ui.ch_bridged_wine.setVisible(False) +#endif + + // ---------------------------------------------------------------------------------------------------------------- + // Set-up Icons + + if (hostSettings->useSystemIcons) + { +#if 0 + ui.b_add.setIcon(getIcon('list-add', 16, 'svgz')) + ui.b_cancel.setIcon(getIcon('dialog-cancel', 16, 'svgz')) + ui.b_clear_filters.setIcon(getIcon('edit-clear', 16, 'svgz')) + ui.b_refresh.setIcon(getIcon('view-refresh', 16, 'svgz')) + QTableWidgetItem* const hhi = ui.tableWidget->horizontalHeaderItem(TW_FAVORITE); + hhi.setIcon(getIcon('bookmarks', 16, 'svgz')) +#endif + } + + // ---------------------------------------------------------------------------------------------------------------- + // Set-up connections + + QObject::connect(this, &QDialog::finished, this, &PluginListDialog::saveSettings); + QObject::connect(ui.b_add, &QPushButton::clicked, this, &QDialog::accept); + QObject::connect(ui.b_cancel, &QPushButton::clicked, this, &QDialog::reject); + + QObject::connect(ui.b_refresh, &QPushButton::clicked, this, &PluginListDialog::refreshPlugins); + QObject::connect(ui.b_clear_filters, &QPushButton::clicked, this, &PluginListDialog::clearFilters); + QObject::connect(ui.lineEdit, &QLineEdit::textChanged, this, &PluginListDialog::checkFilters); + QObject::connect(ui.tableWidget, &QTableWidget::currentCellChanged, this, &PluginListDialog::checkPlugin); + QObject::connect(ui.tableWidget, &QTableWidget::cellClicked, this, &PluginListDialog::cellClicked); + QObject::connect(ui.tableWidget, &QTableWidget::cellDoubleClicked, this, &PluginListDialog::cellDoubleClicked); + + QObject::connect(ui.ch_internal, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_ladspa, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_dssi, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_lv2, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_vst, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_vst3, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_clap, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_au, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_jsfx, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_kits, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_effects, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_instruments, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_midi, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_other, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_native, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_bridged, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_bridged_wine, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_favorites, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_rtsafe, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_cv, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_gui, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_inline_display, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_stereo, &QCheckBox::clicked, this, &PluginListDialog::checkFilters); + QObject::connect(ui.ch_cat_all, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategoryAll); + QObject::connect(ui.ch_cat_delay, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific); + QObject::connect(ui.ch_cat_distortion, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific); + QObject::connect(ui.ch_cat_dynamics, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific); + QObject::connect(ui.ch_cat_eq, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific); + QObject::connect(ui.ch_cat_filter, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific); + QObject::connect(ui.ch_cat_modulator, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific); + QObject::connect(ui.ch_cat_synth, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific); + QObject::connect(ui.ch_cat_utility, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific); + QObject::connect(ui.ch_cat_other, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific); + + QObject::connect(ui.act_focus_search, &QAction::triggered, this, &PluginListDialog::focusSearchFieldAndSelectAll); +} + +PluginListDialog::~PluginListDialog() +{ + if (p->timerId != 0) + killTimer(p->timerId); + + delete p; +} + +// -------------------------------------------------------------------------------------------------------------------- +// public methods + +const PluginInfo& PluginListDialog::getSelectedPluginInfo() const +{ + return p->retPlugin; +} + +void PluginListDialog::addPluginInfo(const CarlaPluginDiscoveryInfo* const info, const char* const sha1sum) +{ + if (info == nullptr) + { + if (sha1sum != nullptr) + { + QSafeSettings settings("falkTX", "CarlaDatabase3"); + settings.setValue(QString("PluginCache/%1").arg(sha1sum), QByteArray()); + + const QString qsha1sum(sha1sum); + p->plugins.cache[qsha1sum] = {}; + } + return; + } + + PluginInfo pinfo = {}; + pinfo.build = static_cast(info->btype); + pinfo.type = static_cast(info->ptype); + pinfo.hints = info->metadata.hints; + pinfo.uniqueId = info->uniqueId; + pinfo.audioIns = static_cast(info->io.audioIns); + pinfo.audioOuts = static_cast(info->io.audioOuts); + pinfo.cvIns = static_cast(info->io.cvIns); + pinfo.cvOuts = static_cast(info->io.cvOuts); + pinfo.midiIns = static_cast(info->io.midiIns); + pinfo.midiOuts = static_cast(info->io.midiOuts); + pinfo.parameterIns = static_cast(info->io.parameterIns); + pinfo.parameterOuts = static_cast(info->io.parameterOuts); + pinfo.category = getPluginCategoryAsString(info->metadata.category); + pinfo.filename = QString::fromUtf8(info->filename); + pinfo.name = QString::fromUtf8(info->metadata.name); + pinfo.label = QString::fromUtf8(info->label); + pinfo.maker = QString::fromUtf8(info->metadata.maker); + + if (sha1sum != nullptr) + { + QSafeSettings settings("falkTX", "CarlaDatabase3"); + const QString qsha1sum(sha1sum); + const QString key = QString("PluginCache/%1").arg(sha1sum); + + // single sha1sum can contain >1 plugin + QByteArray qdata; + if (p->plugins.cache.contains(qsha1sum)) + qdata = settings.valueByteArray(key); + qdata += asVariant(pinfo).toByteArray(); + + settings.setValue(key, qdata); + + p->plugins.cache[qsha1sum].append(pinfo); + } + + #ifdef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + if ((pinfo.hints & PLUGIN_HAS_CUSTOM_EMBED_UI) == 0x0) + return; + #endif + + p->plugins.add(pinfo); +} + +bool PluginListDialog::checkPluginCache(const char* const filename, const char* const sha1sum) +{ + // sha1sum is always valid for this call + const QString qsha1sum(sha1sum); + + if (filename != nullptr) + p->discovery.dialog->progressBar->setFormat(filename); + + if (!p->plugins.cache.contains(qsha1sum)) + return false; + + const QList& plist(p->plugins.cache[qsha1sum]); + + if (plist.isEmpty()) + return p->discovery.ignoreCache || !p->discovery.checkInvalid; + + // if filename does not match, abort (hash collision?) + if (filename == nullptr || plist.first().filename != filename) + { + p->plugins.cache.remove(qsha1sum); + return false; + } + + for (const PluginInfo& info : plist) + { + #ifdef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + if ((info.hints & PLUGIN_HAS_CUSTOM_EMBED_UI) == 0x0) + continue; + #endif + p->plugins.add(info); + } + + return true; +} + +void PluginListDialog::setPluginPath(const PluginType ptype, const char* const path) +{ + switch (ptype) + { + case PLUGIN_LV2: + p->paths.lv2 = path; + break; + case PLUGIN_VST2: + p->paths.vst2 = path; + break; + case PLUGIN_VST3: + p->paths.vst3 = path; + break; + case PLUGIN_CLAP: + p->paths.clap = path; + break; + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + case PLUGIN_LADSPA: + p->paths.ladspa = path; + break; + case PLUGIN_DSSI: + p->paths.dssi = path; + break; + case PLUGIN_SF2: + p->paths.sf2 = path; + break; + case PLUGIN_SFZ: + p->paths.sfz = path; + break; + case PLUGIN_JSFX: + p->paths.jsfx = path; + break; + #endif + default: + break; + } +} + +// -------------------------------------------------------------------------------------------------------------------- +// protected methods + +void PluginListDialog::done(const int r) +{ + if (r == QDialog::Accepted && ui.tableWidget->currentRow() >= 0) + { + QTableWidgetItem* const widget = ui.tableWidget->item(ui.tableWidget->currentRow(), TW_NAME); + p->retPlugin = asPluginInfo(widget->data(Qt::UserRole + UR_PLUGIN_INFO)); + } + else + { + p->retPlugin = {}; + } + + QDialog::done(r); +} + +void PluginListDialog::showEvent(QShowEvent* const event) +{ + focusSearchFieldAndSelectAll(); + QDialog::showEvent(event); + + // Set up initial discovery + if (p->discovery.firstInit) + { + p->discovery.firstInit = false; + + p->discovery.dialog = new PluginRefreshDialog(this); + p->discovery.dialog->b_start->setEnabled(false); + p->discovery.dialog->b_skip->setEnabled(true); + p->discovery.dialog->ch_updated->setChecked(true); + p->discovery.dialog->ch_invalid->setChecked(false); + p->discovery.dialog->group->setEnabled(false); + p->discovery.dialog->progressBar->setFormat("Starting initial discovery..."); + + QObject::connect(p->discovery.dialog->b_skip, &QPushButton::clicked, + this, &PluginListDialog::refreshPluginsSkip); + QObject::connect(p->discovery.dialog, &QDialog::finished, + this, &PluginListDialog::refreshPluginsStop); + + p->timerId = startTimer(0); + + QTimer::singleShot(0, p->discovery.dialog, &QDialog::exec); + } +} + +void PluginListDialog::timerEvent(QTimerEvent* const event) +{ + if (event->timerId() == p->timerId) + { + do { + // discovery in progress, keep it going + if (p->discovery.handle != nullptr) + { + if (!carla_plugin_discovery_idle(p->discovery.handle)) + { + carla_plugin_discovery_stop(p->discovery.handle); + p->discovery.handle = nullptr; + } + break; + } + + // start next discovery + QCarlaString path; + switch (p->discovery.ptype) + { + case PLUGIN_NONE: + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + if (p->discovery.btype == BINARY_NATIVE) + { + ui.label->setText(tr("Discovering internal plugins...")); + p->discovery.ptype = PLUGIN_INTERNAL; + break; + } + [[fallthrough]]; + case PLUGIN_INTERNAL: + ui.label->setText(tr("Discovering LADSPA plugins...")); + path = p->paths.ladspa; + p->discovery.ptype = PLUGIN_LADSPA; + break; + case PLUGIN_LADSPA: + ui.label->setText(tr("Discovering DSSI plugins...")); + path = p->paths.dssi; + p->discovery.ptype = PLUGIN_DSSI; + break; + case PLUGIN_DSSI: + #endif + ui.label->setText(tr("Discovering LV2 plugins...")); + path = p->paths.lv2; + p->discovery.ptype = PLUGIN_LV2; + break; + case PLUGIN_LV2: + ui.label->setText(tr("Discovering VST2 plugins...")); + path = p->paths.vst2; + p->discovery.ptype = PLUGIN_VST2; + break; + case PLUGIN_VST2: + ui.label->setText(tr("Discovering VST3 plugins...")); + path = p->paths.vst3; + p->discovery.ptype = PLUGIN_VST3; + break; + case PLUGIN_VST3: + ui.label->setText(tr("Discovering CLAP plugins...")); + path = p->paths.clap; + p->discovery.ptype = PLUGIN_CLAP; + break; + case PLUGIN_CLAP: + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + #ifdef CARLA_OS_MAC + if (p->discovery.btype == BINARY_POSIX32 || p->discovery.btype == BINARY_POSIX64) + { + ui.label->setText(tr("Discovering AU plugins...")); + p->discovery.ptype = PLUGIN_AU; + break; + } + [[fallthrough]]; + case PLUGIN_AU: + #endif + if (p->discovery.btype == BINARY_NATIVE && p->paths.jsfx.isNotEmpty()) + { + ui.label->setText(tr("Discovering JSFX plugins...")); + path = p->paths.jsfx; + p->discovery.ptype = PLUGIN_JSFX; + break; + } + [[fallthrough]]; + case PLUGIN_JSFX: + if (p->discovery.btype == BINARY_NATIVE && p->paths.sf2.isNotEmpty()) + { + ui.label->setText(tr("Discovering SF2 kits...")); + path = p->paths.sf2; + p->discovery.ptype = PLUGIN_SF2; + break; + } + [[fallthrough]]; + case PLUGIN_SF2: + if (p->discovery.btype == BINARY_NATIVE && p->paths.sfz.isNotEmpty()) + { + ui.label->setText(tr("Discovering SFZ kits...")); + path = p->paths.sfz; + p->discovery.ptype = PLUGIN_SFZ; + break; + } + [[fallthrough]]; + case PLUGIN_SFZ: + #endif + default: + // discovery complete + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + if (! p->discovery.nextTool()) + #endif + refreshPluginsStop(); + } + + if (p->timerId == 0) + break; + + if (p->discovery.dialog) + p->discovery.dialog->progressBar->setFormat(ui.label->text()); + + p->discovery.handle = carla_plugin_discovery_start(p->discovery.tool.toUtf8().constData(), + p->discovery.btype, + p->discovery.ptype, + path.toUtf8().constData(), + discoveryCallback, checkCacheCallback, this); + } while (false); + } + + QDialog::timerEvent(event); +} + +// -------------------------------------------------------------------------------------------------------------------- +// private methods + +void PluginListDialog::addPluginsToTable() +{ + // ---------------------------------------------------------------------------------------------------------------- + // sum plugins first, creating all needed rows in advance + + ui.tableWidget->setSortingEnabled(false); + ui.tableWidget->clearContents(); + + #ifdef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + ui.tableWidget->setRowCount( + int(p->plugins.lv2.size() + p->plugins.vst2.size() + p->plugins.vst3.size() + p->plugins.clap.size())); + + constexpr const char* const txt = "Have %1 LV2, %2 VST2, %3 VST3 and %4 CLAP plugins"; + + ui.label->setText(tr(txt) + .arg(QString::number(p->plugins.lv2.size())) + .arg(QString::number(p->plugins.vst2.size())) + .arg(QString::number(p->plugins.vst3.size())) + .arg(QString::number(p->plugins.clap.size()))); + #else + ui.tableWidget->setRowCount( + int(p->plugins.internal.size() + p->plugins.ladspa.size() + p->plugins.dssi.size() + + p->plugins.lv2.size() + p->plugins.vst2.size() + p->plugins.vst3.size() + p->plugins.clap.size() + + #ifdef CARLA_OS_MAC + p->plugins.au.size() + + #endif + p->plugins.jsfx.size() + p->plugins.kits.size())); + + constexpr const char* const txt = "Have %1 Internal, %2 LADSPA, %3 DSSI, %4 LV2, %5 VST2, %6 VST3, %7 CLAP" + #ifdef CARLA_OS_MAC + ", %8 AudioUnit and %9 JSFX plugins, plus %10 Sound Kits" + #endif + " and %8 JSFX plugins, plus %9 Sound Kits"; + + ui.label->setText(tr(txt) + .arg(QString::number(p->plugins.internal.size())) + .arg(QString::number(p->plugins.ladspa.size())) + .arg(QString::number(p->plugins.dssi.size())) + .arg(QString::number(p->plugins.lv2.size())) + .arg(QString::number(p->plugins.vst2.size())) + .arg(QString::number(p->plugins.vst3.size())) + .arg(QString::number(p->plugins.clap.size())) + #ifdef CARLA_OS_MAC + .arg(QString::number(p->plugins.au.size())) + #endif + .arg(QString::number(p->plugins.jsfx.size())) + .arg(QString::number(p->plugins.kits.size()))); + #endif + + // ---------------------------------------------------------------------------------------------------------------- + // now add all plugins to the table + + auto addPluginToTable = [=](const PluginInfo& info) { + const int index = p->lastTableWidgetIndex++; + const bool isFav = p->plugins.favorites.contains(asPluginFavorite(info)); + + QTableWidgetItem* const itemFav = new QTableWidgetItem; + itemFav->setCheckState(isFav ? Qt::Checked : Qt::Unchecked); + itemFav->setText(isFav ? " " : " "); + + const QString pluginText = (info.name + info.label + info.maker + info.filename).toLower(); + ui.tableWidget->setItem(index, TW_FAVORITE, itemFav); + ui.tableWidget->setItem(index, TW_NAME, new QTableWidgetItem(info.name)); + ui.tableWidget->setItem(index, TW_LABEL, new QTableWidgetItem(info.label)); + ui.tableWidget->setItem(index, TW_MAKER, new QTableWidgetItem(info.maker)); + ui.tableWidget->setItem(index, TW_BINARY, new QTableWidgetItem(QFileInfo(info.filename).fileName())); + + QTableWidgetItem *const itemName = ui.tableWidget->item(index, TW_NAME); + itemName->setData(Qt::UserRole + UR_PLUGIN_INFO, asVariant(info)); + itemName->setData(Qt::UserRole + UR_SEARCH_TEXT, pluginText); + }; + + p->lastTableWidgetIndex = 0; + + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + for (const PluginInfo &plugin : p->plugins.internal) + addPluginToTable(plugin); + + for (const PluginInfo &plugin : p->plugins.ladspa) + addPluginToTable(plugin); + + for (const PluginInfo &plugin : p->plugins.dssi) + addPluginToTable(plugin); + #endif + + for (const PluginInfo &plugin : p->plugins.lv2) + addPluginToTable(plugin); + + for (const PluginInfo &plugin : p->plugins.vst2) + addPluginToTable(plugin); + + for (const PluginInfo &plugin : p->plugins.vst3) + addPluginToTable(plugin); + + for (const PluginInfo& plugin : p->plugins.clap) + addPluginToTable(plugin); + + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + #ifdef CARLA_OS_MAC + for (const PluginInfo& plugin : p->plugins.au) + addPluginToTable(plugin); + #endif + + for (const PluginInfo& plugin : p->plugins.jsfx) + addPluginToTable(plugin); + + for (const PluginInfo& plugin : p->plugins.kits) + addPluginToTable(plugin); + #endif + + CARLA_SAFE_ASSERT_INT2(p->lastTableWidgetIndex == ui.tableWidget->rowCount(), + p->lastTableWidgetIndex, ui.tableWidget->rowCount()); + + // ---------------------------------------------------------------------------------------------------------------- + // and reenable sorting + filtering + + ui.tableWidget->setSortingEnabled(true); + + checkFilters(); + checkPlugin(ui.tableWidget->currentRow()); +} + +void PluginListDialog::loadSettings() +{ + const QSafeSettings settings("falkTX", "CarlaDatabase3"); + + restoreGeometry(settings.valueByteArray("PluginDatabase/Geometry")); + ui.ch_effects->setChecked(settings.valueBool("PluginDatabase/ShowEffects", true)); + ui.ch_instruments->setChecked(settings.valueBool("PluginDatabase/ShowInstruments", true)); + ui.ch_midi->setChecked(settings.valueBool("PluginDatabase/ShowMIDI", true)); + ui.ch_other->setChecked(settings.valueBool("PluginDatabase/ShowOther", true)); + ui.ch_internal->setChecked(settings.valueBool("PluginDatabase/ShowInternal", true)); + ui.ch_ladspa->setChecked(settings.valueBool("PluginDatabase/ShowLADSPA", true)); + ui.ch_dssi->setChecked(settings.valueBool("PluginDatabase/ShowDSSI", true)); + ui.ch_lv2->setChecked(settings.valueBool("PluginDatabase/ShowLV2", true)); + ui.ch_vst->setChecked(settings.valueBool("PluginDatabase/ShowVST2", true)); + ui.ch_vst3->setChecked(settings.valueBool("PluginDatabase/ShowVST3", true)); + ui.ch_clap->setChecked(settings.valueBool("PluginDatabase/ShowCLAP", true)); + #ifdef CARLA_OS_MAC + ui.ch_au->setChecked(settings.valueBool("PluginDatabase/ShowAU", true)); + #endif + ui.ch_jsfx->setChecked(settings.valueBool("PluginDatabase/ShowJSFX", true)); + ui.ch_kits->setChecked(settings.valueBool("PluginDatabase/ShowKits", true)); + ui.ch_native->setChecked(settings.valueBool("PluginDatabase/ShowNative", true)); + ui.ch_bridged->setChecked(settings.valueBool("PluginDatabase/ShowBridged", true)); + ui.ch_bridged_wine->setChecked(settings.valueBool("PluginDatabase/ShowBridgedWine", true)); + ui.ch_favorites->setChecked(settings.valueBool("PluginDatabase/ShowFavorites", false)); + ui.ch_rtsafe->setChecked(settings.valueBool("PluginDatabase/ShowRtSafe", false)); + ui.ch_cv->setChecked(settings.valueBool("PluginDatabase/ShowHasCV", false)); + ui.ch_gui->setChecked(settings.valueBool("PluginDatabase/ShowHasGUI", false)); + ui.ch_inline_display->setChecked(settings.valueBool("PluginDatabase/ShowHasInlineDisplay", false)); + ui.ch_stereo->setChecked(settings.valueBool("PluginDatabase/ShowStereoOnly", false)); + ui.lineEdit->setText(settings.valueString("PluginDatabase/SearchText", "")); + + const QString categories = settings.valueString("PluginDatabase/ShowCategory", "all"); + if (categories == "all" or categories.length() < 2) + { + ui.ch_cat_all->setChecked(true); + ui.ch_cat_delay->setChecked(false); + ui.ch_cat_distortion->setChecked(false); + ui.ch_cat_dynamics->setChecked(false); + ui.ch_cat_eq->setChecked(false); + ui.ch_cat_filter->setChecked(false); + ui.ch_cat_modulator->setChecked(false); + ui.ch_cat_synth->setChecked(false); + ui.ch_cat_utility->setChecked(false); + ui.ch_cat_other->setChecked(false); + } + else + { + ui.ch_cat_all->setChecked(false); + ui.ch_cat_delay->setChecked(categories.contains(":delay:")); + ui.ch_cat_distortion->setChecked(categories.contains(":distortion:")); + ui.ch_cat_dynamics->setChecked(categories.contains(":dynamics:")); + ui.ch_cat_eq->setChecked(categories.contains(":eq:")); + ui.ch_cat_filter->setChecked(categories.contains(":filter:")); + ui.ch_cat_modulator->setChecked(categories.contains(":modulator:")); + ui.ch_cat_synth->setChecked(categories.contains(":synth:")); + ui.ch_cat_utility->setChecked(categories.contains(":utility:")); + ui.ch_cat_other->setChecked(categories.contains(":other:")); + } + + const QByteArray tableGeometry = settings.valueByteArray("PluginDatabase/TableGeometry"); + QHeaderView* const horizontalHeader = ui.tableWidget->horizontalHeader(); + if (! tableGeometry.isNull()) + { + horizontalHeader->restoreState(tableGeometry); + } + else + { + ui.tableWidget->setColumnWidth(TW_NAME, 250); + ui.tableWidget->setColumnWidth(TW_LABEL, 200); + ui.tableWidget->setColumnWidth(TW_MAKER, 150); + ui.tableWidget->sortByColumn(TW_NAME, Qt::AscendingOrder); + } + + horizontalHeader->setSectionResizeMode(TW_FAVORITE, QHeaderView::Fixed); + ui.tableWidget->setColumnWidth(TW_FAVORITE, 24); + ui.tableWidget->setSortingEnabled(true); + + p->plugins.favorites = asPluginFavoriteList(settings.valueByteArray("PluginListDialog/Favorites")); + + // load entire plugin cache + const QStringList keys = settings.allKeys(); + for (const QCarlaString key : keys) + { + if (!key.startsWith("PluginCache/")) + continue; + + const QByteArray data(settings.valueByteArray(key)); + + if (data.isEmpty()) + p->plugins.cache.insert(key.sliced(12), {}); + else + p->plugins.cache.insert(key.sliced(12), asPluginInfoList(data)); + } + + #ifdef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + // these are not visible, force their value + ui.ch_native->setChecked(true); + ui.ch_bridged->setChecked(false); + ui.ch_bridged_wine->setChecked(false); + ui.ch_inline_display->setChecked(false); + #endif +} + +// -------------------------------------------------------------------------------------------------------------------- +// private slots + +void PluginListDialog::cellClicked(const int row, const int column) +{ + if (column != TW_FAVORITE) + return; + + const PluginInfo info = asPluginInfo(ui.tableWidget->item(row, TW_NAME)->data(Qt::UserRole + UR_PLUGIN_INFO)); + const PluginFavorite fav = asPluginFavorite(info); + const bool isFavorite = p->plugins.favorites.contains(fav); + + if (ui.tableWidget->item(row, TW_FAVORITE)->checkState() == Qt::Checked) + { + if (!isFavorite) + p->plugins.favorites.append(fav); + } + else if (isFavorite) + { + p->plugins.favorites.removeAll(fav); + } + + QSafeSettings settings("falkTX", "CarlaDatabase3"); + settings.setValue("PluginListDialog/Favorites", asVariant(p->plugins.favorites)); +} + +void PluginListDialog::cellDoubleClicked(int, const int column) +{ + if (column != TW_FAVORITE) + done(QDialog::Accepted); +} + +void PluginListDialog::focusSearchFieldAndSelectAll() +{ + ui.lineEdit->setFocus(); + ui.lineEdit->selectAll(); +} + +void PluginListDialog::checkFilters() +{ + const QCarlaString text = ui.lineEdit->text().toLower(); + + const bool hideEffects = !ui.ch_effects->isChecked(); + const bool hideInstruments = !ui.ch_instruments->isChecked(); + const bool hideMidi = !ui.ch_midi->isChecked(); + const bool hideOther = !ui.ch_other->isChecked(); + + const bool hideInternal = !ui.ch_internal->isChecked(); + const bool hideLadspa = !ui.ch_ladspa->isChecked(); + const bool hideDSSI = !ui.ch_dssi->isChecked(); + const bool hideLV2 = !ui.ch_lv2->isChecked(); + const bool hideVST2 = !ui.ch_vst->isChecked(); + const bool hideVST3 = !ui.ch_vst3->isChecked(); + const bool hideCLAP = !ui.ch_clap->isChecked(); + const bool hideAU = !ui.ch_au->isChecked(); + const bool hideJSFX = !ui.ch_jsfx->isChecked(); + const bool hideKits = !ui.ch_kits->isChecked(); + + const bool hideNative = !ui.ch_native->isChecked(); + const bool hideBridged = !ui.ch_bridged->isChecked(); + const bool hideBridgedWine = !ui.ch_bridged_wine->isChecked(); + + const bool hideNonFavs = ui.ch_favorites->isChecked(); + const bool hideNonRtSafe = ui.ch_rtsafe->isChecked(); + const bool hideNonCV = ui.ch_cv->isChecked(); + const bool hideNonGui = ui.ch_gui->isChecked(); + const bool hideNonIDisp = ui.ch_inline_display->isChecked(); + const bool hideNonStereo = ui.ch_stereo->isChecked(); + + #if defined(CARLA_OS_WIN64) + static constexpr const BinaryType nativeBins[2] = { BINARY_WIN32, BINARY_WIN64 }; + static constexpr const BinaryType wineBins[2] = { BINARY_NONE, BINARY_NONE }; + #elif defined(CARLA_OS_WIN32) + static constexpr const BinaryType nativeBins[2] = { BINARY_WIN32, BINARY_NONE }; + static constexpr const BinaryType wineBins[2] = { BINARY_NONE, BINARY_NONE }; + #elif defined(CARLA_OS_MAC) + static constexpr const BinaryType nativeBins[2] = { BINARY_POSIX64, BINARY_NONE }; + static constexpr const BinaryType wineBins[2] = { BINARY_WIN32, BINARY_WIN64 }; + #else + static constexpr const BinaryType nativeBins[2] = { BINARY_POSIX32, BINARY_POSIX64 }; + static constexpr const BinaryType wineBins[2] = { BINARY_WIN32, BINARY_WIN64 }; + #endif + + for (int i=0, c=ui.tableWidget->rowCount(); iitem(i, TW_NAME)->data(Qt::UserRole + UR_PLUGIN_INFO)); + const QString ptext = ui.tableWidget->item(i, TW_NAME)->data(Qt::UserRole + UR_SEARCH_TEXT).toString(); + + const uint16_t aIns = info.audioIns; + const uint16_t aOuts = info.audioOuts; + const uint16_t cvIns = info.cvIns; + const uint16_t cvOuts = info.cvOuts; + const uint16_t mIns = info.midiIns; + const uint16_t mOuts = info.midiOuts; + const uint32_t phints = info.hints; + const uint16_t ptype = info.type; + const QString categ = info.category; + const bool isSynth = phints & PLUGIN_IS_SYNTH; + const bool isEffect = aIns > 0 && aOuts > 0 && !isSynth; + const bool isMidi = aIns == 0 && aOuts == 0 && mIns > 0 && mOuts > 0; + const bool isKit = ptype == PLUGIN_SF2 || ptype == PLUGIN_SFZ; + const bool isOther = !(isEffect || isSynth || isMidi || isKit); + const bool isNative = info.build == BINARY_NATIVE; + const bool isRtSafe = phints & PLUGIN_IS_RTSAFE; + const bool isStereo = (aIns == 2 && aOuts == 2) || (isSynth && aOuts == 2); + const bool hasCV = cvIns + cvOuts > 0; + const bool hasGui = phints & PLUGIN_HAS_CUSTOM_UI; + const bool hasIDisp = phints & PLUGIN_HAS_INLINE_DISPLAY; + const bool isBridged = !isNative && (nativeBins[0] == info.build || nativeBins[1] == info.build); + const bool isBridgedWine = !isNative && (wineBins[0] == info.build || wineBins[1] == info.build); + + const auto hasText = [text, ptext]() { + const QStringList textSplit = text.strip().split(' '); + for (const QString& t : textSplit) + if (ptext.contains(t)) + return true; + return false; + }; + + /**/ if (hideEffects && isEffect) + ui.tableWidget->hideRow(i); + else if (hideInstruments && isSynth) + ui.tableWidget->hideRow(i); + else if (hideMidi && isMidi) + ui.tableWidget->hideRow(i); + else if (hideOther && isOther) + ui.tableWidget->hideRow(i); + else if (hideKits && isKit) + ui.tableWidget->hideRow(i); + else if (hideInternal && ptype == PLUGIN_INTERNAL) + ui.tableWidget->hideRow(i); + else if (hideLadspa && ptype == PLUGIN_LADSPA) + ui.tableWidget->hideRow(i); + else if (hideDSSI && ptype == PLUGIN_DSSI) + ui.tableWidget->hideRow(i); + else if (hideLV2 && ptype == PLUGIN_LV2) + ui.tableWidget->hideRow(i); + else if (hideVST2 && ptype == PLUGIN_VST2) + ui.tableWidget->hideRow(i); + else if (hideVST3 && ptype == PLUGIN_VST3) + ui.tableWidget->hideRow(i); + else if (hideCLAP && ptype == PLUGIN_CLAP) + ui.tableWidget->hideRow(i); + else if (hideAU && ptype == PLUGIN_AU) + ui.tableWidget->hideRow(i); + else if (hideJSFX && ptype == PLUGIN_JSFX) + ui.tableWidget->hideRow(i); + else if (hideNative && isNative) + ui.tableWidget->hideRow(i); + else if (hideBridged && isBridged) + ui.tableWidget->hideRow(i); + else if (hideBridgedWine && isBridgedWine) + ui.tableWidget->hideRow(i); + else if (hideNonRtSafe && not isRtSafe) + ui.tableWidget->hideRow(i); + else if (hideNonCV && not hasCV) + ui.tableWidget->hideRow(i); + else if (hideNonGui && not hasGui) + ui.tableWidget->hideRow(i); + else if (hideNonIDisp && not hasIDisp) + ui.tableWidget->hideRow(i); + else if (hideNonStereo && not isStereo) + ui.tableWidget->hideRow(i); + else if (text.isNotEmpty() && ! hasText()) + ui.tableWidget->hideRow(i); + else if (hideNonFavs && !p->plugins.favorites.contains(asPluginFavorite(info))) + ui.tableWidget->hideRow(i); + else if (ui.ch_cat_all->isChecked() or + (ui.ch_cat_delay->isChecked() && categ == "delay") or + (ui.ch_cat_distortion->isChecked() && categ == "distortion") or + (ui.ch_cat_dynamics->isChecked() && categ == "dynamics") or + (ui.ch_cat_eq->isChecked() && categ == "eq") or + (ui.ch_cat_filter->isChecked() && categ == "filter") or + (ui.ch_cat_modulator->isChecked() && categ == "modulator") or + (ui.ch_cat_synth->isChecked() && categ == "synth") or + (ui.ch_cat_utility->isChecked() && categ == "utility") or + (ui.ch_cat_other->isChecked() && categ == "other")) + ui.tableWidget->showRow(i); + else + ui.tableWidget->hideRow(i); + } +} + +void PluginListDialog::checkFiltersCategoryAll(const bool clicked) +{ + const bool notClicked = !clicked; + ui.ch_cat_delay->setChecked(notClicked); + ui.ch_cat_distortion->setChecked(notClicked); + ui.ch_cat_dynamics->setChecked(notClicked); + ui.ch_cat_eq->setChecked(notClicked); + ui.ch_cat_filter->setChecked(notClicked); + ui.ch_cat_modulator->setChecked(notClicked); + ui.ch_cat_synth->setChecked(notClicked); + ui.ch_cat_utility->setChecked(notClicked); + ui.ch_cat_other->setChecked(notClicked); + checkFilters(); +} + +void PluginListDialog::checkFiltersCategorySpecific(bool clicked) +{ + if (clicked) + { + ui.ch_cat_all->setChecked(false); + } + else if (! (ui.ch_cat_delay->isChecked() || + ui.ch_cat_distortion->isChecked() || + ui.ch_cat_dynamics->isChecked() || + ui.ch_cat_eq->isChecked() || + ui.ch_cat_filter->isChecked() || + ui.ch_cat_modulator->isChecked() || + ui.ch_cat_synth->isChecked() || + ui.ch_cat_utility->isChecked() || + ui.ch_cat_other->isChecked())) + { + ui.ch_cat_all->setChecked(true); + } + checkFilters(); +} + +void PluginListDialog::clearFilters() +{ + auto setCheckedWithoutSignaling = [](QCheckBox* const w, const bool checked) + { + w->blockSignals(true); + w->setChecked(checked); + w->blockSignals(false); + }; + + setCheckedWithoutSignaling(ui.ch_internal, true); + setCheckedWithoutSignaling(ui.ch_ladspa, true); + setCheckedWithoutSignaling(ui.ch_dssi, true); + setCheckedWithoutSignaling(ui.ch_lv2, true); + setCheckedWithoutSignaling(ui.ch_vst, true); + setCheckedWithoutSignaling(ui.ch_vst3, true); + setCheckedWithoutSignaling(ui.ch_clap, true); + setCheckedWithoutSignaling(ui.ch_jsfx, true); + setCheckedWithoutSignaling(ui.ch_kits, true); + + setCheckedWithoutSignaling(ui.ch_instruments, true); + setCheckedWithoutSignaling(ui.ch_effects, true); + setCheckedWithoutSignaling(ui.ch_midi, true); + setCheckedWithoutSignaling(ui.ch_other, true); + + setCheckedWithoutSignaling(ui.ch_native, true); + setCheckedWithoutSignaling(ui.ch_bridged, false); + setCheckedWithoutSignaling(ui.ch_bridged_wine, false); + + setCheckedWithoutSignaling(ui.ch_favorites, false); + setCheckedWithoutSignaling(ui.ch_rtsafe, false); + setCheckedWithoutSignaling(ui.ch_stereo, false); + setCheckedWithoutSignaling(ui.ch_cv, false); + setCheckedWithoutSignaling(ui.ch_gui, false); + setCheckedWithoutSignaling(ui.ch_inline_display, false); + + if (ui.ch_au->isEnabled()) + setCheckedWithoutSignaling(ui.ch_au, true); + + setCheckedWithoutSignaling(ui.ch_cat_all, true); + setCheckedWithoutSignaling(ui.ch_cat_delay, false); + setCheckedWithoutSignaling(ui.ch_cat_distortion, false); + setCheckedWithoutSignaling(ui.ch_cat_dynamics, false); + setCheckedWithoutSignaling(ui.ch_cat_eq, false); + setCheckedWithoutSignaling(ui.ch_cat_filter, false); + setCheckedWithoutSignaling(ui.ch_cat_modulator, false); + setCheckedWithoutSignaling(ui.ch_cat_synth, false); + setCheckedWithoutSignaling(ui.ch_cat_utility, false); + setCheckedWithoutSignaling(ui.ch_cat_other, false); + + ui.lineEdit->blockSignals(true); + ui.lineEdit->clear(); + ui.lineEdit->blockSignals(false); + + checkFilters(); +} + +// -------------------------------------------------------------------------------------------------------------------- + +void PluginListDialog::checkPlugin(const int row) +{ + if (row >= 0) + { + ui.b_add->setEnabled(true); + + const PluginInfo info = asPluginInfo(ui.tableWidget->item(row, TW_NAME)->data(Qt::UserRole + UR_PLUGIN_INFO)); + + const bool isSynth = info.hints & PLUGIN_IS_SYNTH; + const bool isEffect = info.audioIns > 0 && info.audioOuts > 0 && !isSynth; + const bool isMidi = info.audioIns == 0 && info.audioOuts == 0 && info.midiIns > 0 && info.midiOuts > 0; + + QString ptype; + /**/ if (isSynth) + ptype = "Instrument"; + else if (isEffect) + ptype = "Effect"; + else if (isMidi) + ptype = "MIDI Plugin"; + else + ptype = "Other"; + + QString parch; + /**/ if (info.build == BINARY_NATIVE) + parch = tr("Native"); + else if (info.build == BINARY_POSIX32) + parch = "posix32"; + else if (info.build == BINARY_POSIX64) + parch = "posix64"; + else if (info.build == BINARY_WIN32) + parch = "win32"; + else if (info.build == BINARY_WIN64) + parch = "win64"; + else if (info.build == BINARY_OTHER) + parch = tr("Other"); + else if (info.build == BINARY_WIN32) + parch = tr("Unknown"); + + ui.l_format->setText(getPluginTypeAsString(static_cast(info.type))); + + ui.l_type->setText(ptype); + ui.l_arch->setText(parch); + ui.l_id->setText(QString::number(info.uniqueId)); + ui.l_ains->setText(QString::number(info.audioIns)); + ui.l_aouts->setText(QString::number(info.audioOuts)); + ui.l_cvins->setText(QString::number(info.cvIns)); + ui.l_cvouts->setText(QString::number(info.cvOuts)); + ui.l_mins->setText(QString::number(info.midiIns)); + ui.l_mouts->setText(QString::number(info.midiOuts)); + ui.l_pins->setText(QString::number(info.parameterIns)); + ui.l_pouts->setText(QString::number(info.parameterOuts)); + ui.l_gui->setText(info.hints & PLUGIN_HAS_CUSTOM_UI ? tr("Yes") : tr("No")); + ui.l_idisp->setText(info.hints & PLUGIN_HAS_INLINE_DISPLAY ? tr("Yes") : tr("No")); + ui.l_bridged->setText(info.hints & PLUGIN_IS_BRIDGE ? tr("Yes") : tr("No")); + ui.l_synth->setText(isSynth ? tr("Yes") : tr("No")); + } + else + { + ui.b_add->setEnabled(false); + ui.l_format->setText("---"); + ui.l_type->setText("---"); + ui.l_arch->setText("---"); + ui.l_id->setText("---"); + ui.l_ains->setText("---"); + ui.l_aouts->setText("---"); + ui.l_cvins->setText("---"); + ui.l_cvouts->setText("---"); + ui.l_mins->setText("---"); + ui.l_mouts->setText("---"); + ui.l_pins->setText("---"); + ui.l_pouts->setText("---"); + ui.l_gui->setText("---"); + ui.l_idisp->setText("---"); + ui.l_bridged->setText("---"); + ui.l_synth->setText("---"); + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +void PluginListDialog::refreshPlugins() +{ + refreshPluginsStop(); + + p->discovery.dialog = new PluginRefreshDialog(this); + + QObject::connect(p->discovery.dialog->b_start, &QPushButton::clicked, + this, &PluginListDialog::refreshPluginsStart); + QObject::connect(p->discovery.dialog->b_skip, &QPushButton::clicked, + this, &PluginListDialog::refreshPluginsSkip); + QObject::connect(p->discovery.dialog, &QDialog::finished, + this, &PluginListDialog::refreshPluginsStop); + + p->discovery.dialog->exec(); +} + +void PluginListDialog::refreshPluginsStart() +{ + // remove old plugins + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + p->plugins.internal.clear(); + p->plugins.ladspa.clear(); + p->plugins.dssi.clear(); + #endif + p->plugins.lv2.clear(); + p->plugins.vst2.clear(); + p->plugins.vst3.clear(); + p->plugins.clap.clear(); + #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS + #ifdef CARLA_OS_MAC + p->plugins.au.clear(); + #endif + p->plugins.jsfx.clear(); + p->plugins.kits.clear(); + #endif + p->discovery.dialog->b_start->setEnabled(false); + p->discovery.dialog->b_skip->setEnabled(true); + p->discovery.ignoreCache = p->discovery.dialog->ch_all->isChecked(); + p->discovery.checkInvalid = + p->discovery.dialog->ch_invalid->isChecked(); + if (p->discovery.ignoreCache) + p->plugins.cache.clear(); + + // start discovery again + p->discovery.restart(); + + if (p->timerId == 0) + p->timerId = startTimer(0); +} + +void PluginListDialog::refreshPluginsStop() +{ + // stop previous discovery if still running + if (p->discovery.handle != nullptr) + { + carla_plugin_discovery_stop(p->discovery.handle); + p->discovery.handle = nullptr; + } + + if (p->discovery.dialog) + { + p->discovery.dialog->close(); + p->discovery.dialog = nullptr; + } + + if (p->timerId != 0) + { + killTimer(p->timerId); + p->timerId = 0; + addPluginsToTable(); + } +} + +void PluginListDialog::refreshPluginsSkip() +{ + if (p->discovery.handle != nullptr) + carla_plugin_discovery_skip(p->discovery.handle); +} + +// -------------------------------------------------------------------------------------------------------------------- + +void PluginListDialog::saveSettings() +{ + QSafeSettings settings("falkTX", "CarlaDatabase3"); + + settings.setValue("PluginDatabase/Geometry", saveGeometry()); + settings.setValue("PluginDatabase/TableGeometry", ui.tableWidget->horizontalHeader()->saveState()); + settings.setValue("PluginDatabase/ShowEffects", ui.ch_effects->isChecked()); + settings.setValue("PluginDatabase/ShowInstruments", ui.ch_instruments->isChecked()); + settings.setValue("PluginDatabase/ShowMIDI", ui.ch_midi->isChecked()); + settings.setValue("PluginDatabase/ShowOther", ui.ch_other->isChecked()); + settings.setValue("PluginDatabase/ShowInternal", ui.ch_internal->isChecked()); + settings.setValue("PluginDatabase/ShowLADSPA", ui.ch_ladspa->isChecked()); + settings.setValue("PluginDatabase/ShowDSSI", ui.ch_dssi->isChecked()); + settings.setValue("PluginDatabase/ShowLV2", ui.ch_lv2->isChecked()); + settings.setValue("PluginDatabase/ShowVST2", ui.ch_vst->isChecked()); + settings.setValue("PluginDatabase/ShowVST3", ui.ch_vst3->isChecked()); + settings.setValue("PluginDatabase/ShowCLAP", ui.ch_clap->isChecked()); + settings.setValue("PluginDatabase/ShowAU", ui.ch_au->isChecked()); + settings.setValue("PluginDatabase/ShowJSFX", ui.ch_jsfx->isChecked()); + settings.setValue("PluginDatabase/ShowKits", ui.ch_kits->isChecked()); + settings.setValue("PluginDatabase/ShowNative", ui.ch_native->isChecked()); + settings.setValue("PluginDatabase/ShowBridged", ui.ch_bridged->isChecked()); + settings.setValue("PluginDatabase/ShowBridgedWine", ui.ch_bridged_wine->isChecked()); + settings.setValue("PluginDatabase/ShowFavorites", ui.ch_favorites->isChecked()); + settings.setValue("PluginDatabase/ShowRtSafe", ui.ch_rtsafe->isChecked()); + settings.setValue("PluginDatabase/ShowHasCV", ui.ch_cv->isChecked()); + settings.setValue("PluginDatabase/ShowHasGUI", ui.ch_gui->isChecked()); + settings.setValue("PluginDatabase/ShowHasInlineDisplay", ui.ch_inline_display->isChecked()); + settings.setValue("PluginDatabase/ShowStereoOnly", ui.ch_stereo->isChecked()); + settings.setValue("PluginDatabase/SearchText", ui.lineEdit->text()); + + if (ui.ch_cat_all->isChecked()) + { + settings.setValue("PluginDatabase/ShowCategory", "all"); + } + else + { + QCarlaString categories; + if (ui.ch_cat_delay->isChecked()) + categories += ":delay"; + if (ui.ch_cat_distortion->isChecked()) + categories += ":distortion"; + if (ui.ch_cat_dynamics->isChecked()) + categories += ":dynamics"; + if (ui.ch_cat_eq->isChecked()) + categories += ":eq"; + if (ui.ch_cat_filter->isChecked()) + categories += ":filter"; + if (ui.ch_cat_modulator->isChecked()) + categories += ":modulator"; + if (ui.ch_cat_synth->isChecked()) + categories += ":synth"; + if (ui.ch_cat_utility->isChecked()) + categories += ":utility"; + if (ui.ch_cat_other->isChecked()) + categories += ":other"; + if (categories.isNotEmpty()) + categories += ":"; + settings.setValue("PluginDatabase/ShowCategory", categories); + } + + settings.setValue("PluginListDialog/Favorites", asVariant(p->plugins.favorites)); +} + +// -------------------------------------------------------------------------------------------------------------------- + +PluginListDialog* +carla_frontend_createPluginListDialog(void* const parent, const HostSettings* const hostSettings) +{ + return new PluginListDialog(reinterpret_cast(parent), hostSettings); +} + +void +carla_frontend_destroyPluginListDialog(PluginListDialog* const dialog) +{ + dialog->close(); + delete dialog; +} + +void +carla_frontend_setPluginListDialogPath(PluginListDialog* const dialog, const int ptype, const char* const path) +{ + dialog->setPluginPath(static_cast(ptype), path); +} + +const PluginListDialogResults* +carla_frontend_execPluginListDialog(PluginListDialog* const dialog) +{ + if (dialog->exec()) + { + static PluginListDialogResults ret; + static CarlaString category; + static CarlaString filename; + static CarlaString name; + static CarlaString label; + static CarlaString maker; + + const PluginInfo& plugin(dialog->getSelectedPluginInfo()); + + category = plugin.category.toUtf8(); + filename = plugin.filename.toUtf8(); + name = plugin.name.toUtf8(); + label = plugin.label.toUtf8(); + maker = plugin.maker.toUtf8(); + + ret.build = plugin.build; + ret.type = plugin.type; + ret.hints = plugin.hints; + ret.category = category; + ret.filename = filename; + ret.name = name; + ret.label = label; + ret.maker = maker; + ret.uniqueId = plugin.uniqueId; + ret.audioIns = plugin.audioIns; + ret.audioOuts = plugin.audioOuts; + ret.cvIns = plugin.cvIns; + ret.cvOuts = plugin.cvOuts; + ret.midiIns = plugin.midiIns; + ret.midiOuts = plugin.midiOuts; + ret.parameterIns = plugin.parameterIns; + ret.parameterOuts = plugin.parameterOuts; + + return &ret; + } + + return nullptr; +} + +// -------------------------------------------------------------------------------------------------------------------- + +// const PluginListDialogResults* +// carla_frontend_createAndExecPluginListDialog(void* const parent, const HostSettings* const hostSettings) +// { +// PluginListDialog gui(reinterpret_cast(parent), hostSettings); +// +// return carla_frontend_execPluginListDialog(&gui); +// } + +// --------------------------------------------------------------------------------------------------------------------