Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2059 lines
75KB

  1. /*
  2. * Carla plugin host
  3. * Copyright (C) 2011-2023 Filipe Coelho <falktx@falktx.com>
  4. * SPDX-License-Identifier: GPL-2.0-or-later
  5. */
  6. #include "pluginlistdialog.hpp"
  7. #include "pluginrefreshdialog.hpp"
  8. #ifdef __clang__
  9. # pragma clang diagnostic push
  10. # pragma clang diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy"
  11. # pragma clang diagnostic ignored "-Wdeprecated-register"
  12. #elif defined(__GNUC__) && __GNUC__ >= 8
  13. # pragma GCC diagnostic push
  14. # pragma GCC diagnostic ignored "-Wclass-memaccess"
  15. # pragma GCC diagnostic ignored "-Wdeprecated-copy"
  16. #endif
  17. #include <QtCore/QDir>
  18. #include <QtCore/QFileInfo>
  19. #include <QtCore/QList>
  20. #include <QtCore/QTimer>
  21. #ifdef __clang__
  22. # pragma clang diagnostic pop
  23. #elif defined(__GNUC__) && __GNUC__ >= 8
  24. # pragma GCC diagnostic pop
  25. #endif
  26. #include "qcarlastring.hpp"
  27. #include "qsafesettings.hpp"
  28. #include "CarlaBackendUtils.hpp"
  29. #include "CarlaJuceUtils.hpp"
  30. #include "CarlaFrontend.h"
  31. #include "CarlaUtils.h"
  32. #include "CarlaString.hpp"
  33. #include <cstdlib>
  34. CARLA_BACKEND_USE_NAMESPACE
  35. // --------------------------------------------------------------------------------------------------------------------
  36. // Carla Settings keys
  37. #define CARLA_KEY_PATHS_LADSPA "Paths/LADSPA"
  38. #define CARLA_KEY_PATHS_DSSI "Paths/DSSI"
  39. #define CARLA_KEY_PATHS_LV2 "Paths/LV2"
  40. #define CARLA_KEY_PATHS_VST2 "Paths/VST2"
  41. #define CARLA_KEY_PATHS_VST3 "Paths/VST3"
  42. #define CARLA_KEY_PATHS_CLAP "Paths/CLAP"
  43. #define CARLA_KEY_PATHS_SF2 "Paths/SF2"
  44. #define CARLA_KEY_PATHS_SFZ "Paths/SFZ"
  45. #define CARLA_KEY_PATHS_JSFX "Paths/JSFX"
  46. // --------------------------------------------------------------------------------------------------------------------
  47. // Carla Settings defaults
  48. // --------------------------------------------------------------------------------------------------------------------
  49. // getenv with a fallback value if unset
  50. static inline
  51. const char* getEnvWithFallback(const char* const env, const char* const fallback)
  52. {
  53. if (const char* const value = std::getenv(env))
  54. return value;
  55. return fallback;
  56. }
  57. // --------------------------------------------------------------------------------------------------------------------
  58. // Plugin paths (from env vars first, then default locations)
  59. struct PluginPaths {
  60. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  61. QCarlaString ladspa;
  62. QCarlaString dssi;
  63. #endif
  64. QCarlaString lv2;
  65. QCarlaString vst2;
  66. QCarlaString vst3;
  67. QCarlaString clap;
  68. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  69. QCarlaString jsfx;
  70. QCarlaString sf2;
  71. QCarlaString sfz;
  72. #endif
  73. PluginPaths()
  74. {
  75. // get common env vars
  76. const QString HOME = QDir::toNativeSeparators(QDir::homePath());
  77. #if defined(CARLA_OS_WIN)
  78. const char *const envAPPDATA = std::getenv("APPDATA");
  79. const char *const envLOCALAPPDATA = getEnvWithFallback("LOCALAPPDATA", envAPPDATA);
  80. const char *const envPROGRAMFILES = std::getenv("PROGRAMFILES");
  81. const char* const envPROGRAMFILESx86 = std::getenv("PROGRAMFILES(x86)");
  82. const char *const envCOMMONPROGRAMFILES = std::getenv("COMMONPROGRAMFILES");
  83. const char* const envCOMMONPROGRAMFILESx86 = std::getenv("COMMONPROGRAMFILES(x86)");
  84. // Small integrity tests
  85. if (envAPPDATA == nullptr)
  86. {
  87. qFatal("APPDATA variable not set, cannot continue");
  88. abort();
  89. }
  90. if (envPROGRAMFILES == nullptr)
  91. {
  92. qFatal("PROGRAMFILES variable not set, cannot continue");
  93. abort();
  94. }
  95. if (envCOMMONPROGRAMFILES == nullptr)
  96. {
  97. qFatal("COMMONPROGRAMFILES variable not set, cannot continue");
  98. abort();
  99. }
  100. const QCarlaString APPDATA(envAPPDATA);
  101. const QCarlaString LOCALAPPDATA(envLOCALAPPDATA);
  102. const QCarlaString PROGRAMFILES(envPROGRAMFILES);
  103. const QCarlaString COMMONPROGRAMFILES(envCOMMONPROGRAMFILES);
  104. #elif !defined(CARLA_OS_MAC)
  105. const QCarlaString CONFIG_HOME(getEnvWithFallback("XDG_CONFIG_HOME", (HOME + "/.config").toUtf8()));
  106. #endif
  107. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  108. // now set paths, listing format path spec if available
  109. if (const char *const envLADSPA = std::getenv("LADSPA_PATH"))
  110. {
  111. ladspa = envLADSPA;
  112. }
  113. else
  114. {
  115. // no official spec, use common paths
  116. #if defined(CARLA_OS_WIN)
  117. ladspa = APPDATA + "\\LADSPA";
  118. ladspa += ";" + PROGRAMFILES + "\\LADSPA";
  119. #elif defined(CARLA_OS_HAIKU)
  120. ladspa = HOME + "/.ladspa";
  121. ladspa += ":/system/add-ons/media/ladspaplugins";
  122. ladspa += ":/system/lib/ladspa";
  123. #elif defined(CARLA_OS_MAC)
  124. ladspa = HOME + "/Library/Audio/Plug-Ins/LADSPA";
  125. ladspa += ":/Library/Audio/Plug-Ins/LADSPA";
  126. #else
  127. ladspa = HOME + "/.ladspa";
  128. ladspa += ":/usr/local/lib/ladspa";
  129. ladspa += ":/usr/lib/ladspa";
  130. #endif
  131. }
  132. if (const char *const envDSSI = std::getenv("DSSI_PATH"))
  133. {
  134. dssi = envDSSI;
  135. }
  136. else
  137. {
  138. // no official spec, use common paths
  139. #if defined(CARLA_OS_WIN)
  140. dssi = APPDATA + "\\DSSI";
  141. dssi += ";" + PROGRAMFILES + "\\DSSI";
  142. #elif defined(CARLA_OS_HAIKU)
  143. dssi = HOME + "/.dssi";
  144. dssi += ":/system/add-ons/media/dssiplugins";
  145. dssi += ":/system/lib/dssi";
  146. #elif defined(CARLA_OS_MAC)
  147. dssi = HOME + "/Library/Audio/Plug-Ins/DSSI";
  148. dssi += ":/Library/Audio/Plug-Ins/DSSI";
  149. #else
  150. dssi = HOME + "/.dssi";
  151. dssi += ":/usr/local/lib/dssi";
  152. dssi += ":/usr/lib/dssi";
  153. #endif
  154. }
  155. #endif // !CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  156. if (const char *const envLV2 = std::getenv("LV2_PATH"))
  157. {
  158. lv2 = envLV2;
  159. }
  160. else
  161. {
  162. // https://lv2plug.in/pages/filesystem-hierarchy-standard.html
  163. #if defined(CARLA_OS_WIN)
  164. lv2 = APPDATA + "\\LV2";
  165. lv2 += ";" + COMMONPROGRAMFILES + "\\LV2";
  166. #elif defined(CARLA_OS_HAIKU)
  167. lv2 = HOME + "/.lv2";
  168. lv2 += ":/system/add-ons/media/lv2plugins";
  169. #elif defined(CARLA_OS_MAC)
  170. lv2 = HOME + "/Library/Audio/Plug-Ins/LV2";
  171. lv2 += ":/Library/Audio/Plug-Ins/LV2";
  172. #else
  173. lv2 = HOME + "/.lv2";
  174. lv2 += ":/usr/local/lib/lv2";
  175. lv2 += ":/usr/lib/lv2";
  176. #endif
  177. }
  178. if (const char *const envVST2 = std::getenv("VST_PATH"))
  179. {
  180. vst2 = envVST2;
  181. }
  182. else
  183. {
  184. #if defined(CARLA_OS_WIN)
  185. // https://helpcenter.steinberg.de/hc/en-us/articles/115000177084
  186. vst2 = PROGRAMFILES + "\\VSTPlugins";
  187. vst2 += ";" + PROGRAMFILES + "\\Steinberg\\VSTPlugins";
  188. vst2 += ";" + COMMONPROGRAMFILES + "\\VST2";
  189. vst2 += ";" + COMMONPROGRAMFILES + "\\Steinberg\\VST2";
  190. #elif defined(CARLA_OS_HAIKU)
  191. vst2 = HOME + "/.vst";
  192. vst2 += ":/system/add-ons/media/vstplugins";
  193. #elif defined(CARLA_OS_MAC)
  194. // https://helpcenter.steinberg.de/hc/en-us/articles/115000171310
  195. vst2 = HOME + "/Library/Audio/Plug-Ins/VST";
  196. vst2 += ":/Library/Audio/Plug-Ins/VST";
  197. #else
  198. // no official spec, use common paths
  199. vst2 = HOME + "/.vst";
  200. vst2 += ":" + HOME + "/.lxvst";
  201. vst2 += ":/usr/local/lib/vst";
  202. vst2 += ":/usr/local/lib/lxvst";
  203. vst2 += ":/usr/lib/vst";
  204. vst2 += ":/usr/lib/lxvst";
  205. #endif
  206. }
  207. if (const char *const envVST3 = std::getenv("VST3_PATH"))
  208. {
  209. vst3 = envVST3;
  210. }
  211. else
  212. {
  213. // https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/Locations+Format/Plugin+Locations.html
  214. #if defined(CARLA_OS_WIN)
  215. vst3 = LOCALAPPDATA + "\\Programs\\Common\\VST3";
  216. vst3 += ";" + COMMONPROGRAMFILES + "\\VST3";
  217. #elif defined(CARLA_OS_HAIKU)
  218. vst3 = HOME + "/.vst3";
  219. vst3 += ":/system/add-ons/media/vst3plugins";
  220. #elif defined(CARLA_OS_MAC)
  221. vst3 = HOME + "/Library/Audio/Plug-Ins/VST3";
  222. vst3 += ":/Library/Audio/Plug-Ins/VST3";
  223. #else
  224. vst3 = HOME + "/.vst3";
  225. vst3 += ":/usr/local/lib/vst3";
  226. vst3 += ":/usr/lib/vst3";
  227. #endif
  228. }
  229. if (const char *const envCLAP = std::getenv("CLAP_PATH"))
  230. {
  231. clap = envCLAP;
  232. }
  233. else
  234. {
  235. // https://github.com/free-audio/clap/blob/main/include/clap/entry.h
  236. #if defined(CARLA_OS_WIN)
  237. clap = LOCALAPPDATA + "\\Programs\\Common\\CLAP";
  238. clap += ";" + COMMONPROGRAMFILES + "\\CLAP";
  239. #elif defined(CARLA_OS_HAIKU)
  240. clap = HOME + "/.clap";
  241. clap += ":/system/add-ons/media/clapplugins";
  242. #elif defined(CARLA_OS_MAC)
  243. clap = HOME + "/Library/Audio/Plug-Ins/CLAP";
  244. clap += ":/Library/Audio/Plug-Ins/CLAP";
  245. #else
  246. clap = HOME + "/.clap";
  247. clap += ":/usr/local/lib/clap";
  248. clap += ":/usr/lib/clap";
  249. #endif
  250. }
  251. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  252. if (const char *const envJSFX = std::getenv("JSFX_PATH"))
  253. {
  254. jsfx = envJSFX;
  255. }
  256. else
  257. {
  258. // REAPER user data directory
  259. #if defined(CARLA_OS_WIN)
  260. jsfx = APPDATA + "\\REAPER\\Effects";
  261. #elif defined(CARLA_OS_MAC)
  262. jsfx = HOME + "/Library/Application Support/REAPER/Effects";
  263. #else
  264. jsfx = CONFIG_HOME + "/REAPER/Effects";
  265. #endif
  266. }
  267. if (const char *const envSF2 = std::getenv("SF2_PATH"))
  268. {
  269. sf2 = envSF2;
  270. }
  271. else
  272. {
  273. #if defined(CARLA_OS_WIN)
  274. sf2 = APPDATA + "\\SF2";
  275. #else
  276. sf2 = HOME + "/.sounds/sf2";
  277. sf2 += ":" + HOME + "/.sounds/sf3";
  278. sf2 += ":/usr/share/sounds/sf2";
  279. sf2 += ":/usr/share/sounds/sf3";
  280. sf2 += ":/usr/share/soundfonts";
  281. #endif
  282. }
  283. if (const char *const envSFZ = std::getenv("SFZ_PATH"))
  284. {
  285. sfz = envSFZ;
  286. }
  287. else
  288. {
  289. #if defined(CARLA_OS_WIN)
  290. sfz = APPDATA + "\\SFZ";
  291. #else
  292. sfz = HOME + "/.sounds/sfz";
  293. sfz += ":/usr/share/sounds/sfz";
  294. #endif
  295. }
  296. #endif // !CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  297. #ifdef CARLA_OS_WIN
  298. if (envPROGRAMFILESx86 != nullptr)
  299. {
  300. const QCarlaString PROGRAMFILESx86(envPROGRAMFILESx86);
  301. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  302. ladspa += ";" + PROGRAMFILESx86 + "\\LADSPA";
  303. dssi += ";" + PROGRAMFILESx86 + "\\DSSI";
  304. #endif
  305. vst2 += ";" + PROGRAMFILESx86 + "\\VSTPlugins";
  306. vst2 += ";" + PROGRAMFILESx86 + "\\Steinberg\\VSTPlugins";
  307. }
  308. if (envCOMMONPROGRAMFILESx86 != nullptr)
  309. {
  310. const QCarlaString COMMONPROGRAMFILESx86(envCOMMONPROGRAMFILESx86);
  311. vst3 += COMMONPROGRAMFILESx86 + "\\VST3";
  312. clap += COMMONPROGRAMFILESx86 + "\\CLAP";
  313. }
  314. #elif !defined(CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS)
  315. QCarlaString winePrefix;
  316. if (const char* const envWINEPREFIX = std::getenv("WINEPREFIX"))
  317. winePrefix = envWINEPREFIX;
  318. if (winePrefix.isEmpty())
  319. winePrefix = HOME + "/.wine";
  320. if (QDir(winePrefix).exists())
  321. {
  322. vst2 += ":" + winePrefix + "/drive_c/Program Files/VstPlugins";
  323. vst2 += ":" + winePrefix + "/drive_c/Program Files/Steinberg/VstPlugins";
  324. vst3 += ":" + winePrefix + "/drive_c/Program Files/Common Files/VST3";
  325. clap += ":" + winePrefix + "/drive_c/Program Files/Common Files/CLAP";
  326. #ifdef CARLA_OS_64BIT
  327. if (QDir(winePrefix + "/drive_c/Program Files (x86)").exists())
  328. {
  329. vst2 += ":" + winePrefix + "/drive_c/Program Files (x86)/VstPlugins";
  330. vst2 += ":" + winePrefix + "/drive_c/Program Files (x86)/Steinberg/VstPlugins";
  331. vst3 += ":" + winePrefix + "/drive_c/Program Files (x86)/Common Files/VST3";
  332. clap += ":" + winePrefix + "/drive_c/Program Files (x86)/Common Files/CLAP";
  333. }
  334. #endif
  335. }
  336. #endif
  337. }
  338. };
  339. // --------------------------------------------------------------------------------------------------------------------
  340. // Backwards-compatible horizontalAdvance/width call, depending on Qt version
  341. static inline
  342. int fontMetricsHorizontalAdvance(const QFontMetrics& fontMetrics, const QString& string)
  343. {
  344. #if QT_VERSION >= 0x50b00
  345. return fontMetrics.horizontalAdvance(string);
  346. #else
  347. return fontMetrics.width(string);
  348. #endif
  349. }
  350. // --------------------------------------------------------------------------------------------------------------------
  351. // Qt-compatible plugin info
  352. // base details, nicely packed and POD-only so we can directly use as binary
  353. struct PluginInfoHeader {
  354. uint16_t build;
  355. uint16_t type;
  356. uint32_t hints;
  357. uint64_t uniqueId;
  358. uint16_t audioIns;
  359. uint16_t audioOuts;
  360. uint16_t cvIns;
  361. uint16_t cvOuts;
  362. uint16_t midiIns;
  363. uint16_t midiOuts;
  364. uint16_t parameterIns;
  365. uint16_t parameterOuts;
  366. };
  367. // full details, now with non-POD types
  368. struct PluginInfo : PluginInfoHeader {
  369. QString category;
  370. QString filename;
  371. QString name;
  372. QString label;
  373. QString maker;
  374. };
  375. // convert PluginInfo to Qt types
  376. static QVariant asByteArray(const PluginInfo& info)
  377. {
  378. QByteArray qdata;
  379. // start with the POD data, stored as-is
  380. qdata.append(static_cast<const char*>(static_cast<const void*>(&info)), sizeof(PluginInfoHeader));
  381. // then all the strings, with a null terminating byte
  382. {
  383. const QByteArray qcategory(info.category.toUtf8());
  384. qdata += qcategory.constData();
  385. qdata += '\0';
  386. }
  387. {
  388. const QByteArray qfilename(info.filename.toUtf8());
  389. qdata += qfilename.constData();
  390. qdata += '\0';
  391. }
  392. {
  393. const QByteArray qname(info.name.toUtf8());
  394. qdata += qname.constData();
  395. qdata += '\0';
  396. }
  397. {
  398. const QByteArray qlabel(info.label.toUtf8());
  399. qdata += qlabel.constData();
  400. qdata += '\0';
  401. }
  402. {
  403. const QByteArray qmaker(info.maker.toUtf8());
  404. qdata += qmaker.constData();
  405. qdata += '\0';
  406. }
  407. return qdata;
  408. }
  409. static QVariant asVariant(const PluginInfo& info)
  410. {
  411. return QVariant(asByteArray(info));
  412. }
  413. // convert Qt types to PluginInfo
  414. static PluginInfo asPluginInfo(const QByteArray &qdata)
  415. {
  416. // make sure data is big enough to fit POD data + 5 strings
  417. CARLA_SAFE_ASSERT_RETURN(static_cast<size_t>(qdata.size()) >= sizeof(PluginInfoHeader) + sizeof(char) * 5, {});
  418. // read POD data first
  419. const PluginInfoHeader* const data
  420. = static_cast<const PluginInfoHeader*>(static_cast<const void*>(qdata.constData()));
  421. PluginInfo info = {};
  422. info.build = data->build;
  423. info.type = data->type;
  424. info.hints = data->hints;
  425. info.uniqueId = data->uniqueId;
  426. info.audioIns = data->audioIns;
  427. info.audioOuts = data->audioOuts;
  428. info.cvIns = data->cvIns;
  429. info.cvOuts = data->cvOuts;
  430. info.midiIns = data->midiIns;
  431. info.midiOuts = data->midiOuts;
  432. info.parameterIns = data->parameterIns;
  433. info.parameterOuts = data->parameterOuts;
  434. // then all the strings, keeping the same order as in `asVariant`
  435. const char* sdata = static_cast<const char*>(static_cast<const void*>(data + 1));
  436. info.category = QString::fromUtf8(sdata);
  437. sdata += info.category.size() + 1;
  438. info.filename = QString::fromUtf8(sdata);
  439. sdata += info.filename.size() + 1;
  440. info.name = QString::fromUtf8(sdata);
  441. sdata += info.name.size() + 1;
  442. info.label = QString::fromUtf8(sdata);
  443. sdata += info.label.size() + 1;
  444. info.maker = QString::fromUtf8(sdata);
  445. sdata += info.maker.size() + 1;
  446. return info;
  447. }
  448. static PluginInfo asPluginInfo(const QVariant& var)
  449. {
  450. return asPluginInfo(var.toByteArray());
  451. }
  452. static QList<PluginInfo> asPluginInfoList(const QVariant& var)
  453. {
  454. QCarlaByteArray qdata(var.toByteArray());
  455. QList<PluginInfo> plist;
  456. while (!qdata.isEmpty())
  457. {
  458. const PluginInfo info = asPluginInfo(qdata);
  459. CARLA_SAFE_ASSERT_RETURN(info.build != BINARY_NONE, {});
  460. plist.append(info);
  461. qdata = qdata.sliced(sizeof(PluginInfoHeader)
  462. + info.category.size() + info.filename.size() + info.name.size()
  463. + info.label.size() + info.maker.size() + 5);
  464. }
  465. return plist;
  466. }
  467. // --------------------------------------------------------------------------------------------------------------------
  468. // Qt-compatible plugin favorite
  469. // base details, nicely packed and POD-only so we can directly use as binary
  470. struct PluginFavoriteHeader {
  471. uint16_t type;
  472. uint64_t uniqueId;
  473. };
  474. // full details, now with non-POD types
  475. struct PluginFavorite : PluginFavoriteHeader {
  476. QString filename;
  477. QString label;
  478. PluginFavorite()
  479. {
  480. type = PLUGIN_NONE;
  481. uniqueId = 0;
  482. }
  483. PluginFavorite(uint16_t t, uint64_t u, const QString& f, const QString& l)
  484. : filename(f), label(l)
  485. {
  486. type = t;
  487. uniqueId = u;
  488. }
  489. bool operator==(const PluginFavorite& other) const
  490. {
  491. return type == other.type && uniqueId == other.uniqueId && filename == other.filename && label == other.label;
  492. }
  493. };
  494. // convert PluginFavorite to Qt types
  495. static QByteArray asByteArray(const PluginFavorite& fav)
  496. {
  497. QByteArray qdata;
  498. // start with the POD data, stored as-is
  499. qdata.append(static_cast<const char*>(static_cast<const void*>(&fav)), sizeof(PluginFavoriteHeader));
  500. // then all the strings, with a null terminating byte
  501. {
  502. const QByteArray qfilename(fav.filename.toUtf8());
  503. qdata += qfilename.constData();
  504. qdata += '\0';
  505. }
  506. {
  507. const QByteArray qlabel(fav.label.toUtf8());
  508. qdata += qlabel.constData();
  509. qdata += '\0';
  510. }
  511. return qdata;
  512. }
  513. static QVariant asVariant(const QList<PluginFavorite>& favlist)
  514. {
  515. QByteArray qdata;
  516. for (const PluginFavorite &fav : favlist)
  517. qdata += asByteArray(fav);
  518. return QVariant(qdata);
  519. }
  520. // convert Qt types to PluginInfo
  521. static PluginFavorite asPluginFavorite(const QByteArray& qdata)
  522. {
  523. // make sure data is big enough to fit POD data + 3 strings
  524. CARLA_SAFE_ASSERT_RETURN(static_cast<size_t>(qdata.size()) >= sizeof(PluginFavoriteHeader) + sizeof(char) * 3, {});
  525. // read POD data first
  526. const PluginFavoriteHeader* const data
  527. = static_cast<const PluginFavoriteHeader*>(static_cast<const void*>(qdata.constData()));
  528. PluginFavorite fav = { data->type, data->uniqueId, {}, {} };
  529. // then all the strings, keeping the same order as in `asVariant`
  530. const char* sdata = static_cast<const char*>(static_cast<const void*>(data + 1));
  531. fav.filename = QString::fromUtf8(sdata);
  532. sdata += fav.filename.size() + 1;
  533. fav.label = QString::fromUtf8(sdata);
  534. sdata += fav.label.size() + 1;
  535. return fav;
  536. }
  537. static QList<PluginFavorite> asPluginFavoriteList(const QVariant& var)
  538. {
  539. QCarlaByteArray qdata(var.toByteArray());
  540. QList<PluginFavorite> favlist;
  541. while (!qdata.isEmpty())
  542. {
  543. const PluginFavorite fav = asPluginFavorite(qdata);
  544. CARLA_SAFE_ASSERT_RETURN(fav.type != PLUGIN_NONE, {});
  545. favlist.append(fav);
  546. qdata = qdata.sliced(sizeof(PluginFavoriteHeader) + fav.filename.size() + fav.label.size() + 2);
  547. }
  548. return favlist;
  549. }
  550. // create PluginFavorite from PluginInfo data
  551. static PluginFavorite asPluginFavorite(const PluginInfo& info)
  552. {
  553. return PluginFavorite(info.type, info.uniqueId, info.filename, info.label);
  554. }
  555. // --------------------------------------------------------------------------------------------------------------------
  556. // discovery callbacks
  557. static void discoveryCallback(void* const ptr, const CarlaPluginDiscoveryInfo* const info, const char* const sha1sum)
  558. {
  559. static_cast<PluginListDialog*>(ptr)->addPluginInfo(info, sha1sum);
  560. }
  561. static bool checkCacheCallback(void* const ptr, const char* const filename, const char* const sha1sum)
  562. {
  563. if (sha1sum == nullptr)
  564. return false;
  565. return static_cast<PluginListDialog*>(ptr)->checkPluginCache(filename, sha1sum);
  566. }
  567. // --------------------------------------------------------------------------------------------------------------------
  568. struct PluginListDialog::PrivateData {
  569. int lastTableWidgetIndex = 0;
  570. int timerId = 0;
  571. PluginInfo retPlugin;
  572. // To be changed by parent
  573. bool hasLoadedLv2Plugins = false;
  574. struct Discovery {
  575. BinaryType btype = BINARY_NATIVE;
  576. PluginType ptype = PLUGIN_NONE;
  577. bool firstInit = true;
  578. bool ignoreCache = false;
  579. bool checkInvalid = false;
  580. CarlaPluginDiscoveryHandle handle = nullptr;
  581. QCarlaString tool;
  582. CarlaScopedPointer<PluginRefreshDialog> dialog;
  583. Discovery()
  584. {
  585. restart();
  586. }
  587. ~Discovery()
  588. {
  589. if (handle != nullptr)
  590. carla_plugin_discovery_stop(handle);
  591. }
  592. bool nextTool()
  593. {
  594. if (handle != nullptr)
  595. {
  596. carla_plugin_discovery_stop(handle);
  597. handle = nullptr;
  598. }
  599. #ifdef CARLA_OS_WIN
  600. #ifdef CARLA_OS_WIN64
  601. // look for win32 plugins on win64
  602. if (btype == BINARY_NATIVE)
  603. {
  604. btype = BINARY_WIN32;
  605. ptype = PLUGIN_INTERNAL;
  606. tool = carla_get_library_folder();
  607. tool += CARLA_OS_SEP_STR "carla-discovery-win32.exe";
  608. if (QFile(tool).exists())
  609. return true;
  610. }
  611. #endif
  612. // no other types to try
  613. return false;
  614. #else // CARLA_OS_WIN
  615. #ifndef CARLA_OS_MAC
  616. // try 32bit plugins on 64bit systems, skipping macOS where 32bit is no longer supported
  617. if (btype == BINARY_NATIVE)
  618. {
  619. btype = BINARY_POSIX32;
  620. ptype = PLUGIN_INTERNAL;
  621. tool = carla_get_library_folder();
  622. tool += CARLA_OS_SEP_STR "carla-discovery-posix32";
  623. if (QFile(tool).exists())
  624. return true;
  625. }
  626. #endif
  627. // try wine bridges
  628. #ifdef CARLA_OS_64BIT
  629. if (btype == BINARY_NATIVE || btype == BINARY_POSIX32)
  630. {
  631. btype = BINARY_WIN64;
  632. ptype = PLUGIN_INTERNAL;
  633. tool = carla_get_library_folder();
  634. tool += CARLA_OS_SEP_STR "carla-discovery-win64.exe";
  635. if (QFile(tool).exists())
  636. return true;
  637. }
  638. #endif
  639. {
  640. btype = BINARY_WIN32;
  641. ptype = PLUGIN_INTERNAL;
  642. tool = carla_get_library_folder();
  643. tool += CARLA_OS_SEP_STR "carla-discovery-win32.exe";
  644. if (QFile(tool).exists())
  645. return true;
  646. }
  647. return false;
  648. #endif // CARLA_OS_WIN
  649. }
  650. void restart()
  651. {
  652. btype = BINARY_NATIVE;
  653. ptype = PLUGIN_NONE;
  654. tool = carla_get_library_folder();
  655. tool += CARLA_OS_SEP_STR "carla-discovery-native";
  656. #ifdef CARLA_OS_WIN
  657. tool += ".exe";
  658. #endif
  659. }
  660. } discovery;
  661. PluginPaths paths;
  662. struct {
  663. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  664. std::vector<PluginInfo> internal;
  665. std::vector<PluginInfo> ladspa;
  666. std::vector<PluginInfo> dssi;
  667. #endif
  668. std::vector<PluginInfo> lv2;
  669. std::vector<PluginInfo> vst2;
  670. std::vector<PluginInfo> vst3;
  671. std::vector<PluginInfo> clap;
  672. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  673. #ifdef CARLA_OS_MAC
  674. std::vector<PluginInfo> au;
  675. #endif
  676. std::vector<PluginInfo> jsfx;
  677. std::vector<PluginInfo> kits;
  678. #endif
  679. QMap<QString, QList<PluginInfo>> cache;
  680. QList<PluginFavorite> favorites;
  681. bool add(const PluginInfo& pinfo)
  682. {
  683. switch (pinfo.type)
  684. {
  685. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  686. case PLUGIN_INTERNAL: internal.push_back(pinfo); return true;
  687. case PLUGIN_LADSPA: ladspa.push_back(pinfo); return true;
  688. case PLUGIN_DSSI: dssi.push_back(pinfo); return true;
  689. #endif
  690. case PLUGIN_LV2: lv2.push_back(pinfo); return true;
  691. case PLUGIN_VST2: vst2.push_back(pinfo); return true;
  692. case PLUGIN_VST3: vst3.push_back(pinfo); return true;
  693. case PLUGIN_CLAP: clap.push_back(pinfo); return true;
  694. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  695. #ifdef CARLA_OS_MAC
  696. case PLUGIN_AU: au.push_back(pinfo); return true;
  697. #endif
  698. case PLUGIN_JSFX: jsfx.push_back(pinfo); return true;
  699. case PLUGIN_SF2:
  700. case PLUGIN_SFZ: kits.push_back(pinfo); return true;
  701. #endif
  702. default: return false;
  703. }
  704. }
  705. } plugins;
  706. };
  707. // --------------------------------------------------------------------------------------------------------------------
  708. // Plugin List Dialog
  709. PluginListDialog::PluginListDialog(QWidget* const parent, const HostSettings& hostSettings)
  710. : QDialog(parent),
  711. p(new PrivateData)
  712. {
  713. ui.setupUi(this);
  714. // p->hostSettings = hostSettings;
  715. // ----------------------------------------------------------------------------------------------------------------
  716. // Set-up GUI
  717. ui.b_add->setEnabled(false);
  718. ui.tab_info->tabBar()->hide();
  719. ui.tab_reqs->tabBar()->hide();
  720. #ifdef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  721. ui.ch_internal->hide();
  722. ui.ch_ladspa->hide();
  723. ui.ch_dssi->hide();
  724. ui.ch_au->hide();
  725. ui.ch_jsfx->hide();
  726. ui.ch_kits->hide();
  727. ui.ch_gui->hide();
  728. ui.ch_inline_display->hide();
  729. ui.toolBox->setItemEnabled(3, false);
  730. #endif
  731. // do not resize info frame so much
  732. const QLayout *const infoLayout = ui.tw_info->layout();
  733. const QMargins infoMargins = infoLayout->contentsMargins();
  734. ui.tab_info->setMinimumWidth(infoMargins.left() + infoMargins.right() + infoLayout->spacing() * 3
  735. + fontMetricsHorizontalAdvance(ui.la_id->fontMetrics(), "Has Custom GUI: 9999999999"));
  736. // start with no plugin selected
  737. checkPlugin(-1);
  738. // custom action that listens for Ctrl+F shortcut
  739. addAction(ui.act_focus_search);
  740. #ifdef CARLA_OS_64BIT
  741. ui.ch_bridged->setText(tr("Bridged (32bit)"));
  742. #else
  743. ui.ch_bridged->setChecked(false);
  744. ui.ch_bridged->setEnabled(false);
  745. #endif
  746. #if !(defined(CARLA_OS_LINUX) || defined(CARLA_OS_MAC))
  747. ui.ch_bridged_wine->setChecked(false);
  748. ui.ch_bridged_wine->setEnabled(false);
  749. #endif
  750. #ifdef CARLA_OS_MAC
  751. setWindowModality(Qt::WindowModal);
  752. #else
  753. ui.ch_au->setChecked(false);
  754. ui.ch_au->setEnabled(false);
  755. ui.ch_au->setVisible(false);
  756. #endif
  757. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
  758. // ----------------------------------------------------------------------------------------------------------------
  759. // Load settings
  760. loadSettings();
  761. // ----------------------------------------------------------------------------------------------------------------
  762. // Disable bridges if not enabled in settings
  763. #if 0
  764. // NOTE: We Assume win32 carla build will not run win64 plugins
  765. if (WINDOWS and not kIs64bit) or not host.showPluginBridges:
  766. ui.ch_native.setChecked(True)
  767. ui.ch_native.setEnabled(False)
  768. ui.ch_native.setVisible(True)
  769. ui.ch_bridged.setChecked(False)
  770. ui.ch_bridged.setEnabled(False)
  771. ui.ch_bridged.setVisible(False)
  772. ui.ch_bridged_wine.setChecked(False)
  773. ui.ch_bridged_wine.setEnabled(False)
  774. ui.ch_bridged_wine.setVisible(False)
  775. elif not host.showWineBridges:
  776. ui.ch_bridged_wine.setChecked(False)
  777. ui.ch_bridged_wine.setEnabled(False)
  778. ui.ch_bridged_wine.setVisible(False)
  779. #endif
  780. // ----------------------------------------------------------------------------------------------------------------
  781. // Set-up Icons
  782. if (hostSettings.useSystemIcons)
  783. {
  784. #if 0
  785. ui.b_add.setIcon(getIcon('list-add', 16, 'svgz'))
  786. ui.b_cancel.setIcon(getIcon('dialog-cancel', 16, 'svgz'))
  787. ui.b_clear_filters.setIcon(getIcon('edit-clear', 16, 'svgz'))
  788. ui.b_refresh.setIcon(getIcon('view-refresh', 16, 'svgz'))
  789. QTableWidgetItem* const hhi = ui.tableWidget->horizontalHeaderItem(TW_FAVORITE);
  790. hhi.setIcon(getIcon('bookmarks', 16, 'svgz'))
  791. #endif
  792. }
  793. // ----------------------------------------------------------------------------------------------------------------
  794. // Set-up connections
  795. QObject::connect(this, &QDialog::finished, this, &PluginListDialog::saveSettings);
  796. QObject::connect(ui.b_add, &QPushButton::clicked, this, &QDialog::accept);
  797. QObject::connect(ui.b_cancel, &QPushButton::clicked, this, &QDialog::reject);
  798. QObject::connect(ui.b_refresh, &QPushButton::clicked, this, &PluginListDialog::refreshPlugins);
  799. QObject::connect(ui.b_clear_filters, &QPushButton::clicked, this, &PluginListDialog::clearFilters);
  800. QObject::connect(ui.lineEdit, &QLineEdit::textChanged, this, &PluginListDialog::checkFilters);
  801. QObject::connect(ui.tableWidget, &QTableWidget::currentCellChanged, this, &PluginListDialog::checkPlugin);
  802. QObject::connect(ui.tableWidget, &QTableWidget::cellClicked, this, &PluginListDialog::cellClicked);
  803. QObject::connect(ui.tableWidget, &QTableWidget::cellDoubleClicked, this, &PluginListDialog::cellDoubleClicked);
  804. QObject::connect(ui.ch_internal, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  805. QObject::connect(ui.ch_ladspa, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  806. QObject::connect(ui.ch_dssi, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  807. QObject::connect(ui.ch_lv2, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  808. QObject::connect(ui.ch_vst, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  809. QObject::connect(ui.ch_vst3, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  810. QObject::connect(ui.ch_clap, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  811. QObject::connect(ui.ch_au, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  812. QObject::connect(ui.ch_jsfx, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  813. QObject::connect(ui.ch_kits, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  814. QObject::connect(ui.ch_effects, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  815. QObject::connect(ui.ch_instruments, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  816. QObject::connect(ui.ch_midi, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  817. QObject::connect(ui.ch_other, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  818. QObject::connect(ui.ch_native, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  819. QObject::connect(ui.ch_bridged, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  820. QObject::connect(ui.ch_bridged_wine, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  821. QObject::connect(ui.ch_favorites, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  822. QObject::connect(ui.ch_rtsafe, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  823. QObject::connect(ui.ch_cv, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  824. QObject::connect(ui.ch_gui, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  825. QObject::connect(ui.ch_inline_display, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  826. QObject::connect(ui.ch_stereo, &QCheckBox::clicked, this, &PluginListDialog::checkFilters);
  827. QObject::connect(ui.ch_cat_all, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategoryAll);
  828. QObject::connect(ui.ch_cat_delay, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific);
  829. QObject::connect(ui.ch_cat_distortion, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific);
  830. QObject::connect(ui.ch_cat_dynamics, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific);
  831. QObject::connect(ui.ch_cat_eq, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific);
  832. QObject::connect(ui.ch_cat_filter, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific);
  833. QObject::connect(ui.ch_cat_modulator, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific);
  834. QObject::connect(ui.ch_cat_synth, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific);
  835. QObject::connect(ui.ch_cat_utility, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific);
  836. QObject::connect(ui.ch_cat_other, &QCheckBox::clicked, this, &PluginListDialog::checkFiltersCategorySpecific);
  837. QObject::connect(ui.act_focus_search, &QAction::triggered, this, &PluginListDialog::focusSearchFieldAndSelectAll);
  838. }
  839. PluginListDialog::~PluginListDialog()
  840. {
  841. if (p->timerId != 0)
  842. killTimer(p->timerId);
  843. delete p;
  844. }
  845. // --------------------------------------------------------------------------------------------------------------------
  846. // public methods
  847. const PluginInfo& PluginListDialog::getSelectedPluginInfo() const
  848. {
  849. return p->retPlugin;
  850. }
  851. void PluginListDialog::addPluginInfo(const CarlaPluginDiscoveryInfo* const info, const char* const sha1sum)
  852. {
  853. if (info == nullptr)
  854. {
  855. if (sha1sum != nullptr)
  856. {
  857. QSafeSettings settings("falkTX", "CarlaDatabase3");
  858. settings.setValue(QString("PluginCache/%1").arg(sha1sum), QByteArray());
  859. const QString qsha1sum(sha1sum);
  860. p->plugins.cache[qsha1sum] = {};
  861. }
  862. return;
  863. }
  864. PluginInfo pinfo = {};
  865. pinfo.build = static_cast<uint16_t>(info->btype);
  866. pinfo.type = static_cast<uint16_t>(info->ptype);
  867. pinfo.hints = info->metadata.hints;
  868. pinfo.uniqueId = info->uniqueId;
  869. pinfo.audioIns = static_cast<uint16_t>(info->io.audioIns);
  870. pinfo.audioOuts = static_cast<uint16_t>(info->io.audioOuts);
  871. pinfo.cvIns = static_cast<uint16_t>(info->io.cvIns);
  872. pinfo.cvOuts = static_cast<uint16_t>(info->io.cvOuts);
  873. pinfo.midiIns = static_cast<uint16_t>(info->io.midiIns);
  874. pinfo.midiOuts = static_cast<uint16_t>(info->io.midiOuts);
  875. pinfo.parameterIns = static_cast<uint16_t>(info->io.parameterIns);
  876. pinfo.parameterOuts = static_cast<uint16_t>(info->io.parameterOuts);
  877. pinfo.category = getPluginCategoryAsString(info->metadata.category);
  878. pinfo.filename = QString::fromUtf8(info->filename);
  879. pinfo.name = QString::fromUtf8(info->metadata.name);
  880. pinfo.label = QString::fromUtf8(info->label);
  881. pinfo.maker = QString::fromUtf8(info->metadata.maker);
  882. if (sha1sum != nullptr)
  883. {
  884. QSafeSettings settings("falkTX", "CarlaDatabase3");
  885. const QString qsha1sum(sha1sum);
  886. const QString key = QString("PluginCache/%1").arg(sha1sum);
  887. // single sha1sum can contain >1 plugin
  888. QByteArray qdata;
  889. if (p->plugins.cache.contains(qsha1sum))
  890. qdata = settings.valueByteArray(key);
  891. qdata += asVariant(pinfo).toByteArray();
  892. settings.setValue(key, qdata);
  893. p->plugins.cache[qsha1sum].append(pinfo);
  894. }
  895. #ifdef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  896. if ((pinfo.hints & PLUGIN_HAS_CUSTOM_EMBED_UI) == 0x0)
  897. return;
  898. #endif
  899. p->plugins.add(pinfo);
  900. }
  901. bool PluginListDialog::checkPluginCache(const char* const filename, const char* const sha1sum)
  902. {
  903. // sha1sum is always valid for this call
  904. const QString qsha1sum(sha1sum);
  905. if (filename != nullptr)
  906. p->discovery.dialog->progressBar->setFormat(filename);
  907. if (!p->plugins.cache.contains(qsha1sum))
  908. return false;
  909. const QList<PluginInfo>& plist(p->plugins.cache[qsha1sum]);
  910. if (plist.isEmpty())
  911. return p->discovery.ignoreCache || !p->discovery.checkInvalid;
  912. // if filename does not match, abort (hash collision?)
  913. if (filename == nullptr || plist.first().filename != filename)
  914. {
  915. p->plugins.cache.remove(qsha1sum);
  916. return false;
  917. }
  918. for (const PluginInfo& info : plist)
  919. {
  920. #ifdef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  921. if ((info.hints & PLUGIN_HAS_CUSTOM_EMBED_UI) == 0x0)
  922. continue;
  923. #endif
  924. p->plugins.add(info);
  925. }
  926. return true;
  927. }
  928. // --------------------------------------------------------------------------------------------------------------------
  929. // protected methods
  930. void PluginListDialog::done(const int r)
  931. {
  932. if (r == QDialog::Accepted && ui.tableWidget->currentRow() >= 0)
  933. {
  934. QTableWidgetItem* const widget = ui.tableWidget->item(ui.tableWidget->currentRow(), TW_NAME);
  935. p->retPlugin = asPluginInfo(widget->data(Qt::UserRole + UR_PLUGIN_INFO));
  936. }
  937. else
  938. {
  939. p->retPlugin = {};
  940. }
  941. QDialog::done(r);
  942. }
  943. void PluginListDialog::showEvent(QShowEvent* const event)
  944. {
  945. focusSearchFieldAndSelectAll();
  946. QDialog::showEvent(event);
  947. // Set up initial discovery
  948. if (p->discovery.firstInit)
  949. {
  950. p->discovery.firstInit = false;
  951. p->discovery.dialog = new PluginRefreshDialog(this);
  952. p->discovery.dialog->b_start->setEnabled(false);
  953. p->discovery.dialog->b_skip->setEnabled(true);
  954. p->discovery.dialog->ch_updated->setChecked(true);
  955. p->discovery.dialog->ch_invalid->setChecked(false);
  956. p->discovery.dialog->group->setEnabled(false);
  957. p->discovery.dialog->progressBar->setFormat("Starting initial discovery...");
  958. QObject::connect(p->discovery.dialog->b_skip, &QPushButton::clicked,
  959. this, &PluginListDialog::refreshPluginsSkip);
  960. QObject::connect(p->discovery.dialog, &QDialog::finished,
  961. this, &PluginListDialog::refreshPluginsStop);
  962. p->timerId = startTimer(0);
  963. QTimer::singleShot(0, p->discovery.dialog, &QDialog::exec);
  964. }
  965. }
  966. void PluginListDialog::timerEvent(QTimerEvent* const event)
  967. {
  968. if (event->timerId() == p->timerId)
  969. {
  970. do {
  971. // discovery in progress, keep it going
  972. if (p->discovery.handle != nullptr)
  973. {
  974. if (!carla_plugin_discovery_idle(p->discovery.handle))
  975. {
  976. carla_plugin_discovery_stop(p->discovery.handle);
  977. p->discovery.handle = nullptr;
  978. }
  979. break;
  980. }
  981. // start next discovery
  982. QCarlaString path;
  983. switch (p->discovery.ptype)
  984. {
  985. case PLUGIN_NONE:
  986. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  987. if (p->discovery.btype == BINARY_NATIVE)
  988. {
  989. ui.label->setText(tr("Discovering internal plugins..."));
  990. p->discovery.ptype = PLUGIN_INTERNAL;
  991. break;
  992. }
  993. [[fallthrough]];
  994. case PLUGIN_INTERNAL:
  995. ui.label->setText(tr("Discovering LADSPA plugins..."));
  996. path = p->paths.ladspa;
  997. p->discovery.ptype = PLUGIN_LADSPA;
  998. break;
  999. case PLUGIN_LADSPA:
  1000. ui.label->setText(tr("Discovering DSSI plugins..."));
  1001. path = p->paths.dssi;
  1002. p->discovery.ptype = PLUGIN_DSSI;
  1003. break;
  1004. case PLUGIN_DSSI:
  1005. #endif
  1006. ui.label->setText(tr("Discovering LV2 plugins..."));
  1007. path = p->paths.lv2;
  1008. p->discovery.ptype = PLUGIN_LV2;
  1009. break;
  1010. case PLUGIN_LV2:
  1011. ui.label->setText(tr("Discovering VST2 plugins..."));
  1012. path = p->paths.vst2;
  1013. p->discovery.ptype = PLUGIN_VST2;
  1014. break;
  1015. case PLUGIN_VST2:
  1016. ui.label->setText(tr("Discovering VST3 plugins..."));
  1017. path = p->paths.vst3;
  1018. p->discovery.ptype = PLUGIN_VST3;
  1019. break;
  1020. case PLUGIN_VST3:
  1021. ui.label->setText(tr("Discovering CLAP plugins..."));
  1022. path = p->paths.clap;
  1023. p->discovery.ptype = PLUGIN_CLAP;
  1024. break;
  1025. case PLUGIN_CLAP:
  1026. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  1027. #ifdef CARLA_OS_MAC
  1028. if (p->discovery.btype == BINARY_POSIX32 || p->discovery.btype == BINARY_POSIX64)
  1029. {
  1030. ui.label->setText(tr("Discovering AU plugins..."));
  1031. p->discovery.ptype = PLUGIN_AU;
  1032. break;
  1033. }
  1034. [[fallthrough]];
  1035. case PLUGIN_AU:
  1036. #endif
  1037. if (p->discovery.btype == BINARY_NATIVE && p->paths.jsfx.isNotEmpty())
  1038. {
  1039. ui.label->setText(tr("Discovering JSFX plugins..."));
  1040. path = p->paths.jsfx;
  1041. p->discovery.ptype = PLUGIN_JSFX;
  1042. break;
  1043. }
  1044. [[fallthrough]];
  1045. case PLUGIN_JSFX:
  1046. if (p->discovery.btype == BINARY_NATIVE && p->paths.sf2.isNotEmpty())
  1047. {
  1048. ui.label->setText(tr("Discovering SF2 kits..."));
  1049. path = p->paths.sf2;
  1050. p->discovery.ptype = PLUGIN_SF2;
  1051. break;
  1052. }
  1053. [[fallthrough]];
  1054. case PLUGIN_SF2:
  1055. if (p->discovery.btype == BINARY_NATIVE && p->paths.sfz.isNotEmpty())
  1056. {
  1057. ui.label->setText(tr("Discovering SFZ kits..."));
  1058. path = p->paths.sfz;
  1059. p->discovery.ptype = PLUGIN_SFZ;
  1060. break;
  1061. }
  1062. [[fallthrough]];
  1063. case PLUGIN_SFZ:
  1064. #endif
  1065. default:
  1066. // discovery complete
  1067. if (! p->discovery.nextTool())
  1068. refreshPluginsStop();
  1069. }
  1070. if (p->timerId == 0)
  1071. break;
  1072. if (p->discovery.dialog)
  1073. p->discovery.dialog->progressBar->setFormat(ui.label->text());
  1074. p->discovery.handle = carla_plugin_discovery_start(p->discovery.tool.toUtf8().constData(),
  1075. p->discovery.btype,
  1076. p->discovery.ptype,
  1077. path.toUtf8().constData(),
  1078. discoveryCallback, checkCacheCallback, this);
  1079. } while (false);
  1080. }
  1081. QDialog::timerEvent(event);
  1082. }
  1083. // --------------------------------------------------------------------------------------------------------------------
  1084. // private methods
  1085. void PluginListDialog::addPluginsToTable()
  1086. {
  1087. // ----------------------------------------------------------------------------------------------------------------
  1088. // sum plugins first, creating all needed rows in advance
  1089. ui.tableWidget->setSortingEnabled(false);
  1090. ui.tableWidget->clearContents();
  1091. #ifdef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  1092. ui.tableWidget->setRowCount(
  1093. int(p->plugins.lv2.size() + p->plugins.vst2.size() + p->plugins.vst3.size() + p->plugins.clap.size()));
  1094. constexpr const char* const txt = "Have %1 LV2, %2 VST2, %3 VST3 and %4 CLAP plugins";
  1095. ui.label->setText(tr(txt)
  1096. .arg(QString::number(p->plugins.lv2.size()))
  1097. .arg(QString::number(p->plugins.vst2.size()))
  1098. .arg(QString::number(p->plugins.vst3.size()))
  1099. .arg(QString::number(p->plugins.clap.size())));
  1100. #else
  1101. ui.tableWidget->setRowCount(
  1102. int(p->plugins.internal.size() + p->plugins.ladspa.size() + p->plugins.dssi.size() +
  1103. p->plugins.lv2.size() + p->plugins.vst2.size() + p->plugins.vst3.size() + p->plugins.clap.size() +
  1104. #ifdef CARLA_OS_MAC
  1105. p->plugins.au.size() +
  1106. #endif
  1107. p->plugins.jsfx.size() + p->plugins.kits.size()));
  1108. constexpr const char* const txt = "Have %1 Internal, %2 LADSPA, %3 DSSI, %4 LV2, %5 VST2, %6 VST3, %7 CLAP"
  1109. #ifdef CARLA_OS_MAC
  1110. ", %8 AudioUnit and %9 JSFX plugins, plus %10 Sound Kits"
  1111. #endif
  1112. " and %8 JSFX plugins, plus %9 Sound Kits";
  1113. ui.label->setText(tr(txt)
  1114. .arg(QString::number(p->plugins.internal.size()))
  1115. .arg(QString::number(p->plugins.ladspa.size()))
  1116. .arg(QString::number(p->plugins.dssi.size()))
  1117. .arg(QString::number(p->plugins.lv2.size()))
  1118. .arg(QString::number(p->plugins.vst2.size()))
  1119. .arg(QString::number(p->plugins.vst3.size()))
  1120. .arg(QString::number(p->plugins.clap.size()))
  1121. #ifdef CARLA_OS_MAC
  1122. .arg(QString::number(p->plugins.au.size()))
  1123. #endif
  1124. .arg(QString::number(p->plugins.jsfx.size()))
  1125. .arg(QString::number(p->plugins.kits.size())));
  1126. #endif
  1127. // ----------------------------------------------------------------------------------------------------------------
  1128. // now add all plugins to the table
  1129. auto addPluginToTable = [=](const PluginInfo& info) {
  1130. const int index = p->lastTableWidgetIndex++;
  1131. const bool isFav = p->plugins.favorites.contains(asPluginFavorite(info));
  1132. QTableWidgetItem* const itemFav = new QTableWidgetItem;
  1133. itemFav->setCheckState(isFav ? Qt::Checked : Qt::Unchecked);
  1134. itemFav->setText(isFav ? " " : " ");
  1135. const QString pluginText = (info.name + info.label + info.maker + info.filename).toLower();
  1136. ui.tableWidget->setItem(index, TW_FAVORITE, itemFav);
  1137. ui.tableWidget->setItem(index, TW_NAME, new QTableWidgetItem(info.name));
  1138. ui.tableWidget->setItem(index, TW_LABEL, new QTableWidgetItem(info.label));
  1139. ui.tableWidget->setItem(index, TW_MAKER, new QTableWidgetItem(info.maker));
  1140. ui.tableWidget->setItem(index, TW_BINARY, new QTableWidgetItem(QFileInfo(info.filename).fileName()));
  1141. QTableWidgetItem *const itemName = ui.tableWidget->item(index, TW_NAME);
  1142. itemName->setData(Qt::UserRole + UR_PLUGIN_INFO, asVariant(info));
  1143. itemName->setData(Qt::UserRole + UR_SEARCH_TEXT, pluginText);
  1144. };
  1145. p->lastTableWidgetIndex = 0;
  1146. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  1147. for (const PluginInfo &plugin : p->plugins.internal)
  1148. addPluginToTable(plugin);
  1149. for (const PluginInfo &plugin : p->plugins.ladspa)
  1150. addPluginToTable(plugin);
  1151. for (const PluginInfo &plugin : p->plugins.dssi)
  1152. addPluginToTable(plugin);
  1153. #endif
  1154. for (const PluginInfo &plugin : p->plugins.lv2)
  1155. addPluginToTable(plugin);
  1156. for (const PluginInfo &plugin : p->plugins.vst2)
  1157. addPluginToTable(plugin);
  1158. for (const PluginInfo &plugin : p->plugins.vst3)
  1159. addPluginToTable(plugin);
  1160. for (const PluginInfo& plugin : p->plugins.clap)
  1161. addPluginToTable(plugin);
  1162. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  1163. #ifdef CARLA_OS_MAC
  1164. for (const PluginInfo& plugin : p->plugins.au)
  1165. addPluginToTable(plugin);
  1166. #endif
  1167. for (const PluginInfo& plugin : p->plugins.jsfx)
  1168. addPluginToTable(plugin);
  1169. for (const PluginInfo& plugin : p->plugins.kits)
  1170. addPluginToTable(plugin);
  1171. #endif
  1172. CARLA_SAFE_ASSERT_INT2(p->lastTableWidgetIndex == ui.tableWidget->rowCount(),
  1173. p->lastTableWidgetIndex, ui.tableWidget->rowCount());
  1174. // ----------------------------------------------------------------------------------------------------------------
  1175. // and reenable sorting + filtering
  1176. ui.tableWidget->setSortingEnabled(true);
  1177. checkFilters();
  1178. checkPlugin(ui.tableWidget->currentRow());
  1179. }
  1180. void PluginListDialog::loadSettings()
  1181. {
  1182. const QSafeSettings settings("falkTX", "CarlaDatabase3");
  1183. restoreGeometry(settings.valueByteArray("PluginDatabase/Geometry"));
  1184. ui.ch_effects->setChecked(settings.valueBool("PluginDatabase/ShowEffects", true));
  1185. ui.ch_instruments->setChecked(settings.valueBool("PluginDatabase/ShowInstruments", true));
  1186. ui.ch_midi->setChecked(settings.valueBool("PluginDatabase/ShowMIDI", true));
  1187. ui.ch_other->setChecked(settings.valueBool("PluginDatabase/ShowOther", true));
  1188. ui.ch_internal->setChecked(settings.valueBool("PluginDatabase/ShowInternal", true));
  1189. ui.ch_ladspa->setChecked(settings.valueBool("PluginDatabase/ShowLADSPA", true));
  1190. ui.ch_dssi->setChecked(settings.valueBool("PluginDatabase/ShowDSSI", true));
  1191. ui.ch_lv2->setChecked(settings.valueBool("PluginDatabase/ShowLV2", true));
  1192. ui.ch_vst->setChecked(settings.valueBool("PluginDatabase/ShowVST2", true));
  1193. ui.ch_vst3->setChecked(settings.valueBool("PluginDatabase/ShowVST3", true));
  1194. ui.ch_clap->setChecked(settings.valueBool("PluginDatabase/ShowCLAP", true));
  1195. #ifdef CARLA_OS_MAC
  1196. ui.ch_au->setChecked(settings.valueBool("PluginDatabase/ShowAU", true));
  1197. #endif
  1198. ui.ch_jsfx->setChecked(settings.valueBool("PluginDatabase/ShowJSFX", true));
  1199. ui.ch_kits->setChecked(settings.valueBool("PluginDatabase/ShowKits", true));
  1200. ui.ch_native->setChecked(settings.valueBool("PluginDatabase/ShowNative", true));
  1201. ui.ch_bridged->setChecked(settings.valueBool("PluginDatabase/ShowBridged", true));
  1202. ui.ch_bridged_wine->setChecked(settings.valueBool("PluginDatabase/ShowBridgedWine", true));
  1203. ui.ch_favorites->setChecked(settings.valueBool("PluginDatabase/ShowFavorites", false));
  1204. ui.ch_rtsafe->setChecked(settings.valueBool("PluginDatabase/ShowRtSafe", false));
  1205. ui.ch_cv->setChecked(settings.valueBool("PluginDatabase/ShowHasCV", false));
  1206. ui.ch_gui->setChecked(settings.valueBool("PluginDatabase/ShowHasGUI", false));
  1207. ui.ch_inline_display->setChecked(settings.valueBool("PluginDatabase/ShowHasInlineDisplay", false));
  1208. ui.ch_stereo->setChecked(settings.valueBool("PluginDatabase/ShowStereoOnly", false));
  1209. ui.lineEdit->setText(settings.valueString("PluginDatabase/SearchText", ""));
  1210. const QString categories = settings.valueString("PluginDatabase/ShowCategory", "all");
  1211. if (categories == "all" or categories.length() < 2)
  1212. {
  1213. ui.ch_cat_all->setChecked(true);
  1214. ui.ch_cat_delay->setChecked(false);
  1215. ui.ch_cat_distortion->setChecked(false);
  1216. ui.ch_cat_dynamics->setChecked(false);
  1217. ui.ch_cat_eq->setChecked(false);
  1218. ui.ch_cat_filter->setChecked(false);
  1219. ui.ch_cat_modulator->setChecked(false);
  1220. ui.ch_cat_synth->setChecked(false);
  1221. ui.ch_cat_utility->setChecked(false);
  1222. ui.ch_cat_other->setChecked(false);
  1223. }
  1224. else
  1225. {
  1226. ui.ch_cat_all->setChecked(false);
  1227. ui.ch_cat_delay->setChecked(categories.contains(":delay:"));
  1228. ui.ch_cat_distortion->setChecked(categories.contains(":distortion:"));
  1229. ui.ch_cat_dynamics->setChecked(categories.contains(":dynamics:"));
  1230. ui.ch_cat_eq->setChecked(categories.contains(":eq:"));
  1231. ui.ch_cat_filter->setChecked(categories.contains(":filter:"));
  1232. ui.ch_cat_modulator->setChecked(categories.contains(":modulator:"));
  1233. ui.ch_cat_synth->setChecked(categories.contains(":synth:"));
  1234. ui.ch_cat_utility->setChecked(categories.contains(":utility:"));
  1235. ui.ch_cat_other->setChecked(categories.contains(":other:"));
  1236. }
  1237. const QByteArray tableGeometry = settings.valueByteArray("PluginDatabase/TableGeometry");
  1238. QHeaderView* const horizontalHeader = ui.tableWidget->horizontalHeader();
  1239. if (! tableGeometry.isNull())
  1240. {
  1241. horizontalHeader->restoreState(tableGeometry);
  1242. }
  1243. else
  1244. {
  1245. ui.tableWidget->setColumnWidth(TW_NAME, 250);
  1246. ui.tableWidget->setColumnWidth(TW_LABEL, 200);
  1247. ui.tableWidget->setColumnWidth(TW_MAKER, 150);
  1248. ui.tableWidget->sortByColumn(TW_NAME, Qt::AscendingOrder);
  1249. }
  1250. horizontalHeader->setSectionResizeMode(TW_FAVORITE, QHeaderView::Fixed);
  1251. ui.tableWidget->setColumnWidth(TW_FAVORITE, 24);
  1252. ui.tableWidget->setSortingEnabled(true);
  1253. p->plugins.favorites = asPluginFavoriteList(settings.valueByteArray("PluginListDialog/Favorites"));
  1254. // load entire plugin cache
  1255. const QStringList keys = settings.allKeys();
  1256. for (const QCarlaString key : keys)
  1257. {
  1258. if (!key.startsWith("PluginCache/"))
  1259. continue;
  1260. const QByteArray data(settings.valueByteArray(key));
  1261. if (data.isEmpty())
  1262. p->plugins.cache.insert(key.sliced(12), {});
  1263. else
  1264. p->plugins.cache.insert(key.sliced(12), asPluginInfoList(data));
  1265. }
  1266. #ifdef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  1267. // these are not visible, force their value
  1268. ui.ch_native->setChecked(true);
  1269. ui.ch_bridged->setChecked(false);
  1270. ui.ch_bridged_wine->setChecked(false);
  1271. ui.ch_inline_display->setChecked(false);
  1272. #endif
  1273. }
  1274. // -----------------------------------------------------------------------------------------------------------------
  1275. // private slots
  1276. void PluginListDialog::cellClicked(const int row, const int column)
  1277. {
  1278. if (column != TW_FAVORITE)
  1279. return;
  1280. const PluginInfo info = asPluginInfo(ui.tableWidget->item(row, TW_NAME)->data(Qt::UserRole + UR_PLUGIN_INFO));
  1281. const PluginFavorite fav = asPluginFavorite(info);
  1282. const bool isFavorite = p->plugins.favorites.contains(fav);
  1283. if (ui.tableWidget->item(row, TW_FAVORITE)->checkState() == Qt::Checked)
  1284. {
  1285. if (!isFavorite)
  1286. p->plugins.favorites.append(fav);
  1287. }
  1288. else if (isFavorite)
  1289. {
  1290. p->plugins.favorites.removeAll(fav);
  1291. }
  1292. QSafeSettings settings("falkTX", "CarlaDatabase3");
  1293. settings.setValue("PluginListDialog/Favorites", asVariant(p->plugins.favorites));
  1294. }
  1295. void PluginListDialog::cellDoubleClicked(int, const int column)
  1296. {
  1297. if (column != TW_FAVORITE)
  1298. done(QDialog::Accepted);
  1299. }
  1300. void PluginListDialog::focusSearchFieldAndSelectAll()
  1301. {
  1302. ui.lineEdit->setFocus();
  1303. ui.lineEdit->selectAll();
  1304. }
  1305. void PluginListDialog::checkFilters()
  1306. {
  1307. const QCarlaString text = ui.lineEdit->text().toLower();
  1308. const bool hideEffects = !ui.ch_effects->isChecked();
  1309. const bool hideInstruments = !ui.ch_instruments->isChecked();
  1310. const bool hideMidi = !ui.ch_midi->isChecked();
  1311. const bool hideOther = !ui.ch_other->isChecked();
  1312. const bool hideInternal = !ui.ch_internal->isChecked();
  1313. const bool hideLadspa = !ui.ch_ladspa->isChecked();
  1314. const bool hideDSSI = !ui.ch_dssi->isChecked();
  1315. const bool hideLV2 = !ui.ch_lv2->isChecked();
  1316. const bool hideVST2 = !ui.ch_vst->isChecked();
  1317. const bool hideVST3 = !ui.ch_vst3->isChecked();
  1318. const bool hideCLAP = !ui.ch_clap->isChecked();
  1319. const bool hideAU = !ui.ch_au->isChecked();
  1320. const bool hideJSFX = !ui.ch_jsfx->isChecked();
  1321. const bool hideKits = !ui.ch_kits->isChecked();
  1322. const bool hideNative = !ui.ch_native->isChecked();
  1323. const bool hideBridged = !ui.ch_bridged->isChecked();
  1324. const bool hideBridgedWine = !ui.ch_bridged_wine->isChecked();
  1325. const bool hideNonFavs = ui.ch_favorites->isChecked();
  1326. const bool hideNonRtSafe = ui.ch_rtsafe->isChecked();
  1327. const bool hideNonCV = ui.ch_cv->isChecked();
  1328. const bool hideNonGui = ui.ch_gui->isChecked();
  1329. const bool hideNonIDisp = ui.ch_inline_display->isChecked();
  1330. const bool hideNonStereo = ui.ch_stereo->isChecked();
  1331. #if defined(CARLA_OS_WIN64)
  1332. static constexpr const BinaryType nativeBins[2] = { BINARY_WIN32, BINARY_WIN64 };
  1333. static constexpr const BinaryType wineBins[2] = { BINARY_NONE, BINARY_NONE };
  1334. #elif defined(CARLA_OS_WIN32)
  1335. static constexpr const BinaryType nativeBins[2] = { BINARY_WIN32, BINARY_NONE };
  1336. static constexpr const BinaryType wineBins[2] = { BINARY_NONE, BINARY_NONE };
  1337. #elif defined(CARLA_OS_MAC)
  1338. static constexpr const BinaryType nativeBins[2] = { BINARY_POSIX64, BINARY_NONE };
  1339. static constexpr const BinaryType wineBins[2] = { BINARY_WIN32, BINARY_WIN64 };
  1340. #else
  1341. static constexpr const BinaryType nativeBins[2] = { BINARY_POSIX32, BINARY_POSIX64 };
  1342. static constexpr const BinaryType wineBins[2] = { BINARY_WIN32, BINARY_WIN64 };
  1343. #endif
  1344. for (int i=0, c=ui.tableWidget->rowCount(); i<c; ++i)
  1345. {
  1346. const PluginInfo info = asPluginInfo(ui.tableWidget->item(i, TW_NAME)->data(Qt::UserRole + UR_PLUGIN_INFO));
  1347. const QString ptext = ui.tableWidget->item(i, TW_NAME)->data(Qt::UserRole + UR_SEARCH_TEXT).toString();
  1348. const uint16_t aIns = info.audioIns;
  1349. const uint16_t aOuts = info.audioOuts;
  1350. const uint16_t cvIns = info.cvIns;
  1351. const uint16_t cvOuts = info.cvOuts;
  1352. const uint16_t mIns = info.midiIns;
  1353. const uint16_t mOuts = info.midiOuts;
  1354. const uint32_t phints = info.hints;
  1355. const uint16_t ptype = info.type;
  1356. const QString categ = info.category;
  1357. const bool isSynth = phints & PLUGIN_IS_SYNTH;
  1358. const bool isEffect = aIns > 0 && aOuts > 0 && !isSynth;
  1359. const bool isMidi = aIns == 0 && aOuts == 0 && mIns > 0 && mOuts > 0;
  1360. const bool isKit = ptype == PLUGIN_SF2 || ptype == PLUGIN_SFZ;
  1361. const bool isOther = !(isEffect || isSynth || isMidi || isKit);
  1362. const bool isNative = info.build == BINARY_NATIVE;
  1363. const bool isRtSafe = phints & PLUGIN_IS_RTSAFE;
  1364. const bool isStereo = (aIns == 2 && aOuts == 2) || (isSynth && aOuts == 2);
  1365. const bool hasCV = cvIns + cvOuts > 0;
  1366. const bool hasGui = phints & PLUGIN_HAS_CUSTOM_UI;
  1367. const bool hasIDisp = phints & PLUGIN_HAS_INLINE_DISPLAY;
  1368. const bool isBridged = !isNative && (nativeBins[0] == info.build || nativeBins[1] == info.build);
  1369. const bool isBridgedWine = !isNative && (wineBins[0] == info.build || wineBins[1] == info.build);
  1370. const auto hasText = [text, ptext]() {
  1371. const QStringList textSplit = text.strip().split(' ');
  1372. for (const QString& t : textSplit)
  1373. if (ptext.contains(t))
  1374. return true;
  1375. return false;
  1376. };
  1377. /**/ if (hideEffects && isEffect)
  1378. ui.tableWidget->hideRow(i);
  1379. else if (hideInstruments && isSynth)
  1380. ui.tableWidget->hideRow(i);
  1381. else if (hideMidi && isMidi)
  1382. ui.tableWidget->hideRow(i);
  1383. else if (hideOther && isOther)
  1384. ui.tableWidget->hideRow(i);
  1385. else if (hideKits && isKit)
  1386. ui.tableWidget->hideRow(i);
  1387. else if (hideInternal && ptype == PLUGIN_INTERNAL)
  1388. ui.tableWidget->hideRow(i);
  1389. else if (hideLadspa && ptype == PLUGIN_LADSPA)
  1390. ui.tableWidget->hideRow(i);
  1391. else if (hideDSSI && ptype == PLUGIN_DSSI)
  1392. ui.tableWidget->hideRow(i);
  1393. else if (hideLV2 && ptype == PLUGIN_LV2)
  1394. ui.tableWidget->hideRow(i);
  1395. else if (hideVST2 && ptype == PLUGIN_VST2)
  1396. ui.tableWidget->hideRow(i);
  1397. else if (hideVST3 && ptype == PLUGIN_VST3)
  1398. ui.tableWidget->hideRow(i);
  1399. else if (hideCLAP && ptype == PLUGIN_CLAP)
  1400. ui.tableWidget->hideRow(i);
  1401. else if (hideAU && ptype == PLUGIN_AU)
  1402. ui.tableWidget->hideRow(i);
  1403. else if (hideJSFX && ptype == PLUGIN_JSFX)
  1404. ui.tableWidget->hideRow(i);
  1405. else if (hideNative && isNative)
  1406. ui.tableWidget->hideRow(i);
  1407. else if (hideBridged && isBridged)
  1408. ui.tableWidget->hideRow(i);
  1409. else if (hideBridgedWine && isBridgedWine)
  1410. ui.tableWidget->hideRow(i);
  1411. else if (hideNonRtSafe && not isRtSafe)
  1412. ui.tableWidget->hideRow(i);
  1413. else if (hideNonCV && not hasCV)
  1414. ui.tableWidget->hideRow(i);
  1415. else if (hideNonGui && not hasGui)
  1416. ui.tableWidget->hideRow(i);
  1417. else if (hideNonIDisp && not hasIDisp)
  1418. ui.tableWidget->hideRow(i);
  1419. else if (hideNonStereo && not isStereo)
  1420. ui.tableWidget->hideRow(i);
  1421. else if (text.isNotEmpty() && ! hasText())
  1422. ui.tableWidget->hideRow(i);
  1423. else if (hideNonFavs && !p->plugins.favorites.contains(asPluginFavorite(info)))
  1424. ui.tableWidget->hideRow(i);
  1425. else if (ui.ch_cat_all->isChecked() or
  1426. (ui.ch_cat_delay->isChecked() && categ == "delay") or
  1427. (ui.ch_cat_distortion->isChecked() && categ == "distortion") or
  1428. (ui.ch_cat_dynamics->isChecked() && categ == "dynamics") or
  1429. (ui.ch_cat_eq->isChecked() && categ == "eq") or
  1430. (ui.ch_cat_filter->isChecked() && categ == "filter") or
  1431. (ui.ch_cat_modulator->isChecked() && categ == "modulator") or
  1432. (ui.ch_cat_synth->isChecked() && categ == "synth") or
  1433. (ui.ch_cat_utility->isChecked() && categ == "utility") or
  1434. (ui.ch_cat_other->isChecked() && categ == "other"))
  1435. ui.tableWidget->showRow(i);
  1436. else
  1437. ui.tableWidget->hideRow(i);
  1438. }
  1439. }
  1440. void PluginListDialog::checkFiltersCategoryAll(const bool clicked)
  1441. {
  1442. const bool notClicked = !clicked;
  1443. ui.ch_cat_delay->setChecked(notClicked);
  1444. ui.ch_cat_distortion->setChecked(notClicked);
  1445. ui.ch_cat_dynamics->setChecked(notClicked);
  1446. ui.ch_cat_eq->setChecked(notClicked);
  1447. ui.ch_cat_filter->setChecked(notClicked);
  1448. ui.ch_cat_modulator->setChecked(notClicked);
  1449. ui.ch_cat_synth->setChecked(notClicked);
  1450. ui.ch_cat_utility->setChecked(notClicked);
  1451. ui.ch_cat_other->setChecked(notClicked);
  1452. checkFilters();
  1453. }
  1454. void PluginListDialog::checkFiltersCategorySpecific(bool clicked)
  1455. {
  1456. if (clicked)
  1457. {
  1458. ui.ch_cat_all->setChecked(false);
  1459. }
  1460. else if (! (ui.ch_cat_delay->isChecked() ||
  1461. ui.ch_cat_distortion->isChecked() ||
  1462. ui.ch_cat_dynamics->isChecked() ||
  1463. ui.ch_cat_eq->isChecked() ||
  1464. ui.ch_cat_filter->isChecked() ||
  1465. ui.ch_cat_modulator->isChecked() ||
  1466. ui.ch_cat_synth->isChecked() ||
  1467. ui.ch_cat_utility->isChecked() ||
  1468. ui.ch_cat_other->isChecked()))
  1469. {
  1470. ui.ch_cat_all->setChecked(true);
  1471. }
  1472. checkFilters();
  1473. }
  1474. void PluginListDialog::clearFilters()
  1475. {
  1476. auto setCheckedWithoutSignaling = [](QCheckBox* const w, const bool checked)
  1477. {
  1478. w->blockSignals(true);
  1479. w->setChecked(checked);
  1480. w->blockSignals(false);
  1481. };
  1482. setCheckedWithoutSignaling(ui.ch_internal, true);
  1483. setCheckedWithoutSignaling(ui.ch_ladspa, true);
  1484. setCheckedWithoutSignaling(ui.ch_dssi, true);
  1485. setCheckedWithoutSignaling(ui.ch_lv2, true);
  1486. setCheckedWithoutSignaling(ui.ch_vst, true);
  1487. setCheckedWithoutSignaling(ui.ch_vst3, true);
  1488. setCheckedWithoutSignaling(ui.ch_clap, true);
  1489. setCheckedWithoutSignaling(ui.ch_jsfx, true);
  1490. setCheckedWithoutSignaling(ui.ch_kits, true);
  1491. setCheckedWithoutSignaling(ui.ch_instruments, true);
  1492. setCheckedWithoutSignaling(ui.ch_effects, true);
  1493. setCheckedWithoutSignaling(ui.ch_midi, true);
  1494. setCheckedWithoutSignaling(ui.ch_other, true);
  1495. setCheckedWithoutSignaling(ui.ch_native, true);
  1496. setCheckedWithoutSignaling(ui.ch_bridged, false);
  1497. setCheckedWithoutSignaling(ui.ch_bridged_wine, false);
  1498. setCheckedWithoutSignaling(ui.ch_favorites, false);
  1499. setCheckedWithoutSignaling(ui.ch_rtsafe, false);
  1500. setCheckedWithoutSignaling(ui.ch_stereo, false);
  1501. setCheckedWithoutSignaling(ui.ch_cv, false);
  1502. setCheckedWithoutSignaling(ui.ch_gui, false);
  1503. setCheckedWithoutSignaling(ui.ch_inline_display, false);
  1504. if (ui.ch_au->isEnabled())
  1505. setCheckedWithoutSignaling(ui.ch_au, true);
  1506. setCheckedWithoutSignaling(ui.ch_cat_all, true);
  1507. setCheckedWithoutSignaling(ui.ch_cat_delay, false);
  1508. setCheckedWithoutSignaling(ui.ch_cat_distortion, false);
  1509. setCheckedWithoutSignaling(ui.ch_cat_dynamics, false);
  1510. setCheckedWithoutSignaling(ui.ch_cat_eq, false);
  1511. setCheckedWithoutSignaling(ui.ch_cat_filter, false);
  1512. setCheckedWithoutSignaling(ui.ch_cat_modulator, false);
  1513. setCheckedWithoutSignaling(ui.ch_cat_synth, false);
  1514. setCheckedWithoutSignaling(ui.ch_cat_utility, false);
  1515. setCheckedWithoutSignaling(ui.ch_cat_other, false);
  1516. ui.lineEdit->blockSignals(true);
  1517. ui.lineEdit->clear();
  1518. ui.lineEdit->blockSignals(false);
  1519. checkFilters();
  1520. }
  1521. // --------------------------------------------------------------------------------------------------------------------
  1522. void PluginListDialog::checkPlugin(const int row)
  1523. {
  1524. if (row >= 0)
  1525. {
  1526. ui.b_add->setEnabled(true);
  1527. const PluginInfo info = asPluginInfo(ui.tableWidget->item(row, TW_NAME)->data(Qt::UserRole + UR_PLUGIN_INFO));
  1528. const bool isSynth = info.hints & PLUGIN_IS_SYNTH;
  1529. const bool isEffect = info.audioIns > 0 && info.audioOuts > 0 && !isSynth;
  1530. const bool isMidi = info.audioIns == 0 && info.audioOuts == 0 && info.midiIns > 0 && info.midiOuts > 0;
  1531. QString ptype;
  1532. /**/ if (isSynth)
  1533. ptype = "Instrument";
  1534. else if (isEffect)
  1535. ptype = "Effect";
  1536. else if (isMidi)
  1537. ptype = "MIDI Plugin";
  1538. else
  1539. ptype = "Other";
  1540. QString parch;
  1541. /**/ if (info.build == BINARY_NATIVE)
  1542. parch = tr("Native");
  1543. else if (info.build == BINARY_POSIX32)
  1544. parch = "posix32";
  1545. else if (info.build == BINARY_POSIX64)
  1546. parch = "posix64";
  1547. else if (info.build == BINARY_WIN32)
  1548. parch = "win32";
  1549. else if (info.build == BINARY_WIN64)
  1550. parch = "win64";
  1551. else if (info.build == BINARY_OTHER)
  1552. parch = tr("Other");
  1553. else if (info.build == BINARY_WIN32)
  1554. parch = tr("Unknown");
  1555. ui.l_format->setText(getPluginTypeAsString(static_cast<PluginType>(info.type)));
  1556. ui.l_type->setText(ptype);
  1557. ui.l_arch->setText(parch);
  1558. ui.l_id->setText(QString::number(info.uniqueId));
  1559. ui.l_ains->setText(QString::number(info.audioIns));
  1560. ui.l_aouts->setText(QString::number(info.audioOuts));
  1561. ui.l_cvins->setText(QString::number(info.cvIns));
  1562. ui.l_cvouts->setText(QString::number(info.cvOuts));
  1563. ui.l_mins->setText(QString::number(info.midiIns));
  1564. ui.l_mouts->setText(QString::number(info.midiOuts));
  1565. ui.l_pins->setText(QString::number(info.parameterIns));
  1566. ui.l_pouts->setText(QString::number(info.parameterOuts));
  1567. ui.l_gui->setText(info.hints & PLUGIN_HAS_CUSTOM_UI ? tr("Yes") : tr("No"));
  1568. ui.l_idisp->setText(info.hints & PLUGIN_HAS_INLINE_DISPLAY ? tr("Yes") : tr("No"));
  1569. ui.l_bridged->setText(info.hints & PLUGIN_IS_BRIDGE ? tr("Yes") : tr("No"));
  1570. ui.l_synth->setText(isSynth ? tr("Yes") : tr("No"));
  1571. }
  1572. else
  1573. {
  1574. ui.b_add->setEnabled(false);
  1575. ui.l_format->setText("---");
  1576. ui.l_type->setText("---");
  1577. ui.l_arch->setText("---");
  1578. ui.l_id->setText("---");
  1579. ui.l_ains->setText("---");
  1580. ui.l_aouts->setText("---");
  1581. ui.l_cvins->setText("---");
  1582. ui.l_cvouts->setText("---");
  1583. ui.l_mins->setText("---");
  1584. ui.l_mouts->setText("---");
  1585. ui.l_pins->setText("---");
  1586. ui.l_pouts->setText("---");
  1587. ui.l_gui->setText("---");
  1588. ui.l_idisp->setText("---");
  1589. ui.l_bridged->setText("---");
  1590. ui.l_synth->setText("---");
  1591. }
  1592. }
  1593. // --------------------------------------------------------------------------------------------------------------------
  1594. void PluginListDialog::refreshPlugins()
  1595. {
  1596. refreshPluginsStop();
  1597. p->discovery.dialog = new PluginRefreshDialog(this);
  1598. QObject::connect(p->discovery.dialog->b_start, &QPushButton::clicked,
  1599. this, &PluginListDialog::refreshPluginsStart);
  1600. QObject::connect(p->discovery.dialog->b_skip, &QPushButton::clicked,
  1601. this, &PluginListDialog::refreshPluginsSkip);
  1602. QObject::connect(p->discovery.dialog, &QDialog::finished,
  1603. this, &PluginListDialog::refreshPluginsStop);
  1604. p->discovery.dialog->exec();
  1605. }
  1606. void PluginListDialog::refreshPluginsStart()
  1607. {
  1608. // remove old plugins
  1609. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  1610. p->plugins.internal.clear();
  1611. p->plugins.ladspa.clear();
  1612. p->plugins.dssi.clear();
  1613. #endif
  1614. p->plugins.lv2.clear();
  1615. p->plugins.vst2.clear();
  1616. p->plugins.vst3.clear();
  1617. p->plugins.clap.clear();
  1618. #ifndef CARLA_FRONTEND_ONLY_EMBEDDABLE_PLUGINS
  1619. #ifdef CARLA_OS_MAC
  1620. p->plugins.au.clear();
  1621. #endif
  1622. p->plugins.jsfx.clear();
  1623. p->plugins.kits.clear();
  1624. #endif
  1625. p->discovery.dialog->b_start->setEnabled(false);
  1626. p->discovery.dialog->b_skip->setEnabled(true);
  1627. p->discovery.ignoreCache = p->discovery.dialog->ch_all->isChecked();
  1628. p->discovery.checkInvalid =
  1629. p->discovery.dialog->ch_invalid->isChecked();
  1630. if (p->discovery.ignoreCache)
  1631. p->plugins.cache.clear();
  1632. // start discovery again
  1633. p->discovery.restart();
  1634. if (p->timerId == 0)
  1635. p->timerId = startTimer(0);
  1636. }
  1637. void PluginListDialog::refreshPluginsStop()
  1638. {
  1639. // stop previous discovery if still running
  1640. if (p->discovery.handle != nullptr)
  1641. {
  1642. carla_plugin_discovery_stop(p->discovery.handle);
  1643. p->discovery.handle = nullptr;
  1644. }
  1645. if (p->discovery.dialog)
  1646. {
  1647. p->discovery.dialog->close();
  1648. p->discovery.dialog = nullptr;
  1649. }
  1650. if (p->timerId != 0)
  1651. {
  1652. killTimer(p->timerId);
  1653. p->timerId = 0;
  1654. addPluginsToTable();
  1655. }
  1656. }
  1657. void PluginListDialog::refreshPluginsSkip()
  1658. {
  1659. if (p->discovery.handle != nullptr)
  1660. carla_plugin_discovery_skip(p->discovery.handle);
  1661. }
  1662. // --------------------------------------------------------------------------------------------------------------------
  1663. void PluginListDialog::saveSettings()
  1664. {
  1665. QSafeSettings settings("falkTX", "CarlaDatabase3");
  1666. settings.setValue("PluginDatabase/Geometry", saveGeometry());
  1667. settings.setValue("PluginDatabase/TableGeometry", ui.tableWidget->horizontalHeader()->saveState());
  1668. settings.setValue("PluginDatabase/ShowEffects", ui.ch_effects->isChecked());
  1669. settings.setValue("PluginDatabase/ShowInstruments", ui.ch_instruments->isChecked());
  1670. settings.setValue("PluginDatabase/ShowMIDI", ui.ch_midi->isChecked());
  1671. settings.setValue("PluginDatabase/ShowOther", ui.ch_other->isChecked());
  1672. settings.setValue("PluginDatabase/ShowInternal", ui.ch_internal->isChecked());
  1673. settings.setValue("PluginDatabase/ShowLADSPA", ui.ch_ladspa->isChecked());
  1674. settings.setValue("PluginDatabase/ShowDSSI", ui.ch_dssi->isChecked());
  1675. settings.setValue("PluginDatabase/ShowLV2", ui.ch_lv2->isChecked());
  1676. settings.setValue("PluginDatabase/ShowVST2", ui.ch_vst->isChecked());
  1677. settings.setValue("PluginDatabase/ShowVST3", ui.ch_vst3->isChecked());
  1678. settings.setValue("PluginDatabase/ShowCLAP", ui.ch_clap->isChecked());
  1679. settings.setValue("PluginDatabase/ShowAU", ui.ch_au->isChecked());
  1680. settings.setValue("PluginDatabase/ShowJSFX", ui.ch_jsfx->isChecked());
  1681. settings.setValue("PluginDatabase/ShowKits", ui.ch_kits->isChecked());
  1682. settings.setValue("PluginDatabase/ShowNative", ui.ch_native->isChecked());
  1683. settings.setValue("PluginDatabase/ShowBridged", ui.ch_bridged->isChecked());
  1684. settings.setValue("PluginDatabase/ShowBridgedWine", ui.ch_bridged_wine->isChecked());
  1685. settings.setValue("PluginDatabase/ShowFavorites", ui.ch_favorites->isChecked());
  1686. settings.setValue("PluginDatabase/ShowRtSafe", ui.ch_rtsafe->isChecked());
  1687. settings.setValue("PluginDatabase/ShowHasCV", ui.ch_cv->isChecked());
  1688. settings.setValue("PluginDatabase/ShowHasGUI", ui.ch_gui->isChecked());
  1689. settings.setValue("PluginDatabase/ShowHasInlineDisplay", ui.ch_inline_display->isChecked());
  1690. settings.setValue("PluginDatabase/ShowStereoOnly", ui.ch_stereo->isChecked());
  1691. settings.setValue("PluginDatabase/SearchText", ui.lineEdit->text());
  1692. if (ui.ch_cat_all->isChecked())
  1693. {
  1694. settings.setValue("PluginDatabase/ShowCategory", "all");
  1695. }
  1696. else
  1697. {
  1698. QCarlaString categories;
  1699. if (ui.ch_cat_delay->isChecked())
  1700. categories += ":delay";
  1701. if (ui.ch_cat_distortion->isChecked())
  1702. categories += ":distortion";
  1703. if (ui.ch_cat_dynamics->isChecked())
  1704. categories += ":dynamics";
  1705. if (ui.ch_cat_eq->isChecked())
  1706. categories += ":eq";
  1707. if (ui.ch_cat_filter->isChecked())
  1708. categories += ":filter";
  1709. if (ui.ch_cat_modulator->isChecked())
  1710. categories += ":modulator";
  1711. if (ui.ch_cat_synth->isChecked())
  1712. categories += ":synth";
  1713. if (ui.ch_cat_utility->isChecked())
  1714. categories += ":utility";
  1715. if (ui.ch_cat_other->isChecked())
  1716. categories += ":other";
  1717. if (categories.isNotEmpty())
  1718. categories += ":";
  1719. settings.setValue("PluginDatabase/ShowCategory", categories);
  1720. }
  1721. settings.setValue("PluginListDialog/Favorites", asVariant(p->plugins.favorites));
  1722. }
  1723. // --------------------------------------------------------------------------------------------------------------------
  1724. PluginListDialog*
  1725. carla_frontend_createPluginListDialog(void* const parent)
  1726. {
  1727. const HostSettings hostSettings = {};
  1728. return new PluginListDialog(reinterpret_cast<QWidget*>(parent), hostSettings);
  1729. }
  1730. const PluginListDialogResults*
  1731. carla_frontend_execPluginListDialog(PluginListDialog* const dialog)
  1732. {
  1733. if (dialog->exec())
  1734. {
  1735. static PluginListDialogResults ret;
  1736. static CarlaString category;
  1737. static CarlaString filename;
  1738. static CarlaString name;
  1739. static CarlaString label;
  1740. static CarlaString maker;
  1741. const PluginInfo& plugin(dialog->getSelectedPluginInfo());
  1742. category = plugin.category.toUtf8();
  1743. filename = plugin.filename.toUtf8();
  1744. name = plugin.name.toUtf8();
  1745. label = plugin.label.toUtf8();
  1746. maker = plugin.maker.toUtf8();
  1747. ret.build = plugin.build;
  1748. ret.type = plugin.type;
  1749. ret.hints = plugin.hints;
  1750. ret.category = category;
  1751. ret.filename = filename;
  1752. ret.name = name;
  1753. ret.label = label;
  1754. ret.maker = maker;
  1755. ret.uniqueId = plugin.uniqueId;
  1756. ret.audioIns = plugin.audioIns;
  1757. ret.audioOuts = plugin.audioOuts;
  1758. ret.cvIns = plugin.cvIns;
  1759. ret.cvOuts = plugin.cvOuts;
  1760. ret.midiIns = plugin.midiIns;
  1761. ret.midiOuts = plugin.midiOuts;
  1762. ret.parameterIns = plugin.parameterIns;
  1763. ret.parameterOuts = plugin.parameterOuts;
  1764. return &ret;
  1765. }
  1766. return nullptr;
  1767. }
  1768. // --------------------------------------------------------------------------------------------------------------------
  1769. const PluginListDialogResults*
  1770. carla_frontend_createAndExecPluginListDialog(void* const parent/*, const HostSettings& hostSettings*/)
  1771. {
  1772. const HostSettings hostSettings = {};
  1773. PluginListDialog gui(reinterpret_cast<QWidget*>(parent), hostSettings);
  1774. return carla_frontend_execPluginListDialog(&gui);
  1775. }
  1776. // --------------------------------------------------------------------------------------------------------------------