diff --git a/CMakeLists.txt b/CMakeLists.txt index f35deec5a0b6..3932dd15fea9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -653,6 +653,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/controllers/controlleroutputmappingtablemodel.cpp src/controllers/controlpickermenu.cpp src/controllers/legacycontrollermappingfilehandler.cpp + src/controllers/controllerruntimedata.cpp src/controllers/delegates/controldelegate.cpp src/controllers/delegates/midibytedelegate.cpp src/controllers/delegates/midichanneldelegate.cpp diff --git a/src/controllers/controller.h b/src/controllers/controller.h index 86c396e92b5d..e2d64cc76424 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -142,7 +142,6 @@ class Controller : public QObject { virtual void sendBytes(const QByteArray& data) = 0; private: // but used by ControllerManager - virtual int open() = 0; virtual int close() = 0; // Requests that the device poll if it is a polling device. Returns true diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index e8014360f20c..9af82c1c9435 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -6,6 +6,7 @@ #include #include "controllers/controllerlearningeventfilter.h" +#include "controllers/controllerruntimedata.h" #include "controllers/defs_controllers.h" #include "controllers/midi/portmidienumerator.h" #include "moc_controllermanager.cpp" @@ -89,7 +90,8 @@ ControllerManager::ControllerManager(UserSettingsPointer pConfig) // its own event loop. m_pControllerLearningEventFilter(new ControllerLearningEventFilter()), m_pollTimer(this), - m_skipPoll(false) { + m_skipPoll(false), + m_pRuntimeData(std::make_shared(this)) { qRegisterMetaType>( "std::shared_ptr"); @@ -293,6 +295,12 @@ void ControllerManager::slotSetUpDevices() { qWarning() << "There was a problem opening" << name; continue; } + VERIFY_OR_DEBUG_ASSERT(pController->getScriptEngine()) { + qWarning() << "Unable to acquire the controller engine. Has the " + "controller open successfully?"; + continue; + } + pController->getScriptEngine()->setRuntimeData(m_pRuntimeData); pController->applyMapping(); } @@ -385,6 +393,13 @@ void ControllerManager::openController(Controller* pController) { // If successfully opened the device, apply the mapping and save the // preference setting. if (result == 0) { + VERIFY_OR_DEBUG_ASSERT(pController->getScriptEngine()) { + qWarning() << "Unable to acquire the controller engine. Has the " + "controller open successfully?"; + return; + } + pController->getScriptEngine()->setRuntimeData(m_pRuntimeData); + pController->applyMapping(); // Update configuration to reflect controller is enabled. diff --git a/src/controllers/controllermanager.h b/src/controllers/controllermanager.h index 4460e0cf816f..d79990b5f5fc 100644 --- a/src/controllers/controllermanager.h +++ b/src/controllers/controllermanager.h @@ -84,4 +84,5 @@ class ControllerManager : public QObject { QSharedPointer m_pMainThreadUserMappingEnumerator; QSharedPointer m_pMainThreadSystemMappingEnumerator; bool m_skipPoll; + std::shared_ptr m_pRuntimeData; }; diff --git a/src/controllers/controllerruntimedata.cpp b/src/controllers/controllerruntimedata.cpp new file mode 100644 index 000000000000..83bc96ad66ac --- /dev/null +++ b/src/controllers/controllerruntimedata.cpp @@ -0,0 +1,3 @@ +#include + +#include "moc_controllerruntimedata.cpp" diff --git a/src/controllers/controllerruntimedata.h b/src/controllers/controllerruntimedata.h new file mode 100644 index 000000000000..8977a98c50b8 --- /dev/null +++ b/src/controllers/controllerruntimedata.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "util/assert.h" + +/// ControllerRuntimeData is a wrapper that allows controllers script runtimes to +/// share arbitrary data via a the JavaScript interface. It doesn't enforce any +/// type consistency and it is the script responsibility to use this data in a +/// considate way, as well as expecting that others won't do so. +class ControllerRuntimeData : public QObject { + Q_OBJECT + public: + ControllerRuntimeData(QObject* parent) + : QObject(parent), m_value() { + } + + const QVariant& get() const { + return m_value; + } + + public slots: + void set(const QVariant& value) { + m_value = value; + emit updated(m_value); + } + + signals: + void updated(const QVariant& value); + + private: + QVariant m_value; +}; diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 2d215ccb13d4..e9fc4db21e42 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -62,6 +62,8 @@ class ControllerScriptEngineBase : public QObject { std::shared_ptr pTrackCollectionManager); protected: + std::shared_ptr m_pRuntimeData; + virtual void shutdown(); void scriptErrorDialog(const QString& detailedError, const QString& key, bool bFatal = false); diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp index 33a60e3cae05..934b787ef2a8 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp @@ -2,6 +2,7 @@ #include "control/controlobject.h" #include "control/controlobjectscript.h" +#include "controllers/controllerruntimedata.h" #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" #include "controllers/scripting/legacy/scriptconnectionjsproxy.h" #include "mixer/playermanager.h" @@ -47,6 +48,11 @@ ControllerScriptInterfaceLegacy::ControllerScriptInterfaceLegacy( m_spinbackActive[i] = false; m_softStartActive[i] = false; } + + connect(m_pEngine->getRuntimeData().get(), + &ControllerRuntimeData::updated, + this, + &ControllerScriptInterfaceLegacy::onRuntimeDataUpdated); } ControllerScriptInterfaceLegacy::~ControllerScriptInterfaceLegacy() { @@ -141,6 +147,88 @@ void ControllerScriptInterfaceLegacy::setValue( } } +QJSValue ControllerScriptInterfaceLegacy::getRuntimeData() { + auto pJsEngine = m_pScriptEngineLegacy->jsEngine(); + VERIFY_OR_DEBUG_ASSERT(pJsEngine) { + return QJSValue(); + } + auto pRuntimeData = m_pScriptEngineLegacy->getRuntimeData(); + + if (!pRuntimeData) { + qWarning() << "Unable to fetch the runtime data"; + return QJSValue(); + } + + return pJsEngine->toScriptValue(pRuntimeData->get()); +} + +void ControllerScriptInterfaceLegacy::setRuntimeData(const QJSValue& value) { + auto pJsEngine = m_pScriptEngineLegacy->jsEngine(); + VERIFY_OR_DEBUG_ASSERT(pJsEngine) { + return; + } + auto pRuntimeData = m_pScriptEngineLegacy->getRuntimeData(); + + if (!pRuntimeData) { + qWarning() << "Unable to fetch the runtime data"; + return; + } + + pRuntimeData->set(value.toVariant()); + qDebug() << "runtime data set successfully"; +} + +QJSValue ControllerScriptInterfaceLegacy::onRuntimeDataUpdate(const QJSValue& callback) { + if (!callback.isCallable()) { + m_pScriptEngineLegacy->throwJSError( + "Tried to connect runtime data update handler" + " to an invalid callback. Make sure that your code contains no " + "syntax errors."); + return QJSValue(); + } + auto pJsEngine = m_pScriptEngineLegacy->jsEngine(); + VERIFY_OR_DEBUG_ASSERT(pJsEngine) { + return QJSValue(); + } + auto pRuntimeData = m_pScriptEngineLegacy->getRuntimeData(); + + if (!pRuntimeData) { + qWarning() << "Unable to fetch the runtime data"; + return QJSValue(); + } + + ScriptConnection connection; + connection.engineJSProxy = this; + connection.controllerEngine = m_pScriptEngineLegacy; + connection.callback = callback; + connection.id = QUuid::createUuid(); + + m_runtimeDataConnections.append(connection); + + return pJsEngine->newQObject( + new ScriptConnectionJSProxy(m_runtimeDataConnections.last())); +} + +void ControllerScriptInterfaceLegacy::onRuntimeDataUpdated(const QVariant& value) { + auto pJsEngine = m_pScriptEngineLegacy->jsEngine(); + const auto args = QJSValueList{ + pJsEngine->toScriptValue(value), + }; + + for (const auto& connection: m_runtimeDataConnections){ + QJSValue func = connection.callback; // copy function because QJSValue::call is not const + QJSValue result = func.call(args); + if (result.isError()) { + if (m_pScriptEngineLegacy != nullptr) { + m_pScriptEngineLegacy->showScriptExceptionDialog(result); + } + qWarning() << "ControllerEngine: Invocation of connection " << connection.id.toString() + << "connected to runtime data failed:" + << result.toString(); + } + } +} + double ControllerScriptInterfaceLegacy::getParameter(const QString& group, const QString& name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { @@ -283,6 +371,7 @@ QJSValue ControllerScriptInterfaceLegacy::makeConnectionInternal( bool ControllerScriptInterfaceLegacy::removeScriptConnection( const ScriptConnection& connection) { + // TODO handle runtimeData connection ControlObjectScript* coScript = getControlObjectScript(connection.key.group, connection.key.item); @@ -299,6 +388,7 @@ void ControllerScriptInterfaceLegacy::triggerScriptConnection( return; } + // TODO handle runtimeData connection ControlObjectScript* coScript = getControlObjectScript(connection.key.group, connection.key.item); if (coScript == nullptr) { diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h index be8589f4d1a6..52c5e597ff84 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h @@ -24,6 +24,11 @@ class ControllerScriptInterfaceLegacy : public QObject { Q_INVOKABLE double getValue(const QString& group, const QString& name); Q_INVOKABLE void setValue(const QString& group, const QString& name, double newValue); + + Q_INVOKABLE QJSValue getRuntimeData(); + Q_INVOKABLE void setRuntimeData(const QJSValue& value); + Q_INVOKABLE QJSValue onRuntimeDataUpdate(const QJSValue& callback); + Q_INVOKABLE double getParameter(const QString& group, const QString& name); Q_INVOKABLE void setParameter(const QString& group, const QString& name, double newValue); Q_INVOKABLE double getParameterForValue( @@ -76,6 +81,9 @@ class ControllerScriptInterfaceLegacy : public QObject { /// Handler for timers that scripts set. virtual void timerEvent(QTimerEvent* event); + private slots: + void onRuntimeDataUpdated(const QVariant& value); + private: QJSValue makeConnectionInternal(const QString& group, const QString& name, @@ -108,4 +116,6 @@ class ControllerScriptInterfaceLegacy : public QObject { ControllerScriptEngineLegacy* m_pScriptEngineLegacy; const RuntimeLoggingCategory m_logger; + + QList m_runtimeDataConnections; };