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.

2168 lines
79KB

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