From bb59eb1a6314946dfd80f44571c24db7b719f042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Mon, 30 Dec 2024 00:02:20 -0500 Subject: [PATCH] protocols: initial implementation for bitfocus modules interop --- .../score-plugin-protocols/CMakeLists.txt | 24 + .../Protocols/Bitfocus/BitfocusContext.cpp | 10 + .../Protocols/Bitfocus/BitfocusContext.hpp | 496 ++++++++++++++++++ .../Protocols/Bitfocus/BitfocusDevice.cpp | 127 +++++ .../Protocols/Bitfocus/BitfocusDevice.hpp | 23 + .../Protocols/Bitfocus/BitfocusEnumerator.cpp | 58 ++ .../Protocols/Bitfocus/BitfocusEnumerator.hpp | 17 + .../Bitfocus/BitfocusProtocolFactory.cpp | 93 ++++ .../Bitfocus/BitfocusProtocolFactory.hpp | 33 ++ .../BitfocusProtocolSettingsWidget.cpp | 69 +++ .../BitfocusProtocolSettingsWidget.hpp | 36 ++ .../Bitfocus/BitfocusSpecificSettings.hpp | 18 + .../BitfocusSpecificSettingsSerialization.cpp | 31 ++ .../Protocols/LibraryDeviceEnumerator.cpp | 32 ++ .../Protocols/LibraryDeviceEnumerator.hpp | 18 + .../score_plugin_protocols.cpp | 3 +- 16 files changed, 1087 insertions(+), 1 deletion(-) create mode 100644 src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.cpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.hpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusDevice.cpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusDevice.hpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.cpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.hpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolFactory.cpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolFactory.hpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.cpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.hpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettings.hpp create mode 100644 src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettingsSerialization.cpp diff --git a/src/plugins/score-plugin-protocols/CMakeLists.txt b/src/plugins/score-plugin-protocols/CMakeLists.txt index 6fab05c0de..2d42b68908 100644 --- a/src/plugins/score-plugin-protocols/CMakeLists.txt +++ b/src/plugins/score-plugin-protocols/CMakeLists.txt @@ -248,6 +248,25 @@ set(EVDEV_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Evdev/EvdevSpecificSettingsSerialization.cpp" ) +set(BITFOCUS_HDRS + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Bitfocus/BitfocusContext.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Bitfocus/BitfocusEnumerator.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Bitfocus/BitfocusDevice.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Bitfocus/BitfocusProtocolFactory.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Bitfocus/BitfocusSpecificSettings.hpp" +) + +set(BITFOCUS_SRCS + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Bitfocus/BitfocusContext.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Bitfocus/BitfocusEnumerator.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Bitfocus/BitfocusDevice.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Bitfocus/BitfocusProtocolFactory.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Bitfocus/BitfocusSpecificSettingsSerialization.cpp" +) + + set(MAPPER_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Mapper/MapperDevice.hpp" @@ -368,6 +387,11 @@ if(LINUX) list(APPEND SCORE_FEATURES_LIST protocol_evdev) endif() +target_sources(${PROJECT_NAME} PRIVATE ${BITFOCUS_HDRS} ${BITFOCUS_SRCS}) +target_include_directories(${PROJECT_NAME} PRIVATE ${BITFOCUS_HEADER}) +target_compile_definitions(${PROJECT_NAME} PRIVATE OSSIA_PROTOCOL_BITFOCUS) +list(APPEND SCORE_FEATURES_LIST protocol_bitfocus) + target_link_libraries(${PROJECT_NAME} PUBLIC ${QT_PREFIX}::Core ${QT_PREFIX}::Widgets ${QT_PREFIX}::Network diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.cpp new file mode 100644 index 0000000000..da628ad164 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.cpp @@ -0,0 +1,10 @@ +#include "BitfocusContext.hpp" + +#include + +W_OBJECT_IMPL(bitfocus::module_handler) +namespace bitfocus +{ +module_handler_base::~module_handler_base() = default; +module_handler::~module_handler() = default; +} diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.hpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.hpp new file mode 100644 index 0000000000..3b889c3443 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.hpp @@ -0,0 +1,496 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include + +#if !defined(_WIN32) +#include +#include + +#include +#include +#include +#endif +namespace bitfocus +{ +struct module +{ +}; + +struct connection +{ + struct action_definition + { + bool hasLearn; + QString name; + std::vector options; + }; + struct variable_definition + { + QString name; + QVariant value; + }; + struct feedback_definition + { + bool hasLearn; + QString name; + std::vector options; + QString type; + + //{"defaultStyle":{"bgcolor":16711680,"color":0}, + // "hasLearn":false, + // "id":"showState", + // "name":"Show state feedback", + // "options":[{"choices":[{"id":"slideshow","label":"Slide show"},{"id":"edit","label":"Edit"}],"default":"slideshow","id":"state","label":"State","type":"dropdown"}], + // "type":"boolean" + // } + }; + struct preset_definition + { + QString name; + QString category; + QString text; + QString type; + std::vector feedbacks; + std::vector steps; + }; + + std::map actions; + std::map variables; + std::map feedbacks; + std::map presets; + + struct config_field + { + }; + + struct config_ui + { + std::vector fields; + std::map config; + }; +}; + +struct companion +{ + std::map modules; + std::map connections; +}; + +// note: callback id shared between both ends so every message has to be processed in order +#if defined(_WIN32) +struct module_handler_base : public QObject +{ + explicit module_handler_base(QString module_path) + { + // https://doc.qt.io/qt-6/qwineventnotifier.html + // https://forum.qt.io/topic/146343/qsocketnotifier-with-win32-namedpipes/9 + // Or maybe QLocalSocket just works on windows? + + // FIXME + } + + void do_write(std::string_view res) + { + // FIXME + } + void do_write(const QByteArray& res) + { + // FIXME + } +}; +#else +struct module_handler_base : public QObject +{ + char buf[16 * 4096]; + QProcess process; + QSocketNotifier* socket{}; + int pfd[2]; + + explicit module_handler_base(QString module_path) + { + // Create socketpair + socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd); + + // Create env + auto genv = QProcessEnvironment::systemEnvironment(); + genv.insert("CONNECTION_ID", "connectionId"); + genv.insert("VERIFICATION_TOKEN", "foobar"); + genv.insert("MODULE_MANIFEST", module_path + "/companion/manifest.json"); + genv.insert("NODE_CHANNEL_SERIALIZATION_MODE", "json"); + genv.insert("NODE_CHANNEL_FD", QString::number(pfd[1]).toUtf8()); + + auto socket = new QSocketNotifier(pfd[0], QSocketNotifier::Read, this); + QObject::connect( + socket, &QSocketNotifier::activated, this, &module_handler_base::on_read); + + process.setProcessChannelMode(QProcess::ForwardedChannels); + process.setProgram("node"); + process.setArguments({"main.js"}); // FIXME entrypoint from spec + process.setWorkingDirectory(module_path); + process.setProcessEnvironment(genv); + + process.start(); + + // See https://forum.qt.io/topic/33964/solved-child-qprocess-that-dies-with-parent/10 + + /// Connection flow: + // Create process + // <- register call + // -> register response + + // -> init call + // <- upgradedItems + // <- setActionDefinitions + // <- setVariableDefinitions + // <- etc. + // <- init response + } + + virtual ~module_handler_base(); + + void on_read(QSocketDescriptor, QSocketNotifier::Type) + { + ssize_t rl = ::read(pfd[0], buf, sizeof(buf)); + if(rl <= 0) + return; + char* pos = buf; + char* idx = buf; + char* const end = pos + rl; + do + { + idx = std::find(pos, end, '\n'); + if(idx < end) + { + std::ptrdiff_t diff = idx - pos; + std::string_view message(pos, diff); + this->processMessage(message); + pos = idx + 1; + continue; + } + } while(idx < end); + } + + void do_write(std::string_view res) { ::write(pfd[0], res.data(), res.size()); } + void do_write(const QByteArray& res) { ::write(pfd[0], res.data(), res.size()); } + + virtual void processMessage(std::string_view) = 0; +}; +#endif +struct module_handler final : public module_handler_base +{ + W_OBJECT(module_handler) +public: + explicit module_handler(QString path) + : module_handler_base{path} + { + } + + virtual ~module_handler(); + + QString toPayload(QJsonObject obj) + { + return QJsonDocument{obj}.toJson(QJsonDocument::Compact); + } + + void processMessage(std::string_view v) override + { + auto doc = QJsonDocument::fromJson(QByteArray::fromRawData(v.data(), v.size())); + auto id = doc.object()["callbackId"]; + auto direction = doc.object()["direction"]; + auto pay = doc.object()["payload"]; + auto name = doc.object()["name"]; + auto success = doc.object()["success"]; + + auto payload_json = QJsonDocument::fromJson(pay.toString().toUtf8()); + auto pretty = [&] { qDebug() << payload_json.toJson().toStdString().data(); }; + + if(direction == "call") + { + if(name == "register") + { + // First message + on_register(); + + QMetaObject::invokeMethod(this, [this] { init_msg_id = init(); }); + } + else if(name == "upgradedItems") + { + QMetaObject::invokeMethod(this, [this, n = id.toInt()] { send_success(n); }); + } + else if(name == "setActionDefinitions") + { + auto actions = payload_json["actions"].toArray(); + for(auto act : actions) + { + auto obj = act.toObject(); + bitfocus::connection::action_definition def; + def.hasLearn = obj["hasLearn"].toBool(); + def.name = obj["name"].toString(); + for(auto opt : obj["options"].toArray()) + def.options.push_back(opt.toObject().toVariantMap()); + + m_model.actions.emplace(obj["id"].toString(), std::move(def)); + } + } + else if(name == "setFeedbackDefinitions") + { + auto fbs = payload_json["feedbacks"].toArray(); + for(auto fb : fbs) + { + auto obj = fb.toObject(); + bitfocus::connection::feedback_definition def; + def.hasLearn = obj["hasLearn"].toBool(); + def.name = obj["name"].toString(); + def.type = obj["type"].toString(); + for(auto opt : obj["options"].toArray()) + def.options.push_back(opt.toObject().toVariantMap()); + + m_model.feedbacks.emplace(obj["id"].toString(), std::move(def)); + } + } + else if(name == "setVariableDefinitions") + { + auto vars = payload_json["variables"].toArray(); + for(auto var : vars) + { + auto obj = var.toObject(); + bitfocus::connection::variable_definition def; + def.name = obj["name"].toString(); + m_model.variables[obj["id"].toString()] = std::move(def); + } + } + else if(name == "setPresetDefinitions") + { + auto vars = payload_json["presets"].toArray(); + for(auto var : vars) + { + auto obj = var.toObject(); + bitfocus::connection::preset_definition def; + def.name = obj["name"].toString(); + def.text = obj["text"].toString(); + def.category = obj["category"].toString(); + def.type = obj["type"].toString(); + + for(auto opt : obj["feedbacks"].toArray()) + def.feedbacks.push_back(opt.toObject().toVariantMap()); + for(auto opt : obj["steps"].toArray()) + def.steps.push_back(opt.toObject().toVariantMap()); + m_model.presets.emplace(obj["id"].toString(), std::move(def)); + } + } + else if(name == "setVariableValues") + { + auto vars = payload_json["newValues"].toArray(); + for(auto var : vars) + { + auto obj = var.toObject(); + m_model.variables[obj["id"].toString()].value = obj["value"].toVariant(); + } + } + else if(name == "log-message") + { + // qDebug() << " !! Unhandled !! " << name; + // pretty(); + } + else if(name == "set-status") + { + qDebug() << " !! Unhandled !! " << name; + pretty(); + } + else if(name == "updateFeedbackValues") + { + qDebug() << " !! Unhandled !! " << name; + pretty(); + } + else if(name == "saveConfig") + { + qDebug() << " !! Unhandled !! " << name; + pretty(); + } + else if(name == "send-osc") + { + on_send_osc(payload_json.object()); + } + else if(name == "parseVariablesInString") + { + qDebug() << " !! Unhandled !! " << name; + pretty(); + } + else if(name == "recordAction") + { + qDebug() << " !! Unhandled !! " << name; + pretty(); + } + else if(name == "setCustomVariable") + { + qDebug() << " !! Unhandled !! " << name; + pretty(); + } + else if(name == "sharedUdpSocketJoin") + { + qDebug() << " !! Unhandled !! " << name; + pretty(); + } + else if(name == "sharedUdpSocketLeave") + { + qDebug() << " !! Unhandled !! " << name; + pretty(); + } + else if(name == "sharedUdpSocketSend") + { + qDebug() << " !! Unhandled !! " << name; + pretty(); + } + } + else if(direction == "response") + { + if(id == init_msg_id) + { + // Update: + //{ + // "hasHttpHandler": false, + // "hasRecordActionsHandler": false, + // "newUpgradeIndex": -1, + // "updatedConfig": { + // "localport": 35550, + // "remotehost": "127.0.0.1", + // "remoteport": 35551 + // } + //} + + // Query config field + this->requestConfigFields(); + } + } + } + + int writeRequest(QString name, QString p) + { + int id = cbid++; + QJsonObject obj; + obj["direction"] = "call"; + obj["name"] = name; + obj["payload"] = p; + obj["callbackId"] = id; + + auto res = toPayload(obj).toUtf8().append('\n'); + qDebug().noquote().nospace() << "sending: " << res.size(); + do_write(res); + return id; + } + + void writeReply(int id, QString p) + { + QJsonObject obj; + obj["direction"] = "response"; + obj["payload"] = p; + obj["callbackId"] = id; + auto res = toPayload(obj).toUtf8().append('\n'); + qDebug() << "sending: " << res; + do_write(res); + } + + // Module -> app + void on_register() + { + // Payload in ejson format. + std::string_view res + = R"_({"direction":"response","callbackId":1,"success":true,"payload":"{}"})_" + "\n"; + qDebug().noquote().nospace() << "sending: " << res; + do_write(res); + cbid = 1; + } + + void on_send_osc(QJsonObject obj) + { + const QString host = obj["host"].toString(); + const int port = obj["port"].toInt(); + const QString path = obj["path"].toString(); + const auto args = obj["args"].toArray(); + + qDebug() << "TODO send osc" << path << args; + } + + // App -> module + + int init() + { + QJsonObject obj; + obj["label"] = "OSCPoint"; + obj["isFirstInit"] = false; + obj["config"] = QJsonObject{ + {"remotehost", "127.0.0.1"}, {"remoteport", 35551}, {"localport", 35550}}; + obj["lastUpgradeIndex"] = -1; + obj["actions"] = QJsonObject{}; + obj["feedbacks"] = QJsonObject{}; + + return writeRequest("init", toPayload(obj)); + } + + void send_success(int id) + { + QJsonObject obj; + obj["direction"] = "response"; + obj["callbackId"] = id; + obj["success"] = true; + auto res = toPayload(obj).toUtf8().append('\n'); + do_write(res); + } + + void updateConfigAndLabel() { qDebug() << "TODO" << Q_FUNC_INFO; } + int requestConfigFields() + { + return writeRequest("getConfigFields", toPayload(QJsonObject{})); + } + void updateFeedbacks() { qDebug() << "TODO" << Q_FUNC_INFO; } + void feedbackLearnValues() { qDebug() << "TODO" << Q_FUNC_INFO; } + void feedbackDelete() { qDebug() << "TODO" << Q_FUNC_INFO; } + void variablesChanged() + { + qDebug() << "TODO" << Q_FUNC_INFO; + // {"direction":"call","name":"variablesChanged","payload":"{\"variablesIds\":[\"internal:time_hms\",\"internal:time_s\",\"internal:time_unix\",\"internal:time_hms_12\",\"internal:uptime\"]}"} + } + void actionUpdate() { qDebug() << "TODO" << Q_FUNC_INFO; } + void actionDelete() { qDebug() << "TODO" << Q_FUNC_INFO; } + void actionLearnValues() { qDebug() << "TODO" << Q_FUNC_INFO; } + void actionRun() + { + // {"direction":"call", + // "name":"executeAction", + // "payload": + // "{\"action\": + // {\"id\":\"ZNn2OwGmoX3ApvT2MdfkM\", + // \"controlId\":\"bank:j_kyzoAM5Cwj0Lb1dx5MS\", + // \"actionId\":\"goto_first_slide\", + // \"options\":{}, + // \"upgradeIndex\":null, + // \"disabled\":false + // },\"surfaceId\":\"hot:tablet\"}", + // "callbackId":3} + + qDebug() << "TODO" << Q_FUNC_INFO; + } + void destroy() { qDebug() << "TODO" << Q_FUNC_INFO; } + void executeHttpRequest() { qDebug() << "TODO" << Q_FUNC_INFO; } + void startStopRecordingActions() { qDebug() << "TODO" << Q_FUNC_INFO; } + void sharedUdpSocketMessage() { qDebug() << "TODO" << Q_FUNC_INFO; } + void sharedUdpSocketError() { qDebug() << "TODO" << Q_FUNC_INFO; } + const bitfocus::connection& model() { return m_model; } + + void configurationParsed() W_SIGNAL(configurationParsed); + +private: + bitfocus::connection m_model; + int cbid = 0; + + int init_msg_id{}; +}; + +} // namespace bitfocus diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusDevice.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusDevice.cpp new file mode 100644 index 0000000000..aa8ec936d6 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusDevice.cpp @@ -0,0 +1,127 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include "BitfocusDevice.hpp" + +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include +namespace Protocols +{ + +BitfocusDevice::BitfocusDevice( + const Device::DeviceSettings& settings, const ossia::net::network_context_ptr& ctx) + : OwningDeviceInterface{settings} + , m_ctx{ctx} +{ + m_capas.canLearn = true; + m_capas.hasCallbacks = false; +} + +bool BitfocusDevice::reconnect() +{ + disconnect(); + + /* + try + { + const BitfocusSpecificSettings& stgs + = settings().deviceSpecificSettings.value(); + const auto& name = settings().name.toStdString(); + if(auto proto + = std::make_unique(m_ctx, stgs.configuration)) + { + if(stgs.rate) + { + auto rate = std::make_unique( + std::chrono::milliseconds{*stgs.rate}, std::move(proto)); + m_dev = std::make_unique(std::move(rate), name); + } + else + { + m_dev = std::make_unique(std::move(proto), name); + } + + deviceChanged(nullptr, m_dev.get()); + setLogging_impl(Device::get_cur_logging(isLogging())); + } + else + { + qDebug() << "Could not create Bitfocus protocol"; + } + } + catch(std::exception& e) + { + qDebug() << "Bitfocus Protocol error: " << e.what(); + } + catch(...) + { + SCORE_TODO; + } +*/ + return connected(); +} + +void BitfocusDevice::recreate(const Device::Node& n) +{ + for(auto& child : n) + { + addNode(child); + } +} + +bool BitfocusDevice::isLearning() const +{ + /* + auto& proto = static_cast(m_dev->get_protocol()); + return proto.learning(); + */ + return false; +} + +void BitfocusDevice::setLearning(bool b) +{ + /* + if(!m_dev) + return; + auto& proto = static_cast(m_dev->get_protocol()); + auto& dev = *m_dev; + if(b) + { + dev.on_node_created.connect<&DeviceInterface::nodeCreated>((DeviceInterface*)this); + dev.on_node_removing.connect<&DeviceInterface::nodeRemoving>((DeviceInterface*)this); + dev.on_node_renamed.connect<&DeviceInterface::nodeRenamed>((DeviceInterface*)this); + dev.on_parameter_created.connect<&DeviceInterface::addressCreated>( + (DeviceInterface*)this); + dev.on_attribute_modified.connect<&DeviceInterface::addressUpdated>( + (DeviceInterface*)this); + } + else + { + dev.on_node_created.disconnect<&DeviceInterface::nodeCreated>( + (DeviceInterface*)this); + dev.on_node_removing.disconnect<&DeviceInterface::nodeRemoving>( + (DeviceInterface*)this); + dev.on_node_renamed.disconnect<&DeviceInterface::nodeRenamed>( + (DeviceInterface*)this); + dev.on_parameter_created.disconnect<&DeviceInterface::addressCreated>( + (DeviceInterface*)this); + dev.on_attribute_modified.disconnect<&DeviceInterface::addressUpdated>( + (DeviceInterface*)this); + } + + proto.set_learning(b);*/ +} +} diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusDevice.hpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusDevice.hpp new file mode 100644 index 0000000000..13b4d6b5fb --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusDevice.hpp @@ -0,0 +1,23 @@ +#pragma once +#include + +#include + +namespace Protocols +{ +class BitfocusDevice final : public Device::OwningDeviceInterface +{ +public: + BitfocusDevice( + const Device::DeviceSettings& stngs, const ossia::net::network_context_ptr& ctx); + + bool reconnect() override; + void recreate(const Device::Node&) final override; + + bool isLearning() const final override; + void setLearning(bool) final override; + +private: + const ossia::net::network_context_ptr& m_ctx; +}; +} diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.cpp new file mode 100644 index 0000000000..13c1e962fb --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.cpp @@ -0,0 +1,58 @@ +#include "BitfocusEnumerator.hpp" + +#include + +#include +namespace Protocols +{ +BitfocusEnumerator::BitfocusEnumerator(const score::DocumentContext& ctx) + : SubfolderDeviceEnumerator{ + ctx.app.settings().getPackagesPath() + + "/default/Devices/Bitfocus", + BitfocusProtocolFactory::static_concreteKey(), + [this](const QString& path) { return loadSettings(path); }, ctx} +{ +} + +static inline QString json_to_qstr(const rapidjson::Value& other) +{ + auto str = other.GetString(); + auto n = other.GetStringLength(); + return QString::fromUtf8(str, n); +} + +std::pair BitfocusEnumerator::loadSettings(const QString& path) +{ + QFile f{path + "/companion/manifest.json"}; + if(f.open(QIODevice::ReadOnly)) + { + auto n = f.size(); + auto ptr = f.map(0, n); + rapidjson::Document doc; + doc.Parse(reinterpret_cast(ptr), n); + if(!doc.HasParseError() && doc.IsObject()) + { + BitfocusSpecificSettings set{}; + if(auto m_it = doc.FindMember("id"); + m_it != doc.MemberEnd() && m_it->value.IsString()) + { + set.id = json_to_qstr(m_it->value); + } + if(auto m_name = doc.FindMember("shortname"); + m_name != doc.MemberEnd() && m_name->value.IsString()) + { + set.name = json_to_qstr(m_name->value); + } + if(!set.id.isEmpty() && !set.name.isEmpty()) + { + set.path = path; + return {set.name, QVariant::fromValue(set)}; + } + } + } + + return {}; +} + +BitfocusEnumerator::~BitfocusEnumerator() { } +} diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.hpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.hpp new file mode 100644 index 0000000000..ac61c5db08 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +namespace Protocols +{ +class BitfocusEnumerator final : public SubfolderDeviceEnumerator +{ +public: + explicit BitfocusEnumerator(const score::DocumentContext& ctx); + + std::pair loadSettings(const QString& path); + ~BitfocusEnumerator(); +}; +} diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolFactory.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolFactory.cpp new file mode 100644 index 0000000000..110ed5f496 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolFactory.cpp @@ -0,0 +1,93 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include "BitfocusProtocolFactory.hpp" + +#include "BitfocusDevice.hpp" + +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include + +namespace Device +{ +class DeviceInterface; +class ProtocolSettingsWidget; +} +struct VisitorVariant; + +namespace Protocols +{ +QString BitfocusProtocolFactory::prettyName() const noexcept +{ + return QObject::tr("Bitfocus"); +} + +QString BitfocusProtocolFactory::category() const noexcept +{ + return StandardCategories::osc; +} + +QUrl BitfocusProtocolFactory::manual() const noexcept +{ + return QUrl("https://ossia.io/score-docs/devices/bitfocus-device.html"); +} + +Device::DeviceEnumerators +BitfocusProtocolFactory::getEnumerators(const score::DocumentContext& ctx) const +{ + return {{"Devices", new BitfocusEnumerator{ctx}}}; +} + +Device::DeviceInterface* BitfocusProtocolFactory::makeDevice( + const Device::DeviceSettings& settings, const Explorer::DeviceDocumentPlugin& plugin, + const score::DocumentContext& ctx) +{ + return new BitfocusDevice{settings, plugin.networkContext()}; +} + +const Device::DeviceSettings& BitfocusProtocolFactory::defaultSettings() const noexcept +{ + static const Device::DeviceSettings settings = [&]() { + Device::DeviceSettings s; + s.protocol = concreteKey(); + s.name = "Bitfocus"; + BitfocusSpecificSettings specif; + s.deviceSpecificSettings = QVariant::fromValue(specif); + return s; + }(); + return settings; +} + +Device::ProtocolSettingsWidget* BitfocusProtocolFactory::makeSettingsWidget() +{ + return new BitfocusProtocolSettingsWidget; +} + +QVariant BitfocusProtocolFactory::makeProtocolSpecificSettings( + const VisitorVariant& visitor) const +{ + return makeProtocolSpecificSettings_T(visitor); +} + +void BitfocusProtocolFactory::serializeProtocolSpecificSettings( + const QVariant& data, const VisitorVariant& visitor) const +{ + serializeProtocolSpecificSettings_T(data, visitor); +} + +bool BitfocusProtocolFactory::checkCompatibility( + const Device::DeviceSettings& a, const Device::DeviceSettings& b) const noexcept +{ + return true; +} +} diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolFactory.hpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolFactory.hpp new file mode 100644 index 0000000000..b0a5ad46d0 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolFactory.hpp @@ -0,0 +1,33 @@ +#pragma once +#include + +namespace Protocols +{ +class BitfocusProtocolFactory final : public DefaultProtocolFactory +{ + SCORE_CONCRETE("303993ed-b39a-4edb-90a6-2a3ae45043c4") + QString prettyName() const noexcept override; + QString category() const noexcept override; + QUrl manual() const noexcept override; + + Device::DeviceEnumerators + getEnumerators(const score::DocumentContext& ctx) const override; + + Device::DeviceInterface* makeDevice( + const Device::DeviceSettings& settings, + const Explorer::DeviceDocumentPlugin& plugin, + const score::DocumentContext& ctx) override; + const Device::DeviceSettings& defaultSettings() const noexcept override; + + Device::ProtocolSettingsWidget* makeSettingsWidget() override; + + QVariant makeProtocolSpecificSettings(const VisitorVariant& visitor) const override; + + void serializeProtocolSpecificSettings( + const QVariant& data, const VisitorVariant& visitor) const override; + + bool checkCompatibility( + const Device::DeviceSettings& a, + const Device::DeviceSettings& b) const noexcept override; +}; +} diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.cpp new file mode 100644 index 0000000000..42c0149a65 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.cpp @@ -0,0 +1,69 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include "BitfocusProtocolSettingsWidget.hpp" + +#include "BitfocusContext.hpp" +#include "BitfocusProtocolFactory.hpp" +#include "BitfocusSpecificSettings.hpp" + +#include + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include + +namespace Protocols +{ + +BitfocusProtocolSettingsWidget::BitfocusProtocolSettingsWidget(QWidget* parent) + : ProtocolSettingsWidget(parent) +{ + m_deviceNameEdit = new State::AddressFragmentLineEdit{this}; + m_deviceNameEdit->setText("OSCdevice"); + checkForChanges(m_deviceNameEdit); + + auto layout = new QFormLayout{this}; + layout->addRow(tr("Name"), m_deviceNameEdit); +} + +Device::DeviceSettings BitfocusProtocolSettingsWidget::getSettings() const +{ + Device::DeviceSettings s; + s.name = m_deviceNameEdit->text(); + s.protocol = BitfocusProtocolFactory::static_concreteKey(); + + BitfocusSpecificSettings osc = m_settings; + s.deviceSpecificSettings = QVariant::fromValue(osc); + + return s; +} + +void BitfocusProtocolSettingsWidget::setSettings(const Device::DeviceSettings& settings) +{ + m_deviceNameEdit->setText(settings.name); + + if(settings.deviceSpecificSettings.canConvert()) + { + auto set = settings.deviceSpecificSettings.value(); + if(!set.path.isEmpty() && QDir{set.path}.exists()) + { + auto module = new bitfocus::module_handler{set.path}; + connect(module, &bitfocus::module_handler::configurationParsed, this, [] { + + }); + } + + m_settings = set; + } +} +} diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.hpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.hpp new file mode 100644 index 0000000000..ec7fb8a578 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.hpp @@ -0,0 +1,36 @@ +#pragma once +#include +#include + +#include + +#include + +class QStackedLayout; +class QLineEdit; +class QSpinBox; +class QWidget; + +namespace Protocols +{ + +class BasicTCPWidget; +class WebsocketClientWidget; +class RateWidget; + +class BitfocusProtocolSettingsWidget final : public Device::ProtocolSettingsWidget +{ +public: + explicit BitfocusProtocolSettingsWidget(QWidget* parent = nullptr); + + Device::DeviceSettings getSettings() const override; + void setSettings(const Device::DeviceSettings& settings) override; + + using Device::ProtocolSettingsWidget::checkForChanges; + +private: + void setDefaults(); + QLineEdit* m_deviceNameEdit{}; + BitfocusSpecificSettings m_settings; +}; +} diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettings.hpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettings.hpp new file mode 100644 index 0000000000..e5cbf7cee0 --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettings.hpp @@ -0,0 +1,18 @@ +#pragma once +#include + +#include + +#include + +namespace Protocols +{ +struct BitfocusSpecificSettings +{ + QString path; + QString id; + QString name; +}; +} +Q_DECLARE_METATYPE(Protocols::BitfocusSpecificSettings) +W_REGISTER_ARGTYPE(Protocols::BitfocusSpecificSettings) diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettingsSerialization.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettingsSerialization.cpp new file mode 100644 index 0000000000..af363cdb4a --- /dev/null +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettingsSerialization.cpp @@ -0,0 +1,31 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include "BitfocusSpecificSettings.hpp" + +#include + +#include +#include +#include + +template <> +void DataStreamReader::read(const Protocols::BitfocusSpecificSettings& n) +{ + insertDelimiter(); +} + +template <> +void DataStreamWriter::write(Protocols::BitfocusSpecificSettings& n) +{ + checkDelimiter(); +} + +template <> +void JSONReader::read(const Protocols::BitfocusSpecificSettings& n) +{ +} + +template <> +void JSONWriter::write(Protocols::BitfocusSpecificSettings& n) +{ +} diff --git a/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.cpp b/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.cpp index a92c87fe29..fcfeda0926 100644 --- a/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -50,4 +51,35 @@ void LibraryDeviceEnumerator::enumerate( { } +SubfolderDeviceEnumerator::SubfolderDeviceEnumerator( + QString root, Device::ProtocolFactory::ConcreteKey k, func_type createDev, + const score::DocumentContext& ctx) + : m_key{k} + , m_createDeviceSettings{createDev} +{ + QTimer::singleShot(100, this, [this, root] { + QDirIterator it{ + root, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::NoIteratorFlags}; + while(it.hasNext()) + { + auto filepath = it.next(); + if(auto spec = m_createDeviceSettings(filepath); spec.second != QVariant{}) + { + Device::DeviceSettings s; + s.name = spec.first; + s.protocol = m_key; + s.deviceSpecificSettings = spec.second; + deviceAdded(s.name, s); + } + } + }); +} + +void SubfolderDeviceEnumerator::next(std::string_view path) { } + +void SubfolderDeviceEnumerator::enumerate( + std::function onDevice) const +{ +} + } diff --git a/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.hpp b/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.hpp index 0303cd840a..b762b3f82a 100644 --- a/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.hpp +++ b/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.hpp @@ -26,4 +26,22 @@ class SCORE_PLUGIN_PROTOCOLS_EXPORT LibraryDeviceEnumerator void enumerate(std::function onDevice) const override; }; + +class SCORE_PLUGIN_PROTOCOLS_EXPORT SubfolderDeviceEnumerator + : public Device::DeviceEnumerator +{ +public: + using func_type = std::function(QString)>; + Device::ProtocolFactory::ConcreteKey m_key; + func_type m_createDeviceSettings; + + SubfolderDeviceEnumerator( + QString rootFolder, Device::ProtocolFactory::ConcreteKey k, func_type createDev, + const score::DocumentContext& ctx); + + void next(std::string_view path); + + void enumerate(std::function + onDevice) const override; +}; } diff --git a/src/plugins/score-plugin-protocols/score_plugin_protocols.cpp b/src/plugins/score-plugin-protocols/score_plugin_protocols.cpp index 6beede3a6d..bedc280d0d 100644 --- a/src/plugins/score-plugin-protocols/score_plugin_protocols.cpp +++ b/src/plugins/score-plugin-protocols/score_plugin_protocols.cpp @@ -5,6 +5,7 @@ #include +#include #include #include @@ -90,7 +91,7 @@ std::vector score_plugin_protocols::factories( { return instantiate_factories< score::ApplicationContext, - FW) , Protocols::MapperProtocolFactory