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.

1944 lines
71KB

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