From ea2c412f6bd77affdc05f462085dc3204018d5ae Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sun, 15 Sep 2024 01:51:23 +0100 Subject: [PATCH 1/2] feat: improve screen rendering framework --- CMakeLists.txt | 1 + res/controllers/DummyDeviceDefaultScreen.qml | 8 +- src/controllers/controller.h | 5 +- src/controllers/controllerscreenpreview.cpp | 1 + .../rendering/controllerrenderingengine.cpp | 2 +- .../legacy/controllerscriptenginelegacy.cpp | 244 ++++-------------- .../legacy/controllerscriptenginelegacy.h | 14 +- .../controllerscriptinterfacelegacy.cpp | 9 +- src/qml/qmlmixxxcontrollerscreen.cpp | 40 +++ src/qml/qmlmixxxcontrollerscreen.h | 51 ++++ .../controllerscriptenginelegacy_test.cpp | 35 +-- 11 files changed, 188 insertions(+), 222 deletions(-) create mode 100644 src/qml/qmlmixxxcontrollerscreen.cpp create mode 100644 src/qml/qmlmixxxcontrollerscreen.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 437d3c0a154..2546feabb93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2789,6 +2789,7 @@ if(QML) src/qml/qmlvisibleeffectsmodel.cpp src/qml/qmlchainpresetmodel.cpp src/qml/qmlwaveformoverview.cpp + src/qml/qmlmixxxcontrollerscreen.cpp # The following sources need to be in this target to get QML_ELEMENT properly interpreted src/control/controlmodel.cpp src/control/controlsortfiltermodel.cpp diff --git a/res/controllers/DummyDeviceDefaultScreen.qml b/res/controllers/DummyDeviceDefaultScreen.qml index c216f2259d6..06179054927 100755 --- a/res/controllers/DummyDeviceDefaultScreen.qml +++ b/res/controllers/DummyDeviceDefaultScreen.qml @@ -13,7 +13,7 @@ import Mixxx.Controls 1.0 as MixxxControls import "." as Skin -Item { +Mixxx.ControllerScreen { id: root required property string screenId @@ -23,7 +23,7 @@ Item { property string group: "[Channel1]" property var deckPlayer: Mixxx.PlayerManager.getPlayer(root.group) - function init(controlerName, isDebug) { + init: function(controlerName, isDebug) { console.log(`Screen ${root.screenId} has started`) switch (root.screenId) { case "jog": @@ -34,13 +34,13 @@ Item { } } - function shutdown() { + shutdown: function() { console.log(`Screen ${root.screenId} is stopping`) loader.sourceComponent = splash } // function transformFrame(input: ArrayBuffer, timestamp: date) { - function transformFrame(input, timestamp) { + transformFrame: function(input, timestamp) { return new ArrayBuffer(0); } diff --git a/src/controllers/controller.h b/src/controllers/controller.h index dcc7ba05032..4ae4296ce49 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -141,8 +141,9 @@ class Controller : public QObject { public: // This must be reimplemented by sub-classes desiring to send raw bytes to a - // controller. - virtual void sendBytes(const QByteArray& data) = 0; + // controller. Return true in case of successful completion, false in case + // of partial completion or failure. + virtual bool sendBytes(const QByteArray& data) = 0; private: // but used by ControllerManager diff --git a/src/controllers/controllerscreenpreview.cpp b/src/controllers/controllerscreenpreview.cpp index 30b655317bb..344be3d3db2 100644 --- a/src/controllers/controllerscreenpreview.cpp +++ b/src/controllers/controllerscreenpreview.cpp @@ -23,6 +23,7 @@ ControllerScreenPreview::ControllerScreenPreview( setMaximumWidth(screen.size.width()); m_pStat->setAlignment(Qt::AlignRight); auto pLayout = make_parented(this); + pLayout->setContentsMargins(0, 0, 0, 0); auto* pBottomLayout = new QHBoxLayout(); pLayout->addWidget(m_pFrame); pBottomLayout->addWidget(make_parented( diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index cf2be8c409c..f03072f0215 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -375,7 +375,7 @@ void ControllerRenderingEngine::send(Controller* controller, const QByteArray& f DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY(); ScopedTimer t(QStringLiteral("ControllerRenderingEngine::send")); if (!frame.isEmpty()) { - controller->sendBytes(frame); + VERIFY_OR_TERMINATE(controller->sendBytes(frame), "Unable to send frame to device"); } if (CmdlineArgs::Instance() diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 22978c5f5c4..805cc90012e 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -1,5 +1,7 @@ #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" +#include + #ifdef MIXXX_USE_QML #include #include @@ -11,38 +13,19 @@ #include "control/controlobject.h" #include "controllers/controller.h" -#ifdef MIXXX_USE_QML -#include "controllers/rendering/controllerrenderingengine.h" -#endif #include "controllers/scripting/colormapperjsproxy.h" #include "controllers/scripting/legacy/controllerscriptinterfacelegacy.h" #include "errordialoghandler.h" #include "mixer/playermanager.h" #include "moc_controllerscriptenginelegacy.cpp" #ifdef MIXXX_USE_QML +#include "qml/qmlmixxxcontrollerscreen.h" #include "util/assert.h" #include "util/cmdlineargs.h" using Clock = std::chrono::steady_clock; #endif -#ifdef MIXXX_USE_QML -namespace { -const QByteArray kScreenTransformFunctionUntypedSignature = - QMetaObject::normalizedSignature( - "transformFrame(QVariant,QVariant)"); -const QByteArray kScreenTransformFunctionTypedSignature = - QMetaObject::normalizedSignature("transformFrame(QVariant,QDateTime)"); -const QByteArray kScreenInitFunctionUntypedSignature = - QMetaObject::normalizedSignature( - "init(QVariant,QVariant)"); -const QByteArray kScreenInitFunctionTypedSignature = - QMetaObject::normalizedSignature("init(QString,bool)"); -const QByteArray kScreenShutdownFunctionSignature = - QMetaObject::normalizedSignature("shutdown()"); -} // anonymous namespace -#endif - ControllerScriptEngineLegacy::ControllerScriptEngineLegacy( Controller* controller, const RuntimeLoggingCategory& logger) : ControllerScriptEngineBase(controller, logger) { @@ -133,50 +116,28 @@ bool ControllerScriptEngineLegacy::callShutdownFunction() { return callFunctionOnObjects(m_scriptFunctionPrefixes, "shutdown"); #ifdef MIXXX_USE_QML } else { - VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine->hasError()) { - qCWarning(m_logger) << "Controller JS engine has an unhandled error."; - qCDebug(m_logger) << "Unhandled controller JS error is:" - << m_pJSEngine->catchError().toString(); - } - QHashIterator> i(m_rootItems); bool success = true; - while (i.hasNext()) { - i.next(); - const QMetaObject* metaObject = i.value()->metaObject(); - const QString& screenIdentifier = i.key(); - - VERIFY_OR_DEBUG_ASSERT(metaObject) { - qCWarning(m_logger) - << "Invalid meta object for screen" << screenIdentifier - << "It may be that an unhandled issue occurred when importing " - "the scene."; - continue; - } - - QMetaMethod shutdownFunction; - int methodIdx = metaObject->indexOfMethod(kScreenShutdownFunctionSignature); - - if (methodIdx == -1 || !metaObject->method(methodIdx).isValid()) { + for (const auto& [screenIdentifier, screen] : m_rootItems) { + if (!screen->getShutdown().isCallable()) { qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier << "has no valid shutdown method."; continue; } - shutdownFunction = metaObject->method(methodIdx); - qCDebug(m_logger) << "Executing shutdown on QML Scene " << screenIdentifier; - VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine->hasError()) { - qCWarning(m_logger) << "Controller JS engine has an unhandled error. Discarding."; - qCDebug(m_logger) << "Controller JS error is:" - << m_pJSEngine->catchError().toString(); + auto result = screen->getShutdown().call(QJSValueList{}); + if (result.isError()) { + qCWarning(m_logger) << "Could not shutdown the QML scene for screen" + << screenIdentifier + << "gracefully"; + + // We manually stop the screen before we trigger the shutdown procedure + // as this last one may continue rendering process in order to perform + // screen splash off. + showScriptExceptionDialog(result, false); + success = false; } - - success &= shutdownFunction.invoke(i.value().get(), - Qt::DirectConnection); - // Error handling is done in ControllerScriptEngineBase, with the - // connection QQmlEngine::warnings -> - // ControllerScriptEngineBase::handleQMLErrors } return success; } @@ -184,74 +145,37 @@ bool ControllerScriptEngineLegacy::callShutdownFunction() { } bool ControllerScriptEngineLegacy::callInitFunction() { // m_pController is nullptr in tests. - const auto controllerName = m_pController ? m_pController->getName() : QString{}; + const auto args = QJSValueList{ + m_pController ? m_pController->getName() : QString{}, + m_logger().isDebugEnabled(), + }; #ifdef MIXXX_USE_QML if (!m_bQmlMode) { #endif - const auto args = QJSValueList{ - controllerName, - m_logger().isDebugEnabled(), - }; return callFunctionOnObjects(m_scriptFunctionPrefixes, "init", args, true); #ifdef MIXXX_USE_QML } else { - VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine->hasError()) { - qCWarning(m_logger) << "Controller JS engine has an unhandled error."; - qCDebug(m_logger) << "Unhandled controller JS error is:" - << m_pJSEngine->catchError().toString(); - } - QHashIterator> i(m_rootItems); - bool success = true; - while (i.hasNext()) { - i.next(); - const QMetaObject* metaObject = i.value()->metaObject(); - const QString& screenIdentifier = i.key(); - - VERIFY_OR_DEBUG_ASSERT(metaObject) { - qCWarning(m_logger) - << "Invalid meta object for screen" << screenIdentifier - << "It may be that an unhandled issue occurred when importing " - "the scene."; - continue; - } - - QMetaMethod initFunction; - bool typed = false; - int methodIdx = metaObject->indexOfMethod(kScreenInitFunctionUntypedSignature); - - if (methodIdx == -1 || !metaObject->method(methodIdx).isValid()) { - qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier - << "has no valid untyped init method."; - methodIdx = metaObject->indexOfMethod(kScreenInitFunctionTypedSignature); - typed = true; - } - - initFunction = metaObject->method(methodIdx); - - if (!initFunction.isValid()) { + for (const auto& [screenIdentifier, screen] : m_rootItems) { + if (!screen->getInit().isCallable()) { qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier - << "has no valid typed init method. Skipping."; + << "has no valid init method."; continue; } - qCDebug(m_logger) << "Executing init on QML Scene " << screenIdentifier; - if (typed) { - success &= initFunction.invoke(i.value().get(), - Qt::DirectConnection, - Q_ARG(QString, controllerName), - Q_ARG(bool, m_logger().isDebugEnabled())); - } else { - success &= initFunction.invoke(i.value().get(), - Qt::DirectConnection, - Q_ARG(QVariant, controllerName), - Q_ARG(QVariant, m_logger().isDebugEnabled())); + auto result = screen->getInit().call(args); + if (result.isError()) { + qCWarning(m_logger) << "Could not init the QML scene for screen" + << screenIdentifier; + + // We manually stop the screen before we trigger the shutdown procedure + // as this last one may continue rendering process in order to perform + // screen splash off. + showScriptExceptionDialog(result, true); + return false; } - // Error handling is done in ControllerScriptEngineBase, with the - // connection QQmlEngine::warnings -> - // ControllerScriptEngineBase::handleQMLErrors } - return success; + return true; } #endif } @@ -303,7 +227,6 @@ void ControllerScriptEngineLegacy::setInfoScreens( const QList& screens) { m_rootItems.clear(); m_renderingScreens.clear(); - m_transformScreenFrameFunctions.clear(); m_infoScreens = screens; } #endif @@ -539,44 +462,6 @@ bool ControllerScriptEngineLegacy::initialize() { } #ifdef MIXXX_USE_QML -void ControllerScriptEngineLegacy::extractTransformFunction( - const QMetaObject* metaObject, const QString& screenIdentifier) { - VERIFY_OR_DEBUG_ASSERT(metaObject) { - qCWarning(m_logger) - << "Invalid meta object for screen" << screenIdentifier - << "It may be that an unhandled issue occurred when importing " - "the scene."; - return; - } - - QMetaMethod transformFunction; - bool typed = false; - int methodIdx = metaObject->indexOfMethod(kScreenTransformFunctionUntypedSignature); - - if (methodIdx == -1 || !metaObject->method(methodIdx).isValid()) { - qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier - << "has no valid untyped transformFrame method."; - methodIdx = metaObject->indexOfMethod(kScreenTransformFunctionTypedSignature); - typed = true; - } - - transformFunction = metaObject->method(methodIdx); - - if (!transformFunction.isValid()) { - qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier - << "has no valid typed transformFrame method. The " - "frame data will be sent " - "untransformed"; - QStringList methods; - for (int i = metaObject->methodOffset(); i < metaObject->methodCount(); ++i) { - methods << QString::fromLatin1(metaObject->method(i).methodSignature()); - } - qCDebug(m_logger) << "Found methods are: " << methods.join(", "); - } - - m_transformScreenFrameFunctions.insert(screenIdentifier, - TransformScreenFrameFunction{transformFunction, typed}); -} bool ControllerScriptEngineLegacy::bindSceneToScreen( const LegacyControllerMapping::ScriptFileInfo& qmlFile, @@ -595,16 +480,13 @@ bool ControllerScriptEngineLegacy::bindSceneToScreen( }; return false; } - const QMetaObject* metaObject = pScene->metaObject(); - - extractTransformFunction(metaObject, screenIdentifier); connect(pScreen.get(), &ControllerRenderingEngine::frameRendered, this, &ControllerScriptEngineLegacy::handleScreenFrame); m_renderingScreens.insert(screenIdentifier, pScreen); - m_rootItems.insert(screenIdentifier, pScene); + m_rootItems.emplace(screenIdentifier, std::move(pScene)); // In case a rendering issue occurs, we need to shutdown the controller // since its only purpose is to render screens. This might not be the case // in the future controller modules @@ -620,16 +502,18 @@ void ControllerScriptEngineLegacy::handleScreenFrame( const QImage& frame, const QDateTime& timestamp) { VERIFY_OR_DEBUG_ASSERT( - m_transformScreenFrameFunctions.contains(screenInfo.identifier) || m_renderingScreens.contains(screenInfo.identifier)) { qCWarning(m_logger) << "Unable to find transform function info for the given screen"; return; }; - VERIFY_OR_DEBUG_ASSERT(m_rootItems.contains(screenInfo.identifier)) { + auto itScreen = m_rootItems.find(screenInfo.identifier); + VERIFY_OR_DEBUG_ASSERT(itScreen != m_rootItems.end()) { qCWarning(m_logger) << "Unable to find a root item for the given screen"; return; }; + auto& pScreen = itScreen->second; + if (CmdlineArgs::Instance().getControllerPreviewScreens()) { QImage screenDebug(frame); @@ -657,15 +541,13 @@ void ControllerScriptEngineLegacy::handleScreenFrame( // TODO: Refactor this to a `std::bit_cast` once we drop support for older // compilers that don't support it (e.g. older than Xcode 14.3/macOS 13) QByteArray input(reinterpret_cast(frame.constBits()), frame.sizeInBytes()); - const TransformScreenFrameFunction& transformMethod = - m_transformScreenFrameFunctions[screenInfo.identifier]; - if (!transformMethod.method.isValid() && screenInfo.rawData) { + if (!pScreen->getTransform().isCallable() && screenInfo.rawData) { m_renderingScreens[screenInfo.identifier]->requestSendingFrameData(m_pController, input); return; } - if (!transformMethod.method.isValid()) { + if (!pScreen->getTransform().isCallable()) { qCWarning(m_logger) << "Could not find a valid transform function but the screen " "doesn't accept raw data. Aborting screen rendering."; @@ -673,40 +555,30 @@ void ControllerScriptEngineLegacy::handleScreenFrame( return; } - QVariant returnedValue; - VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine->hasError()) { qCWarning(m_logger) << "Controller JS engine has an unhandled error. Discarding."; qCDebug(m_logger) << "Controller JS error is:" << m_pJSEngine->catchError().toString(); } // During the frame transformation, any QML errors are considered fatal. setErrorsAreFatal(true); - bool isSuccessful = transformMethod.typed - ? transformMethod.method.invoke( - m_rootItems.value(screenInfo.identifier).get(), - Qt::DirectConnection, - Q_RETURN_ARG(QVariant, returnedValue), - Q_ARG(QVariant, input), - Q_ARG(QDateTime, timestamp)) - : transformMethod.method.invoke( - m_rootItems.value(screenInfo.identifier).get(), - Qt::DirectConnection, - Q_RETURN_ARG(QVariant, returnedValue), - Q_ARG(QVariant, input), - Q_ARG(QVariant, timestamp)); - setErrorsAreFatal(false); - - if (!isSuccessful) { + auto result = pScreen->getTransform().call( + QJSValueList{m_pJSEngine->toScriptValue(input), + m_pJSEngine->toScriptValue(timestamp)}); + if (result.isError()) { qCWarning(m_logger) << "Could not transform rendering buffer for screen" << screenInfo.identifier; // We manually stop the screen before we trigger the shutdown procedure // as this last one may continue rendering process in order to perform // screen splash off. + showScriptExceptionDialog(result, true); shutdown(); return; } - if (!isSuccessful || !returnedValue.isValid()) { + QVariant returnedValue = result.toVariant(); + setErrorsAreFatal(false); + + if (!returnedValue.isValid()) { qCWarning(m_logger) << "Could not transform rendering buffer. The transform " "function didn't return the expected Array. Stopping " "rendering on this screen"; @@ -770,7 +642,6 @@ void ControllerScriptEngineLegacy::shutdown() { }; } m_renderingScreens.clear(); - m_transformScreenFrameFunctions.clear(); #endif m_scriptWrappedFunctionCache.clear(); m_incomingDataFunctions.clear(); @@ -864,7 +735,7 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil } #ifdef MIXXX_USE_QML -std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( +std::unique_ptr ControllerScriptEngineLegacy::loadQMLFile( const LegacyControllerMapping::ScriptFileInfo& qmlScript, std::shared_ptr pScreen) { VERIFY_OR_DEBUG_ASSERT(m_pJSEngine || @@ -873,8 +744,8 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( return nullptr; } - QQmlComponent qmlComponent = QQmlComponent( - std::dynamic_pointer_cast(m_pJSEngine).get()); + QQmlEngine* pQmlEngine = std::dynamic_pointer_cast(m_pJSEngine).get(); + QQmlComponent qmlComponent = QQmlComponent(pQmlEngine); QFile scene = QFile(qmlScript.file.absoluteFilePath()); if (!scene.exists()) { @@ -925,15 +796,14 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( return nullptr; } - std::shared_ptr rootItem = - std::shared_ptr(qobject_cast(pRootObject)); + mixxx::qml::QmlMixxxControllerScreen* rootItem = + qobject_cast(pRootObject); if (!rootItem) { - qWarning("run: Not a QQuickItem"); + qWarning("run: Not a MixxxControllerScreen"); delete pRootObject; return nullptr; } - watchFilePath(qmlScript.file.absoluteFilePath()); // The root item is ready. Associate it with the window. if (!m_bTesting) { @@ -943,7 +813,7 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( rootItem->setHeight(pScreen->quickWindow()->height()); } - return rootItem; + return std::unique_ptr(rootItem); } #endif diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index b0605f4b584..6baa2a80b11 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -4,8 +4,10 @@ #include #include #include +#include #ifdef MIXXX_USE_QML #include +#include #endif #include "controllers/legacycontrollermapping.h" @@ -14,6 +16,11 @@ #ifdef MIXXX_USE_QML class QQuickItem; class ControllerRenderingEngine; +namespace mixxx { +namespace qml { +class QmlMixxxControllerScreen; +} // namespace qml +} // namespace mixxx #endif /// ControllerScriptEngineLegacy loads and executes controller scripts for the legacy @@ -84,7 +91,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { std::shared_ptr pScreen); void extractTransformFunction(const QMetaObject* metaObject, const QString& screenIdentifier); - std::shared_ptr loadQMLFile( + std::unique_ptr loadQMLFile( const LegacyControllerMapping::ScriptFileInfo& qmlScript, std::shared_ptr pScreen); @@ -114,9 +121,8 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { QHash> m_renderingScreens; // Contains all the scenes loaded for this mapping. Key is the scene // identifier (LegacyControllerMapping::ScreenInfo::identifier), value in - // the QML root item - QHash> m_rootItems; - QHash m_transformScreenFrameFunctions; + // the QML root item. + std::unordered_map> m_rootItems; QList m_modules; QList m_infoScreens; QString m_resourcePath{QStringLiteral(".")}; diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp index 589dd4dd6d2..2093cb7ffca 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp @@ -9,6 +9,7 @@ #include "controllers/scripting/legacy/scriptconnectionjsproxy.h" #include "mixer/playermanager.h" #include "moc_controllerscriptinterfacelegacy.cpp" +#include "util/cmdlineargs.h" #include "util/fpclassify.h" #include "util/make_const_iterator.h" #include "util/time.h" @@ -523,7 +524,9 @@ int ControllerScriptInterfaceLegacy::beginTimer( m_timers[timerId] = info; if (timerId == 0) { m_pScriptEngineLegacy->logOrThrowError(QStringLiteral("Script timer could not be created")); - } else if (oneShot) { + } else if (oneShot && + // FIXME workaround log spam (Github issue to be created) + CmdlineArgs::Instance().getControllerDebug()) { qCDebug(m_logger) << "Starting one-shot timer:" << timerId; } else { qCDebug(m_logger) << "Starting timer:" << timerId; @@ -538,7 +541,9 @@ void ControllerScriptInterfaceLegacy::stopTimer(int timerId) { .arg(timerId)); return; } - qCDebug(m_logger) << "Killing timer:" << timerId; + if (CmdlineArgs::Instance().getControllerDebug()) { + qCDebug(m_logger) << "Killing timer:" << timerId; + } killTimer(timerId); m_timers.remove(timerId); } diff --git a/src/qml/qmlmixxxcontrollerscreen.cpp b/src/qml/qmlmixxxcontrollerscreen.cpp new file mode 100644 index 00000000000..1c54af998fc --- /dev/null +++ b/src/qml/qmlmixxxcontrollerscreen.cpp @@ -0,0 +1,40 @@ +#include "qml/qmlmixxxcontrollerscreen.h" + +#include +#include + +#include "moc_qmlmixxxcontrollerscreen.cpp" + +namespace mixxx { +namespace qml { + +QmlMixxxControllerScreen::QmlMixxxControllerScreen(QQuickItem* parent) + : QQuickItem(parent) { +} + +void QmlMixxxControllerScreen::setTransform(const QJSValue& value) { + if (!value.isCallable()) { + return; + } + m_transformFunc = value; + emit transformChanged(); +} + +void QmlMixxxControllerScreen::setInit(const QJSValue& value) { + if (!value.isCallable()) { + return; + } + m_initFunc = value; + emit initChanged(); +} + +void QmlMixxxControllerScreen::setShutdown(const QJSValue& value) { + if (!value.isCallable()) { + return; + } + m_shutdownFunc = value; + emit shutdownChanged(); +} + +} // namespace qml +} // namespace mixxx diff --git a/src/qml/qmlmixxxcontrollerscreen.h b/src/qml/qmlmixxxcontrollerscreen.h new file mode 100644 index 00000000000..b87e29bc744 --- /dev/null +++ b/src/qml/qmlmixxxcontrollerscreen.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "controllers/rendering/controllerrenderingengine.h" + +namespace mixxx { +namespace qml { + +class QmlMixxxControllerScreen : public QQuickItem { + Q_OBJECT + QML_NAMED_ELEMENT(ControllerScreen) + Q_PROPERTY(QJSValue init READ getInit WRITE setInit + NOTIFY initChanged REQUIRED); + Q_PROPERTY(QJSValue shutdown READ getShutdown WRITE setShutdown + NOTIFY shutdownChanged REQUIRED); + Q_PROPERTY(QJSValue transformFrame READ getTransform WRITE setTransform + NOTIFY transformChanged REQUIRED); + + public: + explicit QmlMixxxControllerScreen(QQuickItem* parent = nullptr); + + void setInit(const QJSValue& value); + void setShutdown(const QJSValue& value); + void setTransform(const QJSValue& value); + + QJSValue getInit() const { + return m_initFunc; + } + + QJSValue getShutdown() const { + return m_shutdownFunc; + } + + QJSValue getTransform() const { + return m_transformFunc; + } + + signals: + void initChanged(); + void shutdownChanged(); + void transformChanged(); + + private: + QJSValue m_initFunc; + QJSValue m_shutdownFunc; + QJSValue m_transformFunc; +}; + +} // namespace qml +} // namespace mixxx diff --git a/src/test/controllerscriptenginelegacy_test.cpp b/src/test/controllerscriptenginelegacy_test.cpp index 2e02368922d..aa88263fc63 100644 --- a/src/test/controllerscriptenginelegacy_test.cpp +++ b/src/test/controllerscriptenginelegacy_test.cpp @@ -21,6 +21,9 @@ #include "controllers/softtakeover.h" #include "helpers/log_test.h" #include "preferences/usersettings.h" +#ifdef MIXXX_USE_QML +#include "qml/qmlmixxxcontrollerscreen.h" +#endif #include "test/mixxxtest.h" #include "util/color/colorpalette.h" #include "util/time.h" @@ -55,6 +58,9 @@ class ControllerScriptEngineLegacyTest : public ControllerScriptEngineLegacy, pu void TearDown() override { mixxx::Time::setTestMode(false); +#ifdef MIXXX_USE_QML + m_rootItems.clear(); +#endif } bool evaluateScriptFile(const QFileInfo& scriptFile) { @@ -81,15 +87,13 @@ class ControllerScriptEngineLegacyTest : public ControllerScriptEngineLegacy, pu } #ifdef MIXXX_USE_QML - QHash& transformScreenFrameFunctions() { - return m_transformScreenFrameFunctions; - } - QHash>& renderingScreens() { return m_renderingScreens; } - QHash>& rootItems() { + std::unordered_map>& + rootItems() { return m_rootItems; } @@ -99,11 +103,6 @@ class ControllerScriptEngineLegacyTest : public ControllerScriptEngineLegacy, pu const QDateTime& timestamp) { handleScreenFrame(screeninfo, frame, timestamp); } - - TransformScreenFrameFunction newTransformScreenFrameFunction( - QMetaMethod method, bool typed) const { - return TransformScreenFrameFunction{method, typed}; - } #endif }; @@ -693,13 +692,9 @@ TEST_F(ControllerScriptEngineLegacyTest, screenWontSentRawDataIfNotConfigured) { "Could not find a valid transform function but the screen doesn't " "accept raw data. Aborting screen rendering."); - transformScreenFrameFunctions().insert( - dummyScreen.identifier, - newTransformScreenFrameFunction( - QMetaMethod(), - false)); renderingScreens().insert(dummyScreen.identifier, pDummyRender); - rootItems().insert(dummyScreen.identifier, std::make_shared()); + rootItems().emplace(dummyScreen.identifier, + std::make_unique()); testHandleScreen( dummyScreen, @@ -729,13 +724,9 @@ TEST_F(ControllerScriptEngineLegacyTest, screenWillSentRawDataIfConfigured) { std::make_shared(dummyScreen); EXPECT_CALL(*pDummyRender, requestSendingFrameData(_, QByteArray())); - transformScreenFrameFunctions().insert( - dummyScreen.identifier, - newTransformScreenFrameFunction( - QMetaMethod(), - false)); renderingScreens().insert(dummyScreen.identifier, pDummyRender); - rootItems().insert(dummyScreen.identifier, std::make_shared()); + rootItems().emplace(dummyScreen.identifier, + std::make_unique()); testHandleScreen( dummyScreen, From 533cedb142e2ec69c7b019c3d7fa2fcc6ee58a8b Mon Sep 17 00:00:00 2001 From: Antoine C Date: Tue, 8 Oct 2024 22:01:40 +0100 Subject: [PATCH 2/2] fix: return output result from controller --- CMakeLists.txt | 1 + src/controllers/bulk/bulkcontroller.cpp | 7 +++++-- src/controllers/bulk/bulkcontroller.h | 2 +- src/controllers/hid/hidcontroller.cpp | 3 ++- src/controllers/hid/hidcontroller.h | 2 +- src/controllers/midi/hss1394controller.cpp | 4 +++- src/controllers/midi/hss1394controller.h | 2 +- src/controllers/midi/portmidicontroller.cpp | 8 +++++--- src/controllers/midi/portmidicontroller.h | 2 +- src/test/controller_mapping_validation_test.h | 3 ++- src/test/midicontrollertest.cpp | 2 +- 11 files changed, 23 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2546feabb93..6e0e1a3eb69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2801,6 +2801,7 @@ if(QML) # and :/mixxx.org/imports/Mixxx/Controls are placed into beginning of the binary qt_finalize_target(mixxx) + install( DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/res/qml" diff --git a/src/controllers/bulk/bulkcontroller.cpp b/src/controllers/bulk/bulkcontroller.cpp index d667b099db2..2e51b8f08b2 100644 --- a/src/controllers/bulk/bulkcontroller.cpp +++ b/src/controllers/bulk/bulkcontroller.cpp @@ -7,6 +7,7 @@ #include "controllers/bulk/bulksupported.h" #include "controllers/defs_controllers.h" #include "moc_bulkcontroller.cpp" +#include "util/cmdlineargs.h" #include "util/time.h" #include "util/trace.h" @@ -264,13 +265,13 @@ void BulkController::send(const QList& data, unsigned int length) { sendBytes(temp); } -void BulkController::sendBytes(const QByteArray& data) { +bool BulkController::sendBytes(const QByteArray& data) { VERIFY_OR_DEBUG_ASSERT(!m_pMapping || m_pMapping->getDeviceDirection() & LegacyControllerMapping::DeviceDirection::Outgoing) { qDebug() << "The mapping for the bulk device" << getName() << "doesn't require sending data. Ignoring sending request."; - return; + return false; } int ret; @@ -287,8 +288,10 @@ void BulkController::sendBytes(const QByteArray& data) { if (ret < 0) { qCWarning(m_logOutput) << "Unable to send data to" << getName() << "serial #" << m_sUID << "-" << libusb_error_name(ret); + return false; } else if (CmdlineArgs::Instance().getControllerDebug()) { qCDebug(m_logOutput) << transferred << "bytes sent to" << getName() << "serial #" << m_sUID; } + return true; } diff --git a/src/controllers/bulk/bulkcontroller.h b/src/controllers/bulk/bulkcontroller.h index f2f1b0e9416..acc07232757 100644 --- a/src/controllers/bulk/bulkcontroller.h +++ b/src/controllers/bulk/bulkcontroller.h @@ -64,7 +64,7 @@ class BulkController : public Controller { private: // For devices which only support a single report, reportID must be set to // 0x0. - void sendBytes(const QByteArray& data) override; + bool sendBytes(const QByteArray& data) override; bool matchProductInfo(const ProductInfo& product); diff --git a/src/controllers/hid/hidcontroller.cpp b/src/controllers/hid/hidcontroller.cpp index e97a94045d3..7472a142575 100644 --- a/src/controllers/hid/hidcontroller.cpp +++ b/src/controllers/hid/hidcontroller.cpp @@ -198,10 +198,11 @@ int HidController::close() { /// This function is only for class compatibility with the (midi)controller /// and will not do the same as for MIDI devices, /// because sending of raw bytes is not a supported HIDAPI feature. -void HidController::sendBytes(const QByteArray& data) { +bool HidController::sendBytes(const QByteArray& data) { // Some HIDAPI backends will fail if the device uses ReportIDs (as practical all DJ controllers), // because 0 is no valid ReportID for these devices. m_pHidIoThread->updateCachedOutputReportData(0, data, false); + return true; } ControllerJSProxy* HidController::jsProxy() { diff --git a/src/controllers/hid/hidcontroller.h b/src/controllers/hid/hidcontroller.h index 4da9df976e6..2e0be8dd5fb 100644 --- a/src/controllers/hid/hidcontroller.h +++ b/src/controllers/hid/hidcontroller.h @@ -36,7 +36,7 @@ class HidController final : public Controller { private: // For devices which only support a single report, reportID must be set to // 0x0. - void sendBytes(const QByteArray& data) override; + bool sendBytes(const QByteArray& data) override; const mixxx::hid::DeviceInfo m_deviceInfo; diff --git a/src/controllers/midi/hss1394controller.cpp b/src/controllers/midi/hss1394controller.cpp index 15e2c8e9b52..3a6c3b25092 100644 --- a/src/controllers/midi/hss1394controller.cpp +++ b/src/controllers/midi/hss1394controller.cpp @@ -185,7 +185,7 @@ void Hss1394Controller::sendShortMsg(unsigned char status, unsigned char byte1, } } -void Hss1394Controller::sendBytes(const QByteArray& data) { +bool Hss1394Controller::sendBytes(const QByteArray& data) { const int bytesSent = m_pChannel->SendChannelBytes( reinterpret_cast(data.constData()), data.size()); @@ -193,5 +193,7 @@ void Hss1394Controller::sendBytes(const QByteArray& data) { if (bytesSent != data.size()) { qCWarning(m_logOutput) << "Sent" << bytesSent << "of" << data.size() << "bytes (SysEx)"; //m_pChannel->Flush(); + return false; } + return true; } diff --git a/src/controllers/midi/hss1394controller.h b/src/controllers/midi/hss1394controller.h index cbf6929eb52..78f4a04a546 100644 --- a/src/controllers/midi/hss1394controller.h +++ b/src/controllers/midi/hss1394controller.h @@ -53,7 +53,7 @@ class Hss1394Controller : public MidiController { private: // The sysex data must already contain the start byte 0xf0 and the end byte // 0xf7. - void sendBytes(const QByteArray& data) override; + bool sendBytes(const QByteArray& data) override; hss1394::TNodeInfo m_deviceInfo; int m_iDeviceIndex; diff --git a/src/controllers/midi/portmidicontroller.cpp b/src/controllers/midi/portmidicontroller.cpp index 0d0ef1f6a90..288c2b535dd 100644 --- a/src/controllers/midi/portmidicontroller.cpp +++ b/src/controllers/midi/portmidicontroller.cpp @@ -222,28 +222,30 @@ void PortMidiController::sendShortMsg(unsigned char status, unsigned char byte1, } } -void PortMidiController::sendBytes(const QByteArray& data) { +bool PortMidiController::sendBytes(const QByteArray& data) { // PortMidi does not receive a length argument for the buffer we provide to // Pm_WriteSysEx. Instead, it scans for a MidiOpCode::EndOfExclusive byte // to know when the message is over. If one is not provided, it will // overflow the buffer and cause a segfault. if (!data.endsWith(MidiUtils::opCodeValue(MidiOpCode::EndOfExclusive))) { qCDebug(m_logOutput) << "SysEx message does not end with 0xF7 -- ignoring."; - return; + return false; } if (m_pOutputDevice.isNull() || !m_pOutputDevice->isOpen()) { - return; + return false; } PmError err = m_pOutputDevice->writeSysEx((unsigned char*)data.constData()); if (err == pmNoError) { qCDebug(m_logOutput) << QStringLiteral("outgoing: ") << MidiUtils::formatSysexMessage(getName(), data); + return true; } else { // Use two qWarnings() to ensure line break works on all operating systems qCWarning(m_logOutput) << "Error sending SysEx message:" << MidiUtils::formatSysexMessage(getName(), data); qCWarning(m_logOutput) << "PortMidi error:" << Pm_GetErrorText(err); } + return false; } diff --git a/src/controllers/midi/portmidicontroller.h b/src/controllers/midi/portmidicontroller.h index 25012eb1ac9..87351ca4d18 100644 --- a/src/controllers/midi/portmidicontroller.h +++ b/src/controllers/midi/portmidicontroller.h @@ -68,7 +68,7 @@ class PortMidiController : public MidiController { private: // The sysex data must already contain the start byte 0xf0 and the end byte // 0xf7. - void sendBytes(const QByteArray& data) override; + bool sendBytes(const QByteArray& data) override; bool isPolling() const override { return true; diff --git a/src/test/controller_mapping_validation_test.h b/src/test/controller_mapping_validation_test.h index b2596c51051..4c5e2d280ce 100644 --- a/src/test/controller_mapping_validation_test.h +++ b/src/test/controller_mapping_validation_test.h @@ -119,8 +119,9 @@ class FakeController : public Controller { Q_UNUSED(length); } - void sendBytes(const QByteArray& data) override { + bool sendBytes(const QByteArray& data) override { Q_UNUSED(data); + return true; } private slots: diff --git a/src/test/midicontrollertest.cpp b/src/test/midicontrollertest.cpp index 83811eb5fe8..23386c5bd2e 100644 --- a/src/test/midicontrollertest.cpp +++ b/src/test/midicontrollertest.cpp @@ -26,7 +26,7 @@ class MockMidiController : public MidiController { void(unsigned char status, unsigned char byte1, unsigned char byte2)); - MOCK_METHOD1(sendBytes, void(const QByteArray& data)); + MOCK_METHOD1(sendBytes, bool(const QByteArray& data)); MOCK_CONST_METHOD0(isPolling, bool()); };