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.

1852 lines
67KB

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