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.

841 lines
24KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 3 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the LICENSE file.
  16. */
  17. /**
  18. * This file is partially based on VCVRack's patch.cpp
  19. * Copyright (C) 2016-2021 VCV.
  20. *
  21. * This program is free software: you can redistribute it and/or
  22. * modify it under the terms of the GNU General Public License as
  23. * published by the Free Software Foundation; either version 3 of
  24. * the License, or (at your option) any later version.
  25. */
  26. #include "CardinalCommon.hpp"
  27. #include "AsyncDialog.hpp"
  28. #include "PluginContext.hpp"
  29. #include "DistrhoPluginUtils.hpp"
  30. #include <asset.hpp>
  31. #include <context.hpp>
  32. #include <history.hpp>
  33. #include <patch.hpp>
  34. #include <settings.hpp>
  35. #include <string.hpp>
  36. #include <system.hpp>
  37. #include <app/Browser.hpp>
  38. #include <app/Scene.hpp>
  39. #include <engine/Engine.hpp>
  40. #include <window/Window.hpp>
  41. #ifndef DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  42. # error wrong build
  43. #endif
  44. #if defined(STATIC_BUILD) || CARDINAL_VARIANT_MINI
  45. # undef CARDINAL_INIT_OSC_THREAD
  46. #endif
  47. #ifdef NDEBUG
  48. # undef DEBUG
  49. #endif
  50. // for finding special paths
  51. #ifdef ARCH_WIN
  52. # include <shlobj.h>
  53. #else
  54. # include <pwd.h>
  55. # include <unistd.h>
  56. #endif
  57. #ifdef CARDINAL_INIT_OSC_THREAD
  58. # include <lo/lo.h>
  59. #endif
  60. #ifdef DISTRHO_OS_WASM
  61. # include <emscripten/emscripten.h>
  62. #endif
  63. #if defined(CARDINAL_COMMON_DSP_ONLY) || defined(HEADLESS)
  64. # define HEADLESS_BEHAVIOUR
  65. #endif
  66. #if CARDINAL_VARIANT_FX
  67. # define CARDINAL_TEMPLATE_NAME "init/fx.vcv"
  68. #elif CARDINAL_VARIANT_MINI
  69. # define CARDINAL_TEMPLATE_NAME "init/mini.vcv"
  70. #elif CARDINAL_VARIANT_NATIVE
  71. # define CARDINAL_TEMPLATE_NAME "init/native.vcv"
  72. #elif CARDINAL_VARIANT_SYNTH
  73. # define CARDINAL_TEMPLATE_NAME "init/synth.vcv"
  74. #else
  75. # define CARDINAL_TEMPLATE_NAME "init/main.vcv"
  76. #endif
  77. namespace rack {
  78. namespace asset {
  79. std::string patchesPath();
  80. void destroy();
  81. }
  82. namespace plugin {
  83. void initStaticPlugins();
  84. void destroyStaticPlugins();
  85. }
  86. }
  87. const std::string CARDINAL_VERSION = "22.12";
  88. START_NAMESPACE_DISTRHO
  89. // -----------------------------------------------------------------------------------------------------------
  90. #ifndef HEADLESS
  91. void handleHostParameterDrag(const CardinalPluginContext* pcontext, uint index, bool started)
  92. {
  93. DISTRHO_SAFE_ASSERT_RETURN(pcontext->ui != nullptr,);
  94. #ifndef CARDINAL_COMMON_DSP_ONLY
  95. if (started)
  96. {
  97. pcontext->ui->editParameter(index, true);
  98. pcontext->ui->setParameterValue(index, pcontext->parameters[index]);
  99. }
  100. else
  101. {
  102. pcontext->ui->editParameter(index, false);
  103. }
  104. #endif
  105. }
  106. #endif
  107. // --------------------------------------------------------------------------------------------------------------------
  108. #ifndef HEADLESS
  109. bool CardinalPluginContext::addIdleCallback(IdleCallback* const cb) const
  110. {
  111. #ifndef CARDINAL_COMMON_DSP_ONLY
  112. if (ui != nullptr)
  113. {
  114. ui->addIdleCallback(cb);
  115. return true;
  116. }
  117. #else
  118. // unused
  119. (void)cb;
  120. #endif
  121. return false;
  122. }
  123. void CardinalPluginContext::removeIdleCallback(IdleCallback* const cb) const
  124. {
  125. #ifndef CARDINAL_COMMON_DSP_ONLY
  126. if (ui != nullptr)
  127. ui->removeIdleCallback(cb);
  128. #else
  129. // unused
  130. (void)cb;
  131. #endif
  132. }
  133. #endif
  134. void CardinalPluginContext::writeMidiMessage(const rack::midi::Message& message, const uint8_t channel)
  135. {
  136. if (bypassed)
  137. return;
  138. const size_t size = message.bytes.size();
  139. DISTRHO_SAFE_ASSERT_RETURN(size > 0,);
  140. DISTRHO_SAFE_ASSERT_RETURN(message.frame >= 0,);
  141. MidiEvent event;
  142. event.frame = message.frame;
  143. switch (message.bytes[0] & 0xF0)
  144. {
  145. case 0x80:
  146. case 0x90:
  147. case 0xA0:
  148. case 0xB0:
  149. case 0xE0:
  150. event.size = 3;
  151. break;
  152. case 0xC0:
  153. case 0xD0:
  154. event.size = 2;
  155. break;
  156. case 0xF0:
  157. switch (message.bytes[0] & 0x0F)
  158. {
  159. case 0x0:
  160. case 0x4:
  161. case 0x5:
  162. case 0x7:
  163. case 0x9:
  164. case 0xD:
  165. // unsupported
  166. return;
  167. case 0x1:
  168. case 0x2:
  169. case 0x3:
  170. case 0xE:
  171. event.size = 3;
  172. break;
  173. case 0x6:
  174. case 0x8:
  175. case 0xA:
  176. case 0xB:
  177. case 0xC:
  178. case 0xF:
  179. event.size = 1;
  180. break;
  181. }
  182. break;
  183. default:
  184. // invalid
  185. return;
  186. }
  187. DISTRHO_SAFE_ASSERT_RETURN(size >= event.size,);
  188. std::memcpy(event.data, message.bytes.data(), event.size);
  189. if (channel != 0 && event.data[0] < 0xF0)
  190. event.data[0] |= channel & 0x0F;
  191. plugin->writeMidiEvent(event);
  192. }
  193. // -----------------------------------------------------------------------------------------------------------
  194. #ifdef CARDINAL_INIT_OSC_THREAD
  195. static void osc_error_handler(int num, const char* msg, const char* path)
  196. {
  197. d_stderr("Cardinal OSC Error: code: %i, msg: \"%s\", path: \"%s\")", num, msg, path);
  198. }
  199. static int osc_fallback_handler(const char* const path, const char* const types, lo_arg**, int, lo_message, void*)
  200. {
  201. d_stderr("Cardinal OSC unhandled message \"%s\" with types \"%s\"", path, types);
  202. return 0;
  203. }
  204. static int osc_hello_handler(const char*, const char*, lo_arg**, int, const lo_message m, void* const self)
  205. {
  206. d_debug("osc_hello_handler()");
  207. const lo_address source = lo_message_get_source(m);
  208. const lo_server server = lo_server_thread_get_server(static_cast<Initializer*>(self)->oscServerThread);
  209. lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "hello", "ok");
  210. return 0;
  211. }
  212. static int osc_load_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
  213. {
  214. d_debug("osc_load_handler()");
  215. DISTRHO_SAFE_ASSERT_RETURN(argc == 1, 0);
  216. DISTRHO_SAFE_ASSERT_RETURN(types != nullptr && types[0] == 'b', 0);
  217. const int32_t size = argv[0]->blob.size;
  218. DISTRHO_SAFE_ASSERT_RETURN(size > 4, 0);
  219. const uint8_t* const blob = (uint8_t*)(&argv[0]->blob.data);
  220. DISTRHO_SAFE_ASSERT_RETURN(blob != nullptr, 0);
  221. bool ok = false;
  222. if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->remotePluginInstance)
  223. {
  224. CardinalPluginContext* const context = plugin->context;
  225. std::vector<uint8_t> data(size);
  226. std::memcpy(data.data(), blob, size);
  227. rack::contextSet(context);
  228. rack::system::removeRecursively(context->patch->autosavePath);
  229. rack::system::createDirectories(context->patch->autosavePath);
  230. try {
  231. rack::system::unarchiveToDirectory(data, context->patch->autosavePath);
  232. context->patch->loadAutosave();
  233. ok = true;
  234. }
  235. catch (rack::Exception& e) {
  236. WARN("%s", e.what());
  237. }
  238. rack::contextSet(nullptr);
  239. }
  240. const lo_address source = lo_message_get_source(m);
  241. const lo_server server = lo_server_thread_get_server(static_cast<Initializer*>(self)->oscServerThread);
  242. lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "load", ok ? "ok" : "fail");
  243. return 0;
  244. }
  245. static int osc_param_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
  246. {
  247. d_debug("osc_param_handler()");
  248. DISTRHO_SAFE_ASSERT_RETURN(argc == 3, 0);
  249. DISTRHO_SAFE_ASSERT_RETURN(types != nullptr, 0);
  250. DISTRHO_SAFE_ASSERT_RETURN(types[0] == 'h', 0);
  251. DISTRHO_SAFE_ASSERT_RETURN(types[1] == 'i', 0);
  252. DISTRHO_SAFE_ASSERT_RETURN(types[2] == 'f', 0);
  253. if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->remotePluginInstance)
  254. {
  255. CardinalPluginContext* const context = plugin->context;
  256. const int64_t moduleId = argv[0]->h;
  257. const int paramId = argv[1]->i;
  258. const float paramValue = argv[2]->f;
  259. rack::engine::Module* const module = context->engine->getModule(moduleId);
  260. DISTRHO_SAFE_ASSERT_RETURN(module != nullptr, 0);
  261. context->engine->setParamValue(module, paramId, paramValue);
  262. }
  263. return 0;
  264. }
  265. static int osc_screenshot_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
  266. {
  267. d_debug("osc_screenshot_handler()");
  268. DISTRHO_SAFE_ASSERT_RETURN(argc == 1, 0);
  269. DISTRHO_SAFE_ASSERT_RETURN(types != nullptr && types[0] == 'b', 0);
  270. const int32_t size = argv[0]->blob.size;
  271. DISTRHO_SAFE_ASSERT_RETURN(size > 4, 0);
  272. const uint8_t* const blob = (uint8_t*)(&argv[0]->blob.data);
  273. DISTRHO_SAFE_ASSERT_RETURN(blob != nullptr, 0);
  274. bool ok = false;
  275. if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->remotePluginInstance)
  276. {
  277. if (char* const screenshot = String::asBase64(blob, size).getAndReleaseBuffer())
  278. {
  279. ok = plugin->updateStateValue("screenshot", screenshot);
  280. std::free(screenshot);
  281. }
  282. }
  283. const lo_address source = lo_message_get_source(m);
  284. const lo_server server = lo_server_thread_get_server(static_cast<Initializer*>(self)->oscServerThread);
  285. lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "screenshot", ok ? "ok" : "fail");
  286. return 0;
  287. }
  288. #endif
  289. Initializer::Initializer(const CardinalBasePlugin* const plugin, const CardinalBaseUI* const ui)
  290. {
  291. using namespace rack;
  292. #ifdef DISTRHO_OS_WASM
  293. settings::allowCursorLock = true;
  294. #else
  295. settings::allowCursorLock = false;
  296. #endif
  297. settings::autoCheckUpdates = false;
  298. settings::autosaveInterval = 0;
  299. settings::devMode = true;
  300. settings::isPlugin = true;
  301. settings::skipLoadOnLaunch = true;
  302. settings::showTipsOnLaunch = false;
  303. settings::windowPos = math::Vec(0, 0);
  304. #ifdef HEADLESS_BEHAVIOUR
  305. settings::headless = true;
  306. #endif
  307. // copied from https://community.vcvrack.com/t/16-colour-cable-palette/15951
  308. settings::cableColors = {
  309. color::fromHexString("#ff5252"),
  310. color::fromHexString("#ff9352"),
  311. color::fromHexString("#ffd452"),
  312. color::fromHexString("#e8ff52"),
  313. color::fromHexString("#a8ff52"),
  314. color::fromHexString("#67ff52"),
  315. color::fromHexString("#52ff7d"),
  316. color::fromHexString("#52ffbe"),
  317. color::fromHexString("#52ffff"),
  318. color::fromHexString("#52beff"),
  319. color::fromHexString("#527dff"),
  320. color::fromHexString("#6752ff"),
  321. color::fromHexString("#a852ff"),
  322. color::fromHexString("#e952ff"),
  323. color::fromHexString("#ff52d4"),
  324. color::fromHexString("#ff5293"),
  325. };
  326. system::init();
  327. logger::init();
  328. random::init();
  329. ui::init();
  330. if (asset::systemDir.empty())
  331. {
  332. if (const char* const bundlePath = (plugin != nullptr ? plugin->getBundlePath() :
  333. #if DISTRHO_PLUGIN_HAS_UI
  334. ui != nullptr ? ui->getBundlePath() :
  335. #endif
  336. nullptr))
  337. {
  338. if (const char* const resourcePath = getResourcePath(bundlePath))
  339. {
  340. asset::systemDir = resourcePath;
  341. asset::bundlePath = system::join(asset::systemDir, "PluginManifests");
  342. }
  343. }
  344. if (asset::systemDir.empty() || ! system::exists(asset::systemDir) || ! system::exists(asset::bundlePath))
  345. {
  346. #ifdef CARDINAL_PLUGIN_SOURCE_DIR
  347. // Make system dir point to source code location as fallback
  348. asset::systemDir = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR "Rack";
  349. asset::bundlePath.clear();
  350. // If source code dir does not exist use install target prefix as system dir
  351. if (!system::exists(system::join(asset::systemDir, "res")))
  352. #endif
  353. {
  354. #if defined(DISTRHO_OS_WASM)
  355. asset::systemDir = "/resources";
  356. #elif defined(ARCH_MAC)
  357. asset::systemDir = "/Library/Application Support/Cardinal";
  358. #elif defined(ARCH_WIN)
  359. const std::string commonprogfiles = getSpecialPath(kSpecialPathCommonProgramFiles);
  360. if (! commonprogfiles.empty())
  361. asset::systemDir = system::join(commonprogfiles, "Cardinal");
  362. #else
  363. asset::systemDir = CARDINAL_PLUGIN_PREFIX "/share/cardinal";
  364. #endif
  365. asset::bundlePath = system::join(asset::systemDir, "PluginManifests");
  366. }
  367. }
  368. asset::userDir = asset::systemDir;
  369. }
  370. const std::string patchesPath = asset::patchesPath();
  371. #ifdef DISTRHO_OS_WASM
  372. templatePath = system::join(patchesPath, CARDINAL_WASM_WELCOME_TEMPLATE_FILENAME);
  373. #else
  374. templatePath = system::join(patchesPath, CARDINAL_TEMPLATE_NAME);
  375. #endif
  376. factoryTemplatePath = system::join(patchesPath, CARDINAL_TEMPLATE_NAME);
  377. // Log environment
  378. INFO("%s %s %s, compatible with Rack version %s", APP_NAME.c_str(), APP_EDITION.c_str(), CARDINAL_VERSION.c_str(), APP_VERSION.c_str());
  379. INFO("%s", system::getOperatingSystemInfo().c_str());
  380. INFO("Binary filename: %s", getBinaryFilename());
  381. if (plugin != nullptr) {
  382. INFO("Bundle path: %s", plugin->getBundlePath());
  383. #if DISTRHO_PLUGIN_HAS_UI
  384. } else if (ui != nullptr) {
  385. INFO("Bundle path: %s", ui->getBundlePath());
  386. #endif
  387. }
  388. INFO("System directory: %s", asset::systemDir.c_str());
  389. INFO("User directory: %s", asset::userDir.c_str());
  390. INFO("Template patch: %s", templatePath.c_str());
  391. INFO("System template patch: %s", factoryTemplatePath.c_str());
  392. // Report to user if something is wrong with the installation
  393. if (asset::systemDir.empty())
  394. {
  395. d_stderr2("Failed to locate Cardinal plugin bundle.\n"
  396. "Install Cardinal with its bundle folder intact and try again.");
  397. }
  398. else if (! system::exists(asset::systemDir))
  399. {
  400. d_stderr2("System directory \"%s\" does not exist.\n"
  401. "Make sure Cardinal was downloaded and installed correctly.", asset::systemDir.c_str());
  402. }
  403. INFO("Initializing plugins");
  404. plugin::initStaticPlugins();
  405. INFO("Initializing plugin browser DB");
  406. app::browserInit();
  407. #ifdef CARDINAL_INIT_OSC_THREAD
  408. INFO("Initializing OSC Remote control");
  409. const char* port;
  410. if (const char* const portEnv = std::getenv("CARDINAL_REMOTE_HOST_PORT"))
  411. port = portEnv;
  412. else
  413. port = CARDINAL_DEFAULT_REMOTE_HOST_PORT;
  414. oscServerThread = lo_server_thread_new_with_proto(port, LO_UDP, osc_error_handler);
  415. DISTRHO_SAFE_ASSERT_RETURN(oscServerThread != nullptr,);
  416. lo_server_thread_add_method(oscServerThread, "/hello", "", osc_hello_handler, this);
  417. lo_server_thread_add_method(oscServerThread, "/load", "b", osc_load_handler, this);
  418. lo_server_thread_add_method(oscServerThread, "/param", "hif", osc_param_handler, this);
  419. lo_server_thread_add_method(oscServerThread, "/screenshot", "b", osc_screenshot_handler, this);
  420. lo_server_thread_add_method(oscServerThread, nullptr, nullptr, osc_fallback_handler, nullptr);
  421. lo_server_thread_start(oscServerThread);
  422. #else
  423. INFO("OSC Remote control is not enabled in this build");
  424. #endif
  425. }
  426. Initializer::~Initializer()
  427. {
  428. using namespace rack;
  429. #ifdef CARDINAL_INIT_OSC_THREAD
  430. if (oscServerThread != nullptr)
  431. {
  432. lo_server_thread_stop(oscServerThread);
  433. lo_server_thread_del_method(oscServerThread, nullptr, nullptr);
  434. lo_server_thread_free(oscServerThread);
  435. oscServerThread = nullptr;
  436. }
  437. #endif
  438. INFO("Clearing asset paths");
  439. asset::bundlePath.clear();
  440. asset::systemDir.clear();
  441. asset::userDir.clear();
  442. INFO("Destroying plugins");
  443. plugin::destroyStaticPlugins();
  444. INFO("Destroying colourized assets");
  445. asset::destroy();
  446. INFO("Destroying settings");
  447. settings::destroy();
  448. INFO("Destroying logger");
  449. logger::destroy();
  450. }
  451. // --------------------------------------------------------------------------------------------------------------------
  452. END_NAMESPACE_DISTRHO
  453. // --------------------------------------------------------------------------------------------------------------------
  454. namespace rack {
  455. bool isMini()
  456. {
  457. #if CARDINAL_VARIANT_MINI
  458. return true;
  459. #else
  460. return false;
  461. #endif
  462. }
  463. bool isStandalone()
  464. {
  465. return std::strstr(getPluginFormatName(), "Standalone") != nullptr;
  466. }
  467. #ifdef ARCH_WIN
  468. std::string getSpecialPath(const SpecialPath type)
  469. {
  470. int csidl;
  471. switch (type)
  472. {
  473. case kSpecialPathUserProfile:
  474. csidl = CSIDL_PROFILE;
  475. break;
  476. case kSpecialPathCommonProgramFiles:
  477. csidl = CSIDL_PROGRAM_FILES_COMMON;
  478. break;
  479. case kSpecialPathProgramFiles:
  480. csidl = CSIDL_PROGRAM_FILES;
  481. break;
  482. case kSpecialPathAppData:
  483. csidl = CSIDL_APPDATA;
  484. break;
  485. default:
  486. return {};
  487. }
  488. WCHAR path[MAX_PATH + 256];
  489. if (SHGetSpecialFolderPathW(nullptr, path, csidl, FALSE))
  490. return string::UTF16toUTF8(path);
  491. return {};
  492. }
  493. #endif
  494. #ifdef DISTRHO_OS_WASM
  495. char* patchFromURL = nullptr;
  496. char* patchRemoteURL = nullptr;
  497. char* patchStorageSlug = nullptr;
  498. #endif
  499. std::string homeDir()
  500. {
  501. # ifdef ARCH_WIN
  502. return getSpecialPath(kSpecialPathUserProfile);
  503. # else
  504. if (const char* const home = getenv("HOME"))
  505. return home;
  506. if (struct passwd* const pwd = getpwuid(getuid()))
  507. return pwd->pw_dir;
  508. # endif
  509. return {};
  510. }
  511. } // namespace rack
  512. // --------------------------------------------------------------------------------------------------------------------
  513. namespace patchUtils
  514. {
  515. using namespace rack;
  516. #ifndef HEADLESS_BEHAVIOUR
  517. static void promptClear(const char* const message, const std::function<void()> action)
  518. {
  519. if (APP->history->isSaved() || APP->scene->rack->hasModules())
  520. return action();
  521. asyncDialog::create(message, action);
  522. }
  523. #endif
  524. void loadDialog()
  525. {
  526. #ifndef HEADLESS_BEHAVIOUR
  527. promptClear("The current patch is unsaved. Clear it and open a new patch?", []() {
  528. std::string dir;
  529. if (! APP->patch->path.empty())
  530. dir = system::getDirectory(APP->patch->path);
  531. else
  532. dir = homeDir();
  533. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  534. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
  535. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
  536. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  537. DISTRHO_NAMESPACE::FileBrowserOptions opts;
  538. opts.saving = ui->saving = false;
  539. opts.startDir = dir.c_str();
  540. opts.title = "Open patch";
  541. ui->openFileBrowser(opts);
  542. });
  543. #endif
  544. }
  545. void loadPathDialog(const std::string& path, const bool asTemplate)
  546. {
  547. #ifndef HEADLESS_BEHAVIOUR
  548. promptClear("The current patch is unsaved. Clear it and open the new patch?", [path, asTemplate]() {
  549. APP->patch->loadAction(path);
  550. if (asTemplate)
  551. {
  552. APP->patch->path = "";
  553. APP->history->setSaved();
  554. }
  555. if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
  556. if (remoteDetails->autoDeploy)
  557. remoteUtils::sendFullPatchToRemote(remoteDetails);
  558. });
  559. #endif
  560. }
  561. void loadSelectionDialog()
  562. {
  563. app::RackWidget* const w = APP->scene->rack;
  564. std::string selectionDir = asset::user("selections");
  565. system::createDirectories(selectionDir);
  566. async_dialog_filebrowser(false, nullptr, selectionDir.c_str(), "Import selection", [w](char* pathC) {
  567. if (!pathC) {
  568. // No path selected
  569. return;
  570. }
  571. try {
  572. w->loadSelection(pathC);
  573. }
  574. catch (Exception& e) {
  575. async_dialog_message(e.what());
  576. }
  577. std::free(pathC);
  578. if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
  579. if (remoteDetails->autoDeploy)
  580. remoteUtils::sendFullPatchToRemote(remoteDetails);
  581. });
  582. }
  583. void loadTemplateDialog()
  584. {
  585. #ifndef HEADLESS_BEHAVIOUR
  586. promptClear("The current patch is unsaved. Clear it and start a new patch?", []() {
  587. APP->patch->loadTemplate();
  588. if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
  589. if (remoteDetails->autoDeploy)
  590. remoteUtils::sendFullPatchToRemote(remoteDetails);
  591. });
  592. #endif
  593. }
  594. void revertDialog()
  595. {
  596. #ifndef HEADLESS_BEHAVIOUR
  597. if (APP->patch->path.empty())
  598. return;
  599. promptClear("Revert patch to the last saved state?", []{
  600. APP->patch->loadAction(APP->patch->path);
  601. if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
  602. if (remoteDetails->autoDeploy)
  603. remoteUtils::sendFullPatchToRemote(remoteDetails);
  604. });
  605. #endif
  606. }
  607. void saveDialog(const std::string& path)
  608. {
  609. #ifndef HEADLESS_BEHAVIOUR
  610. if (path.empty()) {
  611. return;
  612. }
  613. // Note: If save() fails below, this should probably be reset. But we need it so toJson() doesn't set the "unsaved" property.
  614. APP->history->setSaved();
  615. try {
  616. APP->patch->save(path);
  617. }
  618. catch (Exception& e) {
  619. asyncDialog::create(string::f("Could not save patch: %s", e.what()).c_str());
  620. return;
  621. }
  622. #endif
  623. }
  624. #ifndef HEADLESS_BEHAVIOUR
  625. static void saveAsDialog(const bool uncompressed)
  626. {
  627. std::string dir;
  628. if (! APP->patch->path.empty())
  629. dir = system::getDirectory(APP->patch->path);
  630. else
  631. dir = homeDir();
  632. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  633. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
  634. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
  635. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  636. DISTRHO_NAMESPACE::FileBrowserOptions opts;
  637. opts.saving = ui->saving = true;
  638. opts.defaultName = "patch.vcv";
  639. opts.startDir = dir.c_str();
  640. opts.title = "Save patch";
  641. ui->savingUncompressed = uncompressed;
  642. ui->openFileBrowser(opts);
  643. }
  644. #endif
  645. void saveAsDialog()
  646. {
  647. #ifndef HEADLESS_BEHAVIOUR
  648. saveAsDialog(false);
  649. #endif
  650. }
  651. void saveAsDialogUncompressed()
  652. {
  653. #ifndef HEADLESS_BEHAVIOUR
  654. saveAsDialog(true);
  655. #endif
  656. }
  657. void openBrowser(const std::string& url)
  658. {
  659. #ifdef DISTRHO_OS_WASM
  660. EM_ASM({
  661. window.open(UTF8ToString($0), '_blank');
  662. }, url.c_str());
  663. #else
  664. system::openBrowser(url);
  665. #endif
  666. }
  667. }
  668. // --------------------------------------------------------------------------------------------------------------------
  669. void async_dialog_filebrowser(const bool saving,
  670. const char* const defaultName,
  671. const char* const startDir,
  672. const char* const title,
  673. const std::function<void(char* path)> action)
  674. {
  675. #ifndef HEADLESS_BEHAVIOUR
  676. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  677. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
  678. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
  679. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  680. // only 1 dialog possible at a time
  681. DISTRHO_SAFE_ASSERT_RETURN(ui->filebrowserhandle == nullptr,);
  682. DISTRHO_NAMESPACE::FileBrowserOptions opts;
  683. opts.saving = saving;
  684. opts.defaultName = defaultName;
  685. opts.startDir = startDir;
  686. opts.title = title;
  687. ui->filebrowseraction = action;
  688. ui->filebrowserhandle = fileBrowserCreate(true, pcontext->nativeWindowId, pcontext->window->pixelRatio, opts);
  689. #endif
  690. }
  691. void async_dialog_message(const char* const message)
  692. {
  693. #ifndef HEADLESS_BEHAVIOUR
  694. asyncDialog::create(message);
  695. #endif
  696. }
  697. void async_dialog_message(const char* const message, const std::function<void()> action)
  698. {
  699. #ifndef HEADLESS_BEHAVIOUR
  700. asyncDialog::create(message, action);
  701. #endif
  702. }
  703. void async_dialog_text_input(const char* const message, const char* const text,
  704. const std::function<void(char* newText)> action)
  705. {
  706. #ifndef HEADLESS_BEHAVIOUR
  707. asyncDialog::textInput(message, text, action);
  708. #endif
  709. }