1305 lines
37 KiB
C++
1305 lines
37 KiB
C++
/*
|
|
* DISTRHO Cardinal Plugin
|
|
* Copyright (C) 2021-2024 Filipe Coelho <falktx@falktx.com>
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
/**
|
|
* This file is partially based on VCVRack's patch.cpp
|
|
* Copyright (C) 2016-2021 VCV.
|
|
*
|
|
* This program is free software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 3 of
|
|
* the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include "CardinalCommon.hpp"
|
|
|
|
#include "AsyncDialog.hpp"
|
|
#include "CardinalPluginContext.hpp"
|
|
#include "DistrhoPluginUtils.hpp"
|
|
|
|
#include <asset.hpp>
|
|
#include <context.hpp>
|
|
#include <history.hpp>
|
|
#include <patch.hpp>
|
|
#include <settings.hpp>
|
|
#include <string.hpp>
|
|
#include <system.hpp>
|
|
#include <app/Browser.hpp>
|
|
#include <app/Scene.hpp>
|
|
#include <engine/Engine.hpp>
|
|
#include <window/Window.hpp>
|
|
|
|
#ifndef DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
# error wrong build
|
|
#endif
|
|
|
|
#if (defined(STATIC_BUILD) && !defined(__MOD_DEVICES__)) || CARDINAL_VARIANT_MINI
|
|
# undef CARDINAL_INIT_OSC_THREAD
|
|
#endif
|
|
|
|
#ifdef NDEBUG
|
|
# undef DEBUG
|
|
#endif
|
|
|
|
// for finding special paths
|
|
#ifdef ARCH_WIN
|
|
# include <shlobj.h>
|
|
#else
|
|
# include <pwd.h>
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef ARCH_LIN
|
|
# include <fstream>
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBLO
|
|
# include <lo/lo.h>
|
|
#endif
|
|
|
|
#ifdef DISTRHO_OS_WASM
|
|
# include <emscripten/emscripten.h>
|
|
#endif
|
|
|
|
#if defined(CARDINAL_COMMON_DSP_ONLY) || defined(HEADLESS)
|
|
# define HEADLESS_BEHAVIOUR
|
|
#endif
|
|
|
|
#if CARDINAL_VARIANT_FX
|
|
# define CARDINAL_VARIANT_NAME "fx"
|
|
#elif CARDINAL_VARIANT_MINI
|
|
# define CARDINAL_VARIANT_NAME "mini"
|
|
#elif CARDINAL_VARIANT_NATIVE
|
|
# define CARDINAL_VARIANT_NAME "native"
|
|
#elif CARDINAL_VARIANT_SYNTH
|
|
# define CARDINAL_VARIANT_NAME "synth"
|
|
#else
|
|
# define CARDINAL_VARIANT_NAME "main"
|
|
#endif
|
|
|
|
#ifdef DISTRHO_OS_WASM
|
|
# if CARDINAL_VARIANT_MINI
|
|
# define CARDINAL_WASM_WELCOME_TEMPLATE_FILENAME "welcome-wasm-mini"
|
|
# else
|
|
# define CARDINAL_WASM_WELCOME_TEMPLATE_FILENAME "welcome-wasm"
|
|
# endif
|
|
#endif
|
|
|
|
namespace rack {
|
|
namespace asset {
|
|
std::string patchesPath();
|
|
void destroy();
|
|
}
|
|
namespace plugin {
|
|
void initStaticPlugins();
|
|
void destroyStaticPlugins();
|
|
}
|
|
}
|
|
|
|
const std::string CARDINAL_VERSION = "24.09";
|
|
|
|
// -----------------------------------------------------------------------------------------------------------
|
|
|
|
#ifndef HEADLESS
|
|
void handleHostParameterDrag(const CardinalPluginContext* pcontext, uint index, bool started)
|
|
{
|
|
DISTRHO_SAFE_ASSERT_RETURN(pcontext->ui != nullptr,);
|
|
|
|
#ifndef CARDINAL_COMMON_DSP_ONLY
|
|
if (started)
|
|
{
|
|
pcontext->ui->editParameter(index, true);
|
|
pcontext->ui->setParameterValue(index, pcontext->parameters[index]);
|
|
}
|
|
else
|
|
{
|
|
pcontext->ui->editParameter(index, false);
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
CardinalPluginContext::CardinalPluginContext(Plugin* const p)
|
|
: bufferSize(p != nullptr ? p->getBufferSize() : 0),
|
|
processCounter(0),
|
|
sampleRate(p != nullptr ? p->getSampleRate() : 0.0),
|
|
#if CARDINAL_VARIANT_MAIN
|
|
variant(kCardinalVariantMain),
|
|
#elif CARDINAL_VARIANT_MINI
|
|
variant(kCardinalVariantMini),
|
|
#elif CARDINAL_VARIANT_FX
|
|
variant(kCardinalVariantFX),
|
|
#elif CARDINAL_VARIANT_NATIVE
|
|
variant(kCardinalVariantNative),
|
|
#elif CARDINAL_VARIANT_SYNTH
|
|
variant(kCardinalVariantSynth),
|
|
#else
|
|
#error cardinal variant not set
|
|
#endif
|
|
bypassed(false),
|
|
playing(false),
|
|
reset(false),
|
|
bbtValid(false),
|
|
bar(1),
|
|
beat(1),
|
|
beatsPerBar(4),
|
|
beatType(4),
|
|
frame(0),
|
|
barStartTick(0.0),
|
|
beatsPerMinute(120.0),
|
|
tick(0.0),
|
|
tickClock(0.0),
|
|
ticksPerBeat(0.0),
|
|
ticksPerClock(0.0),
|
|
ticksPerFrame(0.0),
|
|
nativeWindowId(0),
|
|
dataIns(nullptr),
|
|
dataOuts(nullptr),
|
|
midiEvents(nullptr),
|
|
midiEventCount(0),
|
|
plugin(p),
|
|
tlw(nullptr),
|
|
ui(nullptr)
|
|
{
|
|
std::memset(parameters, 0, sizeof(parameters));
|
|
}
|
|
|
|
bool CardinalPluginContext::addIdleCallback(IdleCallback* const cb) const
|
|
{
|
|
#if !(defined(HEADLESS) || defined(CARDINAL_COMMON_DSP_ONLY))
|
|
if (ui != nullptr)
|
|
{
|
|
ui->addIdleCallback(cb);
|
|
return true;
|
|
}
|
|
#else
|
|
// unused
|
|
(void)cb;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
void CardinalPluginContext::removeIdleCallback(IdleCallback* const cb) const
|
|
{
|
|
#if !(defined(HEADLESS) || defined(CARDINAL_COMMON_DSP_ONLY))
|
|
if (ui != nullptr)
|
|
ui->removeIdleCallback(cb);
|
|
#else
|
|
// unused
|
|
(void)cb;
|
|
#endif
|
|
}
|
|
|
|
void CardinalPluginContext::writeMidiMessage(const rack::midi::Message& message, const uint8_t channel)
|
|
{
|
|
if (bypassed)
|
|
return;
|
|
|
|
const size_t size = message.bytes.size();
|
|
DISTRHO_SAFE_ASSERT_RETURN(size > 0,);
|
|
DISTRHO_SAFE_ASSERT_RETURN(message.frame >= 0,);
|
|
|
|
MidiEvent event;
|
|
event.frame = message.frame;
|
|
|
|
switch (message.bytes[0] & 0xF0)
|
|
{
|
|
case 0x80:
|
|
case 0x90:
|
|
case 0xA0:
|
|
case 0xB0:
|
|
case 0xE0:
|
|
event.size = 3;
|
|
break;
|
|
case 0xC0:
|
|
case 0xD0:
|
|
event.size = 2;
|
|
break;
|
|
case 0xF0:
|
|
switch (message.bytes[0] & 0x0F)
|
|
{
|
|
case 0x0:
|
|
case 0x4:
|
|
case 0x5:
|
|
case 0x7:
|
|
case 0x9:
|
|
case 0xD:
|
|
// unsupported
|
|
return;
|
|
case 0x1:
|
|
case 0x2:
|
|
case 0x3:
|
|
case 0xE:
|
|
event.size = 3;
|
|
break;
|
|
case 0x6:
|
|
case 0x8:
|
|
case 0xA:
|
|
case 0xB:
|
|
case 0xC:
|
|
case 0xF:
|
|
event.size = 1;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
// invalid
|
|
return;
|
|
}
|
|
|
|
DISTRHO_SAFE_ASSERT_RETURN(size >= event.size,);
|
|
|
|
std::memcpy(event.data, message.bytes.data(), event.size);
|
|
|
|
if (channel != 0 && event.data[0] < 0xF0)
|
|
event.data[0] |= channel & 0x0F;
|
|
|
|
plugin->writeMidiEvent(event);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------------------------
|
|
|
|
namespace rack {
|
|
namespace midi {
|
|
|
|
struct InputQueue::Internal {
|
|
CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
|
|
const CardinalDISTRHO::MidiEvent* midiEvents = nullptr;
|
|
uint32_t midiEventsLeft = 0;
|
|
uint32_t lastProcessCounter = 0;
|
|
int64_t lastBlockFrame = 0;
|
|
};
|
|
|
|
InputQueue::InputQueue() {
|
|
internal = new Internal;
|
|
}
|
|
|
|
InputQueue::~InputQueue() {
|
|
delete internal;
|
|
}
|
|
|
|
bool InputQueue::tryPop(Message* const messageOut, int64_t maxFrame)
|
|
{
|
|
const uint32_t processCounter = internal->pcontext->processCounter;
|
|
const bool processCounterChanged = internal->lastProcessCounter != processCounter;
|
|
|
|
if (processCounterChanged)
|
|
{
|
|
internal->lastBlockFrame = internal->pcontext->engine->getBlockFrame();
|
|
internal->lastProcessCounter = processCounter;
|
|
|
|
internal->midiEvents = internal->pcontext->midiEvents;
|
|
internal->midiEventsLeft = internal->pcontext->midiEventCount;
|
|
}
|
|
|
|
if (internal->midiEventsLeft == 0 || maxFrame < internal->lastBlockFrame)
|
|
return false;
|
|
|
|
const uint32_t frame = maxFrame - internal->lastBlockFrame;
|
|
|
|
if (frame > internal->midiEvents->frame)
|
|
return false;
|
|
|
|
const CardinalDISTRHO::MidiEvent& midiEvent(*internal->midiEvents);
|
|
|
|
const uint8_t* data;
|
|
if (midiEvent.size > CardinalDISTRHO::MidiEvent::kDataSize)
|
|
{
|
|
data = midiEvent.dataExt;
|
|
messageOut->bytes.resize(midiEvent.size);
|
|
}
|
|
else
|
|
{
|
|
data = midiEvent.data;
|
|
}
|
|
|
|
messageOut->frame = frame;
|
|
std::memcpy(messageOut->bytes.data(), data, midiEvent.size);
|
|
|
|
++internal->midiEvents;
|
|
--internal->midiEventsLeft;
|
|
return true;
|
|
}
|
|
|
|
json_t* InputQueue::toJson() const
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
void InputQueue::fromJson(json_t* rootJ)
|
|
{
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------------------------
|
|
|
|
START_NAMESPACE_DISTRHO
|
|
|
|
// -----------------------------------------------------------------------------------------------------------
|
|
|
|
#ifdef HAVE_LIBLO
|
|
static void osc_error_handler(int num, const char* msg, const char* path)
|
|
{
|
|
d_stderr("Cardinal OSC Error: code: %i, msg: \"%s\", path: \"%s\")", num, msg, path);
|
|
}
|
|
|
|
static int osc_fallback_handler(const char* const path, const char* const types, lo_arg**, int, lo_message, void*)
|
|
{
|
|
d_stderr("Cardinal OSC unhandled message \"%s\" with types \"%s\"", path, types);
|
|
return 0;
|
|
}
|
|
|
|
static int osc_hello_handler(const char*, const char*, lo_arg**, int, const lo_message m, void* const self)
|
|
{
|
|
d_stdout("Hello received from OSC, saying hello back to them o/");
|
|
const lo_address source = lo_message_get_source(m);
|
|
const lo_server server = static_cast<Initializer*>(self)->oscServer;
|
|
|
|
// send list of features first
|
|
#ifdef CARDINAL_INIT_OSC_THREAD
|
|
lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "features", ":screenshot:");
|
|
#else
|
|
lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "features", "");
|
|
#endif
|
|
|
|
// then finally hello reply
|
|
lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "hello", "ok");
|
|
return 0;
|
|
}
|
|
|
|
static int osc_load_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
|
|
{
|
|
d_debug("osc_load_handler()");
|
|
DISTRHO_SAFE_ASSERT_RETURN(argc == 1, 0);
|
|
DISTRHO_SAFE_ASSERT_RETURN(types != nullptr && types[0] == 'b', 0);
|
|
|
|
const int32_t size = argv[0]->blob.size;
|
|
DISTRHO_SAFE_ASSERT_RETURN(size > 4, 0);
|
|
|
|
const uint8_t* const blob = (uint8_t*)(&argv[0]->blob.data);
|
|
DISTRHO_SAFE_ASSERT_RETURN(blob != nullptr, 0);
|
|
|
|
bool ok = false;
|
|
|
|
if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->remotePluginInstance)
|
|
{
|
|
CardinalPluginContext* const context = plugin->context;
|
|
std::vector<uint8_t> data(size);
|
|
std::memcpy(data.data(), blob, size);
|
|
|
|
#ifdef CARDINAL_INIT_OSC_THREAD
|
|
rack::contextSet(context);
|
|
#endif
|
|
|
|
rack::system::removeRecursively(context->patch->autosavePath);
|
|
rack::system::createDirectories(context->patch->autosavePath);
|
|
try {
|
|
rack::system::unarchiveToDirectory(data, context->patch->autosavePath);
|
|
context->patch->loadAutosave();
|
|
ok = true;
|
|
}
|
|
catch (rack::Exception& e) {
|
|
WARN("%s", e.what());
|
|
}
|
|
|
|
#ifdef CARDINAL_INIT_OSC_THREAD
|
|
rack::contextSet(nullptr);
|
|
#endif
|
|
}
|
|
|
|
const lo_address source = lo_message_get_source(m);
|
|
const lo_server server = static_cast<Initializer*>(self)->oscServer;
|
|
lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "load", ok ? "ok" : "fail");
|
|
return 0;
|
|
}
|
|
|
|
static int osc_param_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
|
|
{
|
|
d_debug("osc_param_handler()");
|
|
DISTRHO_SAFE_ASSERT_RETURN(argc == 3, 0);
|
|
DISTRHO_SAFE_ASSERT_RETURN(types != nullptr, 0);
|
|
DISTRHO_SAFE_ASSERT_RETURN(types[0] == 'h', 0);
|
|
DISTRHO_SAFE_ASSERT_RETURN(types[1] == 'i', 0);
|
|
DISTRHO_SAFE_ASSERT_RETURN(types[2] == 'f', 0);
|
|
|
|
if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->remotePluginInstance)
|
|
{
|
|
CardinalPluginContext* const context = plugin->context;
|
|
|
|
const int64_t moduleId = argv[0]->h;
|
|
const int paramId = argv[1]->i;
|
|
const float paramValue = argv[2]->f;
|
|
|
|
#ifdef CARDINAL_INIT_OSC_THREAD
|
|
rack::contextSet(context);
|
|
#endif
|
|
|
|
rack::engine::Module* const module = context->engine->getModule(moduleId);
|
|
DISTRHO_SAFE_ASSERT_RETURN(module != nullptr, 0);
|
|
|
|
context->engine->setParamValue(module, paramId, paramValue);
|
|
|
|
#ifdef CARDINAL_INIT_OSC_THREAD
|
|
rack::contextSet(nullptr);
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int osc_host_param_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
|
|
{
|
|
d_debug("osc_host_param_handler()");
|
|
DISTRHO_SAFE_ASSERT_RETURN(argc == 2, 0);
|
|
DISTRHO_SAFE_ASSERT_RETURN(types != nullptr, 0);
|
|
DISTRHO_SAFE_ASSERT_RETURN(types[0] == 'i', 0);
|
|
DISTRHO_SAFE_ASSERT_RETURN(types[1] == 'f', 0);
|
|
|
|
if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->remotePluginInstance)
|
|
{
|
|
CardinalPluginContext* const context = plugin->context;
|
|
|
|
const int paramId = argv[0]->i;
|
|
DISTRHO_SAFE_ASSERT_RETURN(paramId >= 0, 0);
|
|
|
|
const uint uparamId = static_cast<uint>(paramId);
|
|
DISTRHO_SAFE_ASSERT_UINT2_RETURN(uparamId < kModuleParameterCount, uparamId, kModuleParameterCount, 0);
|
|
|
|
const float paramValue = argv[1]->f;
|
|
|
|
context->parameters[uparamId] = paramValue;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
# ifdef CARDINAL_INIT_OSC_THREAD
|
|
static int osc_screenshot_handler(const char*, const char* types, lo_arg** argv, int argc, const lo_message m, void* const self)
|
|
{
|
|
d_debug("osc_screenshot_handler()");
|
|
DISTRHO_SAFE_ASSERT_RETURN(argc == 1, 0);
|
|
DISTRHO_SAFE_ASSERT_RETURN(types != nullptr && types[0] == 'b', 0);
|
|
|
|
const int32_t size = argv[0]->blob.size;
|
|
DISTRHO_SAFE_ASSERT_RETURN(size > 4, 0);
|
|
|
|
const uint8_t* const blob = (uint8_t*)(&argv[0]->blob.data);
|
|
DISTRHO_SAFE_ASSERT_RETURN(blob != nullptr, 0);
|
|
|
|
bool ok = false;
|
|
|
|
if (CardinalBasePlugin* const plugin = static_cast<Initializer*>(self)->remotePluginInstance)
|
|
{
|
|
if (char* const screenshot = String::asBase64(blob, size).getAndReleaseBuffer())
|
|
{
|
|
ok = plugin->updateStateValue("screenshot", screenshot);
|
|
std::free(screenshot);
|
|
}
|
|
}
|
|
|
|
const lo_address source = lo_message_get_source(m);
|
|
const lo_server server = static_cast<Initializer*>(self)->oscServer;
|
|
lo_send_from(source, server, LO_TT_IMMEDIATE, "/resp", "ss", "screenshot", ok ? "ok" : "fail");
|
|
return 0;
|
|
}
|
|
# endif
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------------------------------------------
|
|
|
|
#if defined(DISTRHO_OS_WASM) && !defined(CARDINAL_COMMON_UI_ONLY)
|
|
static void WebBrowserDataLoaded(void* const data)
|
|
{
|
|
static_cast<Initializer*>(data)->loadSettings(true);
|
|
}
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------------------------------------------
|
|
|
|
Initializer::Initializer(const CardinalBasePlugin* const plugin, const CardinalBaseUI* const ui)
|
|
{
|
|
using namespace rack;
|
|
|
|
// Cardinal default settings, potentially overriding VCV Rack ones
|
|
settings::allowCursorLock = false;
|
|
settings::tooltips = true;
|
|
settings::cableOpacity = 0.5f;
|
|
settings::cableTension = 0.75f;
|
|
settings::rackBrightness = 1.0f;
|
|
settings::haloBrightness = 0.25f;
|
|
settings::knobMode = settings::KNOB_MODE_LINEAR;
|
|
settings::knobScroll = false;
|
|
settings::knobScrollSensitivity = 0.001f;
|
|
settings::lockModules = false;
|
|
settings::browserSort = settings::BROWSER_SORT_UPDATED;
|
|
settings::browserZoom = -1.f;
|
|
settings::invertZoom = false;
|
|
settings::squeezeModules = true;
|
|
settings::preferDarkPanels = true;
|
|
settings::uiTheme = "dark";
|
|
|
|
// runtime behaviour
|
|
settings::devMode = true;
|
|
settings::isPlugin = true;
|
|
#ifdef HEADLESS_BEHAVIOUR
|
|
settings::headless = true;
|
|
#endif
|
|
|
|
// copied from https://community.vcvrack.com/t/16-colour-cable-palette/15951
|
|
settings::cableColors = {
|
|
color::fromHexString("#ff5252"),
|
|
color::fromHexString("#ff9352"),
|
|
color::fromHexString("#ffd452"),
|
|
color::fromHexString("#e8ff52"),
|
|
color::fromHexString("#a8ff52"),
|
|
color::fromHexString("#67ff52"),
|
|
color::fromHexString("#52ff7d"),
|
|
color::fromHexString("#52ffbe"),
|
|
color::fromHexString("#52ffff"),
|
|
color::fromHexString("#52beff"),
|
|
color::fromHexString("#527dff"),
|
|
color::fromHexString("#6752ff"),
|
|
color::fromHexString("#a852ff"),
|
|
color::fromHexString("#e952ff"),
|
|
color::fromHexString("#ff52d4"),
|
|
color::fromHexString("#ff5293"),
|
|
};
|
|
|
|
system::init();
|
|
logger::init();
|
|
random::init();
|
|
ui::init();
|
|
|
|
#ifdef CARDINAL_COMMON_UI_ONLY
|
|
constexpr const bool isRealInstance = true;
|
|
#else
|
|
const bool isRealInstance = !plugin->isDummyInstance();
|
|
#endif
|
|
|
|
if (asset::systemDir.empty())
|
|
{
|
|
if (const char* const bundlePath = (plugin != nullptr ? plugin->getBundlePath() :
|
|
#if DISTRHO_PLUGIN_HAS_UI
|
|
ui != nullptr ? ui->getBundlePath() :
|
|
#endif
|
|
nullptr))
|
|
{
|
|
if (const char* const resourcePath = getResourcePath(bundlePath))
|
|
{
|
|
asset::systemDir = resourcePath;
|
|
asset::bundlePath = system::join(asset::systemDir, "PluginManifests");
|
|
}
|
|
}
|
|
|
|
if (asset::systemDir.empty() || ! system::exists(asset::systemDir) || ! system::exists(asset::bundlePath))
|
|
{
|
|
#ifdef CARDINAL_PLUGIN_SOURCE_DIR
|
|
// Make system dir point to source code location as fallback
|
|
asset::systemDir = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR "Rack";
|
|
asset::bundlePath.clear();
|
|
|
|
// If source code dir does not exist use install target prefix as system dir
|
|
if (!system::exists(system::join(asset::systemDir, "res")))
|
|
#endif
|
|
{
|
|
#if defined(DISTRHO_OS_WASM)
|
|
asset::systemDir = "/resources";
|
|
#elif defined(ARCH_MAC)
|
|
asset::systemDir = "/Library/Application Support/Cardinal";
|
|
#elif defined(ARCH_WIN)
|
|
asset::systemDir = system::join(getSpecialPath(kSpecialPathCommonProgramFiles), "Cardinal");
|
|
#else
|
|
asset::systemDir = CARDINAL_PLUGIN_PREFIX "/share/cardinal";
|
|
#endif
|
|
asset::bundlePath = system::join(asset::systemDir, "PluginManifests");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (asset::userDir.empty())
|
|
{
|
|
#if defined(DISTRHO_OS_WASM)
|
|
asset::userDir = "/userfiles";
|
|
#elif defined(ARCH_MAC)
|
|
asset::userDir = system::join(homeDir(), "Documents", "Cardinal");
|
|
#elif defined(ARCH_WIN)
|
|
asset::userDir = system::join(getSpecialPath(kSpecialPathMyDocuments), "Cardinal");
|
|
#else
|
|
std::string xdgConfigDir;
|
|
if (const char* const xdgEnv = getenv("XDG_CONFIG_HOME"))
|
|
xdgConfigDir = xdgEnv;
|
|
if (xdgConfigDir.empty())
|
|
xdgConfigDir = system::join(homeDir(), ".config");
|
|
|
|
const std::string xdgDirsConfigPath(system::join(xdgConfigDir, "user-dirs.dirs"));
|
|
|
|
if (system::exists(xdgDirsConfigPath))
|
|
{
|
|
std::ifstream xdgDirsConfigFile(xdgDirsConfigPath, std::ios::in|std::ios::ate);
|
|
std::string xdgDirsConfig(xdgDirsConfigFile.tellg(), 0);
|
|
|
|
xdgDirsConfigFile.seekg(0);
|
|
xdgDirsConfigFile.read(&xdgDirsConfig[0], xdgDirsConfig.size());
|
|
|
|
if (const char* const xdgDocsDir = std::strstr(xdgDirsConfig.c_str(), "XDG_DOCUMENTS_DIR=\""))
|
|
{
|
|
if (const char* const xdgDocsDirNL = std::strstr(xdgDocsDir, "\"\n"))
|
|
{
|
|
asset::userDir = std::string(xdgDocsDir + 19, xdgDocsDirNL - xdgDocsDir - 19);
|
|
|
|
if (string::startsWith(asset::userDir, "$HOME"))
|
|
asset::userDir.replace(asset::userDir.begin(), asset::userDir.begin() + 5, homeDir());
|
|
|
|
if (! system::exists(asset::userDir))
|
|
asset::userDir.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (asset::userDir.empty())
|
|
asset::userDir = system::join(homeDir(), "Documents", "Cardinal");
|
|
#endif
|
|
|
|
if (isRealInstance)
|
|
{
|
|
system::createDirectory(asset::userDir);
|
|
#if defined(DISTRHO_OS_WASM) && !defined(CARDINAL_COMMON_UI_ONLY)
|
|
EM_ASM({
|
|
Module.FS.mount(Module.IDBFS, {}, '/userfiles');
|
|
Module.FS.syncfs(true, function(err) { if (!err) { dynCall('vi', $0, [$1]) } });
|
|
}, WebBrowserDataLoaded, this);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifndef CARDINAL_COMMON_DSP_ONLY
|
|
if (asset::configDir.empty())
|
|
{
|
|
#if defined(ARCH_MAC) || defined(ARCH_WIN) || defined(DISTRHO_OS_WASM)
|
|
asset::configDir = asset::userDir;
|
|
#else
|
|
if (const char* const xdgEnv = getenv("XDG_CONFIG_HOME"))
|
|
asset::configDir = system::join(xdgEnv, "Cardinal");
|
|
else
|
|
asset::configDir = system::join(homeDir(), ".config", "Cardinal");
|
|
|
|
if (isRealInstance)
|
|
system::createDirectory(asset::configDir);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
if (settings::settingsPath.empty())
|
|
settings::settingsPath = asset::config(CARDINAL_VARIANT_NAME ".json");
|
|
|
|
templatePath = asset::user("templates/" CARDINAL_VARIANT_NAME ".vcv");
|
|
#ifdef DISTRHO_OS_WASM
|
|
factoryTemplatePath = system::join(asset::patchesPath(), CARDINAL_WASM_WELCOME_TEMPLATE_FILENAME ".vcv");
|
|
#else
|
|
factoryTemplatePath = system::join(asset::patchesPath(), "templates/" CARDINAL_VARIANT_NAME ".vcv");
|
|
#endif
|
|
|
|
// Log environment
|
|
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());
|
|
INFO("%s", system::getOperatingSystemInfo().c_str());
|
|
INFO("Binary filename: %s", getBinaryFilename());
|
|
if (plugin != nullptr) {
|
|
INFO("Bundle path: %s", plugin->getBundlePath());
|
|
#if DISTRHO_PLUGIN_HAS_UI
|
|
} else if (ui != nullptr) {
|
|
INFO("Bundle path: %s", ui->getBundlePath());
|
|
#endif
|
|
}
|
|
INFO("System directory: %s", asset::systemDir.c_str());
|
|
INFO("User directory: %s", asset::userDir.c_str());
|
|
INFO("Template patch: %s", templatePath.c_str());
|
|
INFO("System template patch: %s", factoryTemplatePath.c_str());
|
|
|
|
// Report to user if something is wrong with the installation
|
|
if (asset::systemDir.empty())
|
|
{
|
|
d_stderr2("Failed to locate Cardinal plugin bundle.\n"
|
|
"Install Cardinal with its bundle folder intact and try again.");
|
|
}
|
|
else if (! system::exists(asset::systemDir))
|
|
{
|
|
d_stderr2("System directory \"%s\" does not exist.\n"
|
|
"Make sure Cardinal was downloaded and installed correctly.", asset::systemDir.c_str());
|
|
}
|
|
|
|
INFO("Initializing plugins");
|
|
plugin::initStaticPlugins();
|
|
|
|
INFO("Initializing plugin browser DB");
|
|
app::browserInit();
|
|
|
|
loadSettings(isRealInstance);
|
|
|
|
#if defined(CARDINAL_INIT_OSC_THREAD)
|
|
INFO("Initializing OSC Remote control");
|
|
const char* port;
|
|
if (const char* const portEnv = std::getenv("CARDINAL_REMOTE_HOST_PORT"))
|
|
port = portEnv;
|
|
else
|
|
port = CARDINAL_DEFAULT_REMOTE_PORT;
|
|
startRemoteServer(port);
|
|
#elif defined(HAVE_LIBLO)
|
|
if (isStandalone()) {
|
|
INFO("OSC Remote control is available on request");
|
|
} else {
|
|
INFO("OSC Remote control is not available on plugin variants");
|
|
}
|
|
#else
|
|
INFO("OSC Remote control is not enabled in this build");
|
|
#endif
|
|
}
|
|
|
|
Initializer::~Initializer()
|
|
{
|
|
using namespace rack;
|
|
|
|
#ifdef HAVE_LIBLO
|
|
stopRemoteServer();
|
|
#endif
|
|
|
|
if (shouldSaveSettings)
|
|
{
|
|
INFO("Save settings");
|
|
settings::save();
|
|
}
|
|
|
|
INFO("Clearing asset paths");
|
|
asset::bundlePath.clear();
|
|
asset::systemDir.clear();
|
|
asset::userDir.clear();
|
|
|
|
INFO("Destroying plugins");
|
|
plugin::destroyStaticPlugins();
|
|
|
|
INFO("Destroying colourized assets");
|
|
asset::destroy();
|
|
|
|
INFO("Destroying settings");
|
|
settings::destroy();
|
|
|
|
INFO("Destroying logger");
|
|
logger::destroy();
|
|
}
|
|
|
|
void Initializer::loadSettings(const bool isRealInstance)
|
|
{
|
|
using namespace rack;
|
|
|
|
if (isRealInstance)
|
|
{
|
|
INFO("Loading settings");
|
|
settings::load();
|
|
shouldSaveSettings = true;
|
|
}
|
|
|
|
// enforce settings that do not make sense as anything else
|
|
settings::safeMode = false;
|
|
settings::token.clear();
|
|
settings::windowMaximized = false;
|
|
settings::windowPos = math::Vec(0, 0);
|
|
settings::pixelRatio = 0.0;
|
|
settings::sampleRate = 0;
|
|
settings::threadCount = 1;
|
|
settings::autosaveInterval = 0;
|
|
settings::skipLoadOnLaunch = true;
|
|
settings::autoCheckUpdates = false;
|
|
settings::showTipsOnLaunch = false;
|
|
settings::tipIndex = -1;
|
|
|
|
if (settings::uiTheme != "dark" && settings::uiTheme != "light")
|
|
{
|
|
settings::uiTheme = "dark";
|
|
rack::ui::refreshTheme();
|
|
}
|
|
|
|
// reload dark/light mode as necessary
|
|
switchDarkMode(settings::uiTheme == "dark");
|
|
}
|
|
|
|
#ifdef HAVE_LIBLO
|
|
bool Initializer::startRemoteServer(const char* const port)
|
|
{
|
|
#ifdef CARDINAL_INIT_OSC_THREAD
|
|
if (oscServerThread != nullptr)
|
|
return true;
|
|
|
|
if ((oscServerThread = lo_server_thread_new_with_proto(port, LO_UDP, osc_error_handler)) == nullptr)
|
|
return false;
|
|
|
|
oscServer = lo_server_thread_get_server(oscServerThread);
|
|
|
|
lo_server_thread_add_method(oscServerThread, "/hello", "", osc_hello_handler, this);
|
|
lo_server_thread_add_method(oscServerThread, "/host-param", "if", osc_host_param_handler, this);
|
|
lo_server_thread_add_method(oscServerThread, "/load", "b", osc_load_handler, this);
|
|
lo_server_thread_add_method(oscServerThread, "/param", "hif", osc_param_handler, this);
|
|
lo_server_thread_add_method(oscServerThread, "/screenshot", "b", osc_screenshot_handler, this);
|
|
lo_server_thread_add_method(oscServerThread, nullptr, nullptr, osc_fallback_handler, nullptr);
|
|
lo_server_thread_start(oscServerThread);
|
|
#else
|
|
if (oscServer != nullptr)
|
|
return true;
|
|
|
|
if ((oscServer = lo_server_new_with_proto(port, LO_UDP, osc_error_handler)) == nullptr)
|
|
return false;
|
|
|
|
lo_server_add_method(oscServer, "/hello", "", osc_hello_handler, this);
|
|
lo_server_add_method(oscServer, "/host-param", "if", osc_host_param_handler, this);
|
|
lo_server_add_method(oscServer, "/load", "b", osc_load_handler, this);
|
|
lo_server_add_method(oscServer, "/param", "hif", osc_param_handler, this);
|
|
lo_server_add_method(oscServer, nullptr, nullptr, osc_fallback_handler, nullptr);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void Initializer::stopRemoteServer()
|
|
{
|
|
DISTRHO_SAFE_ASSERT(remotePluginInstance == nullptr);
|
|
|
|
#ifdef CARDINAL_INIT_OSC_THREAD
|
|
if (oscServerThread != nullptr)
|
|
{
|
|
lo_server_thread_stop(oscServerThread);
|
|
lo_server_thread_del_method(oscServerThread, nullptr, nullptr);
|
|
lo_server_thread_free(oscServerThread);
|
|
oscServerThread = oscServer = nullptr;
|
|
}
|
|
#else
|
|
if (oscServer != nullptr)
|
|
{
|
|
lo_server_del_method(oscServer, nullptr, nullptr);
|
|
lo_server_free(oscServer);
|
|
oscServer = nullptr;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Initializer::stepRemoteServer()
|
|
{
|
|
DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr,);
|
|
DISTRHO_SAFE_ASSERT_RETURN(remotePluginInstance != nullptr,);
|
|
|
|
#ifndef CARDINAL_INIT_OSC_THREAD
|
|
for (;;)
|
|
{
|
|
try {
|
|
if (lo_server_recv_noblock(oscServer, 0) == 0)
|
|
break;
|
|
} DISTRHO_SAFE_EXCEPTION_CONTINUE("stepRemoteServer")
|
|
}
|
|
#endif
|
|
}
|
|
#endif // HAVE_LIBLO
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
END_NAMESPACE_DISTRHO
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
namespace rack {
|
|
|
|
bool isMini()
|
|
{
|
|
#if CARDINAL_VARIANT_MINI
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool isStandalone()
|
|
{
|
|
static const bool standalone = std::strstr(getPluginFormatName(), "Standalone") != nullptr;
|
|
return standalone;
|
|
}
|
|
|
|
#ifdef ARCH_WIN
|
|
std::string getSpecialPath(const SpecialPath type)
|
|
{
|
|
int csidl;
|
|
switch (type)
|
|
{
|
|
case kSpecialPathUserProfile:
|
|
csidl = CSIDL_PROFILE;
|
|
break;
|
|
case kSpecialPathCommonProgramFiles:
|
|
csidl = CSIDL_PROGRAM_FILES_COMMON;
|
|
break;
|
|
case kSpecialPathProgramFiles:
|
|
csidl = CSIDL_PROGRAM_FILES;
|
|
break;
|
|
case kSpecialPathAppData:
|
|
csidl = CSIDL_APPDATA;
|
|
break;
|
|
case kSpecialPathMyDocuments:
|
|
csidl = CSIDL_MYDOCUMENTS;
|
|
break;
|
|
default:
|
|
return {};
|
|
}
|
|
|
|
WCHAR path[MAX_PATH] = {};
|
|
|
|
if (SHGetFolderPathW(nullptr, csidl, nullptr, SHGFP_TYPE_CURRENT, path) == S_OK)
|
|
return string::UTF16toUTF8(path);
|
|
|
|
return {};
|
|
}
|
|
#endif
|
|
|
|
#ifdef DISTRHO_OS_WASM
|
|
char* patchFromURL = nullptr;
|
|
char* patchRemoteURL = nullptr;
|
|
char* patchStorageSlug = nullptr;
|
|
|
|
void syncfs()
|
|
{
|
|
settings::save();
|
|
|
|
#ifndef CARDINAL_COMMON_UI_ONLY
|
|
EM_ASM({
|
|
Module.FS.syncfs(false, function(){} );
|
|
});
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
std::string homeDir()
|
|
{
|
|
#ifdef ARCH_WIN
|
|
return getSpecialPath(kSpecialPathUserProfile);
|
|
#else
|
|
if (const char* const home = getenv("HOME"))
|
|
return home;
|
|
if (struct passwd* const pwd = getpwuid(getuid()))
|
|
return pwd->pw_dir;
|
|
#endif
|
|
return {};
|
|
}
|
|
|
|
} // namespace rack
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
namespace patchUtils
|
|
{
|
|
|
|
using namespace rack;
|
|
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
static void promptClear(const char* const message, const std::function<void()> action)
|
|
{
|
|
if (APP->history->isSaved() || APP->scene->rack->hasModules())
|
|
return action();
|
|
|
|
asyncDialog::create(message, action);
|
|
}
|
|
#endif
|
|
|
|
void loadDialog()
|
|
{
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
promptClear("The current patch is unsaved. Clear it and open a new patch?", []() {
|
|
std::string dir;
|
|
if (! APP->patch->path.empty())
|
|
dir = system::getDirectory(APP->patch->path);
|
|
else
|
|
dir = homeDir();
|
|
|
|
CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
|
|
DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
|
|
|
|
CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
|
|
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
|
|
|
|
DISTRHO_NAMESPACE::FileBrowserOptions opts;
|
|
opts.saving = ui->saving = false;
|
|
opts.startDir = dir.c_str();
|
|
opts.title = "Open patch";
|
|
ui->openFileBrowser(opts);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
void loadPathDialog(const std::string& path, const bool asTemplate)
|
|
{
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
promptClear("The current patch is unsaved. Clear it and open the new patch?", [path, asTemplate]() {
|
|
APP->patch->loadAction(path);
|
|
|
|
if (asTemplate)
|
|
{
|
|
APP->patch->path = "";
|
|
APP->history->setSaved();
|
|
}
|
|
|
|
#ifdef DISTRHO_OS_WASM
|
|
syncfs();
|
|
#endif
|
|
|
|
if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
|
|
if (remoteDetails->autoDeploy)
|
|
remoteUtils::sendFullPatchToRemote(remoteDetails);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
void loadSelectionDialog()
|
|
{
|
|
app::RackWidget* const w = APP->scene->rack;
|
|
|
|
std::string selectionDir = asset::user("selections");
|
|
system::createDirectories(selectionDir);
|
|
|
|
async_dialog_filebrowser(false, nullptr, selectionDir.c_str(), "Import selection", [w](char* pathC) {
|
|
if (!pathC) {
|
|
// No path selected
|
|
return;
|
|
}
|
|
|
|
try {
|
|
w->loadSelection(pathC);
|
|
}
|
|
catch (Exception& e) {
|
|
async_dialog_message(e.what());
|
|
}
|
|
|
|
std::free(pathC);
|
|
|
|
#ifdef DISTRHO_OS_WASM
|
|
syncfs();
|
|
#endif
|
|
|
|
if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
|
|
if (remoteDetails->autoDeploy)
|
|
remoteUtils::sendFullPatchToRemote(remoteDetails);
|
|
});
|
|
}
|
|
|
|
void loadTemplate(const bool factory)
|
|
{
|
|
try {
|
|
APP->patch->load(factory ? APP->patch->factoryTemplatePath : APP->patch->templatePath);
|
|
}
|
|
catch (Exception& e) {
|
|
// if user template failed, try the factory one
|
|
if (!factory)
|
|
return loadTemplate(true);
|
|
|
|
const std::string message = string::f("Could not load template patch, clearing rack: %s", e.what());
|
|
asyncDialog::create(message.c_str());
|
|
|
|
APP->patch->clear();
|
|
APP->patch->clearAutosave();
|
|
}
|
|
|
|
// load() sets the patch's original patch, but we don't want to use that.
|
|
APP->patch->path.clear();
|
|
APP->history->setSaved();
|
|
|
|
#ifdef DISTRHO_OS_WASM
|
|
syncfs();
|
|
#endif
|
|
|
|
if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
|
|
if (remoteDetails->autoDeploy)
|
|
remoteUtils::sendFullPatchToRemote(remoteDetails);
|
|
}
|
|
|
|
|
|
void loadTemplateDialog(const bool factory)
|
|
{
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
promptClear("The current patch is unsaved. Clear it and start a new patch?", [factory]() {
|
|
loadTemplate(factory);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
void revertDialog()
|
|
{
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
if (APP->patch->path.empty())
|
|
return;
|
|
promptClear("Revert patch to the last saved state?", []{
|
|
APP->patch->loadAction(APP->patch->path);
|
|
|
|
#ifdef DISTRHO_OS_WASM
|
|
syncfs();
|
|
#endif
|
|
|
|
if (remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote())
|
|
if (remoteDetails->autoDeploy)
|
|
remoteUtils::sendFullPatchToRemote(remoteDetails);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
void saveDialog(const std::string& path)
|
|
{
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
if (path.empty()) {
|
|
return;
|
|
}
|
|
|
|
// Note: If save() fails below, this should probably be reset. But we need it so toJson() doesn't set the "unsaved" property.
|
|
APP->history->setSaved();
|
|
|
|
try {
|
|
APP->patch->save(path);
|
|
}
|
|
catch (Exception& e) {
|
|
asyncDialog::create(string::f("Could not save patch: %s", e.what()).c_str());
|
|
return;
|
|
}
|
|
|
|
APP->patch->pushRecentPath(path);
|
|
|
|
#ifdef DISTRHO_OS_WASM
|
|
syncfs();
|
|
#else
|
|
rack::settings::save();
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
static void saveAsDialog(const bool uncompressed)
|
|
{
|
|
std::string dir;
|
|
if (! APP->patch->path.empty())
|
|
{
|
|
dir = system::getDirectory(APP->patch->path);
|
|
}
|
|
else
|
|
{
|
|
dir = asset::user("patches");
|
|
system::createDirectories(dir);
|
|
}
|
|
|
|
CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
|
|
DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
|
|
|
|
CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
|
|
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
|
|
|
|
DISTRHO_NAMESPACE::FileBrowserOptions opts;
|
|
opts.saving = ui->saving = true;
|
|
opts.defaultName = "patch.vcv";
|
|
opts.startDir = dir.c_str();
|
|
opts.title = "Save patch";
|
|
ui->savingUncompressed = uncompressed;
|
|
ui->openFileBrowser(opts);
|
|
}
|
|
#endif
|
|
|
|
void saveAsDialog()
|
|
{
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
saveAsDialog(false);
|
|
#endif
|
|
}
|
|
|
|
void saveAsDialogUncompressed()
|
|
{
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
saveAsDialog(true);
|
|
#endif
|
|
}
|
|
|
|
void saveTemplateDialog()
|
|
{
|
|
asyncDialog::create("Overwrite template patch?", []{
|
|
rack::system::createDirectories(system::getDirectory(APP->patch->templatePath));
|
|
|
|
try {
|
|
APP->patch->save(APP->patch->templatePath);
|
|
}
|
|
catch (Exception& e) {
|
|
asyncDialog::create(string::f("Could not save template patch: %s", e.what()).c_str());
|
|
return;
|
|
}
|
|
|
|
#ifdef DISTRHO_OS_WASM
|
|
syncfs();
|
|
#endif
|
|
});
|
|
}
|
|
|
|
void openBrowser(const std::string& url)
|
|
{
|
|
#ifdef DISTRHO_OS_WASM
|
|
EM_ASM({
|
|
window.open(UTF8ToString($0), '_blank');
|
|
}, url.c_str());
|
|
#else
|
|
system::openBrowser(url);
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
void async_dialog_filebrowser(const bool saving,
|
|
const char* const defaultName,
|
|
const char* const startDir,
|
|
const char* const title,
|
|
const std::function<void(char* path)> action)
|
|
{
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
|
|
DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
|
|
|
|
CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
|
|
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
|
|
|
|
// only 1 dialog possible at a time
|
|
DISTRHO_SAFE_ASSERT_RETURN(ui->filebrowserhandle == nullptr,);
|
|
|
|
DISTRHO_NAMESPACE::FileBrowserOptions opts;
|
|
opts.saving = saving;
|
|
opts.defaultName = defaultName;
|
|
opts.startDir = startDir;
|
|
opts.title = title;
|
|
|
|
ui->filebrowseraction = action;
|
|
ui->filebrowserhandle = fileBrowserCreate(true, pcontext->nativeWindowId, pcontext->window->pixelRatio, opts);
|
|
#endif
|
|
}
|
|
|
|
void async_dialog_message(const char* const message)
|
|
{
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
asyncDialog::create(message);
|
|
#endif
|
|
}
|
|
|
|
void async_dialog_message(const char* const message, const std::function<void()> action)
|
|
{
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
asyncDialog::create(message, action);
|
|
#endif
|
|
}
|
|
|
|
void async_dialog_text_input(const char* const message, const char* const text,
|
|
const std::function<void(char* newText)> action)
|
|
{
|
|
#ifndef HEADLESS_BEHAVIOUR
|
|
asyncDialog::textInput(message, text, action);
|
|
#endif
|
|
}
|