From 8d3dc6e5a29950f34ca1ef360ebadedaf3e2dd11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Fri, 3 Jan 2025 18:56:12 -0500 Subject: [PATCH] bitfocus: implement serialization --- .../Device/Protocol/DeviceInterface.cpp | 17 +- .../Device/Protocol/DeviceInterface.hpp | 5 +- .../Protocol/ProtocolFactoryInterface.hpp | 2 + .../Explorer/Widgets/DeviceEditDialog.cpp | 3 + .../Protocols/Bitfocus/BitfocusContext.cpp | 96 +++++++--- .../Protocols/Bitfocus/BitfocusContext.hpp | 35 ++-- .../Bitfocus/BitfocusContext.unix.cpp | 9 +- .../Bitfocus/BitfocusContext.win32.cpp | 8 +- .../Protocols/Bitfocus/BitfocusDevice.cpp | 166 ++++++++++++------ .../Protocols/Bitfocus/BitfocusEnumerator.cpp | 29 ++- .../Protocols/Bitfocus/BitfocusEnumerator.hpp | 2 +- .../Bitfocus/BitfocusProtocolFactory.cpp | 7 +- .../BitfocusProtocolSettingsWidget.cpp | 80 +++++++-- .../BitfocusProtocolSettingsWidget.hpp | 3 +- .../Bitfocus/BitfocusSpecificSettings.hpp | 4 +- .../BitfocusSpecificSettingsSerialization.cpp | 29 ++- .../Protocols/LibraryDeviceEnumerator.cpp | 36 ++-- .../Protocols/LibraryDeviceEnumerator.hpp | 6 +- 18 files changed, 382 insertions(+), 155 deletions(-) diff --git a/src/plugins/score-lib-device/Device/Protocol/DeviceInterface.cpp b/src/plugins/score-lib-device/Device/Protocol/DeviceInterface.cpp index 2cd1345932..d4923ae332 100644 --- a/src/plugins/score-lib-device/Device/Protocol/DeviceInterface.cpp +++ b/src/plugins/score-lib-device/Device/Protocol/DeviceInterface.cpp @@ -951,7 +951,7 @@ void OwningDeviceInterface::releaseDevice() { DeviceInterface::disconnect(); deviceChanged(m_dev.get(), nullptr); - m_dev.release(); + // FIXME instead it should just be shared_ptrs in player m_dev.release(); } void OwningDeviceInterface::disconnect() @@ -1040,14 +1040,18 @@ void DeviceInterface::addressRemoved(const ossia::net::parameter_base& addr) void releaseDevice( ossia::net::network_context& ctx, std::unique_ptr dd) { - if(dd) + releaseDevice(ctx, std::shared_ptr(std::move(dd))); +} + +void releaseDevice( + ossia::net::network_context& ctx, std::shared_ptr shared_dd) +{ + if(shared_dd) { - auto shared_dd = std::shared_ptr(std::move(dd)); auto strand = boost::asio::make_strand(ctx.context); boost::asio::dispatch(strand, [dev = shared_dd] { dev->get_protocol().stop(); }); - std::future wait1 - = boost::asio::dispatch(strand, boost::asio::use_future); + std::future wait1 = boost::asio::dispatch(strand, boost::asio::use_future); if(auto res = wait1.wait_for(std::chrono::seconds(1)); res != std::future_status::ready) { @@ -1056,8 +1060,7 @@ void releaseDevice( boost::asio::dispatch(strand, [d = std::move(shared_dd)]() mutable { d.reset(); }); - std::future wait2 - = boost::asio::dispatch(strand, boost::asio::use_future); + std::future wait2 = boost::asio::dispatch(strand, boost::asio::use_future); if(auto res = wait2.wait_for(std::chrono::seconds(1)); res != std::future_status::ready) { diff --git a/src/plugins/score-lib-device/Device/Protocol/DeviceInterface.hpp b/src/plugins/score-lib-device/Device/Protocol/DeviceInterface.hpp index 45174985b6..d890bec2f7 100644 --- a/src/plugins/score-lib-device/Device/Protocol/DeviceInterface.hpp +++ b/src/plugins/score-lib-device/Device/Protocol/DeviceInterface.hpp @@ -195,7 +195,7 @@ class SCORE_LIB_DEVICE_EXPORT OwningDeviceInterface : public DeviceInterface ossia::net::device_base* getDevice() const final override { return m_dev.get(); } - std::unique_ptr m_dev; + std::shared_ptr m_dev; bool m_owned{true}; }; @@ -216,4 +216,7 @@ findNodeFromPath(const QStringList& path, ossia::net::device_base& dev); SCORE_LIB_DEVICE_EXPORT void releaseDevice( ossia::net::network_context& ctx, std::unique_ptr dev); +SCORE_LIB_DEVICE_EXPORT +void releaseDevice( + ossia::net::network_context& ctx, std::shared_ptr dev); } diff --git a/src/plugins/score-lib-device/Device/Protocol/ProtocolFactoryInterface.hpp b/src/plugins/score-lib-device/Device/Protocol/ProtocolFactoryInterface.hpp index f9bb6b04f2..9b58614904 100644 --- a/src/plugins/score-lib-device/Device/Protocol/ProtocolFactoryInterface.hpp +++ b/src/plugins/score-lib-device/Device/Protocol/ProtocolFactoryInterface.hpp @@ -39,6 +39,8 @@ class SCORE_LIB_DEVICE_EXPORT DeviceEnumerator : public QObject E_SIGNAL(SCORE_LIB_DEVICE_EXPORT, deviceAdded, n, s) void deviceRemoved(const QString& s) E_SIGNAL(SCORE_LIB_DEVICE_EXPORT, deviceRemoved, s) + + void sort() E_SIGNAL(SCORE_LIB_DEVICE_EXPORT, sort) }; using DeviceEnumerators = std::vector>; diff --git a/src/plugins/score-plugin-deviceexplorer/Explorer/Explorer/Widgets/DeviceEditDialog.cpp b/src/plugins/score-plugin-deviceexplorer/Explorer/Explorer/Widgets/DeviceEditDialog.cpp index 7486ca3b7f..3fbe9951d4 100644 --- a/src/plugins/score-plugin-deviceexplorer/Explorer/Explorer/Widgets/DeviceEditDialog.cpp +++ b/src/plugins/score-plugin-deviceexplorer/Explorer/Explorer/Widgets/DeviceEditDialog.cpp @@ -341,6 +341,9 @@ void DeviceEditDialog::selectedProtocolChanged() connect(e.get(), &Device::DeviceEnumerator::deviceAdded, this, addItem); connect(e.get(), &Device::DeviceEnumerator::deviceRemoved, this, rmItem); + connect(e.get(), &Device::DeviceEnumerator::sort, this, [cat] { + cat->sortChildren(0, Qt::SortOrder::AscendingOrder); + }); e->enumerate(addItem); } } diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.cpp index 6bd077dea1..706d1b795f 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.cpp @@ -15,8 +15,8 @@ namespace bitfocus module_handler::~module_handler() { } module_handler::module_handler( - QString path, QString apiversion, module_configuration conf) - : module_handler_base{path} + QString path, QString entrypoint, QString apiversion, module_configuration conf) + : module_handler_base{path, entrypoint} { for(QChar& c : apiversion) if(!c.isDigit() && c != '.') @@ -26,6 +26,7 @@ module_handler::module_handler( = QVersionNumber::fromString(apiversion) >= QVersionNumber(1, 2); this->m_model.config = std::move(conf); + // Init an udp socket for sending osc boost::system::error_code ec; m_socket.open(boost::asio::ip::udp::v4(), ec); @@ -49,6 +50,12 @@ module_handler::module_handler( // <- setVariableDefinitions // <- etc. // <- init response + + // -> updateFeedback + // <- response + + // -> requestConfigFields + // <- response } void module_handler::do_write(QString str) @@ -62,6 +69,14 @@ QString module_handler::jsonToString(QJsonObject obj) return QJsonDocument{obj}.toJson(QJsonDocument::Compact); } +void module_handler::afterRegistration(std::function f) +{ + if(m_registered) + f(); + else + m_afterRegistrationQueue.push_back(std::move(f)); +} + void module_handler::processMessage(std::string_view v) { auto doc = QJsonDocument::fromJson(QByteArray::fromRawData(v.data(), v.size())); @@ -75,12 +90,9 @@ void module_handler::processMessage(std::string_view v) auto payload_json = QJsonDocument::fromJson(pay.toString().toUtf8()); - auto pretty = [&] { - // qDebug() << payload_json.toJson().toStdString().data(); - }; + // auto pretty + // = [&] { qDebug() << " <- " << payload_json.toJson().toStdString().data(); }; - // qDebug() << id.toString() << name.toString() << direction.toString(); - //qDebug() << payload_json.toJson().toStdString().data(); if(direction == "call") { if(name == "register") @@ -88,7 +100,9 @@ void module_handler::processMessage(std::string_view v) // First message on_register(id); - QMetaObject::invokeMethod(this, [this] { init_msg_id = init(); }); + QMetaObject::invokeMethod(this, [this] { + m_init_msg_id = init("label_" + QString::number(std::abs(rand() % 100))); + }); } else if(name == "upgradedItems") QMetaObject::invokeMethod(this, [this, id] { send_success(id); }); @@ -136,10 +150,10 @@ void module_handler::processMessage(std::string_view v) } else if(direction == "response") { - if(id == init_msg_id) + if(id == m_init_msg_id) { // Query config field - req_cfg_id = this->requestConfigFields(); + m_req_cfg_id = this->requestConfigFields(); // Update: //{ // "hasHttpHandler": false, @@ -152,16 +166,20 @@ void module_handler::processMessage(std::string_view v) // } //} } - else if(id == req_cfg_id) + else if(id == m_req_cfg_id) { on_response_configFields(payload_json["fields"].toArray()); + for(auto fun : m_afterRegistrationQueue) + fun(); + m_afterRegistrationQueue.clear(); + m_registered = true; } } } int module_handler::writeRequest(QString name, QString p) { - int id = cbid++; + int id = m_cbid++; QJsonObject obj; obj["direction"] = "call"; obj["name"] = name; @@ -186,6 +204,21 @@ void module_handler::writeReply(QJsonValue id, QJsonObject p) { return writeReply(id, QJsonDocument(p).toJson(QJsonDocument::Compact)); } +void module_handler::writeReply(QJsonValue id, QString p, bool success) +{ + QJsonObject obj; + obj["direction"] = "response"; + obj["payload"] = p; + obj["success"] = success; + obj["callbackId"] = id; + + do_write(jsonToString(obj)); +} + +void module_handler::writeReply(QJsonValue id, QJsonObject p, bool success) +{ + return writeReply(id, QJsonDocument(p).toJson(QJsonDocument::Compact), success); +} void module_handler::on_register(QJsonValue id) { @@ -206,9 +239,7 @@ void module_handler::on_setActionDefinitions(QJsonArray actions) def.hasLearn = obj["hasLearn"].toBool(); def.name = obj["name"].toString(); for(auto opt : obj["options"].toArray()) - { def.options.push_back(parseConfigField(opt.toObject())); - } m_model.actions.emplace(obj["id"].toString(), std::move(def)); } @@ -235,7 +266,7 @@ void module_handler::on_setFeedbackDefinitions(QJsonArray fbs) def.name = obj["name"].toString(); def.type = obj["type"].toString(); for(auto opt : obj["options"].toArray()) - def.options.push_back(opt.toObject().toVariantMap()); + def.options.push_back(parseConfigField(opt.toObject())); m_model.feedbacks.emplace(obj["id"].toString(), std::move(def)); } @@ -298,8 +329,6 @@ module_data::config_field module_handler::parseConfigField(QJsonObject f) void module_handler::on_response_configFields(QJsonArray fields) { - qDebug() << Q_FUNC_INFO << fields.size(); - m_model.config_fields.clear(); for(auto obj : fields) { @@ -312,7 +341,8 @@ void module_handler::on_response_configFields(QJsonArray fields) void module_handler::on_send_osc(QJsonObject obj) { const std::string host = obj["host"].toString().toStdString(); - const int port = obj["port"].toInt(); + const auto pp = obj["port"]; + const int port = pp.isDouble() ? pp.toInt() : pp.toString().toInt(); const std::string path = obj["path"].toString().toStdString(); const auto args = obj["args"].toArray(); @@ -380,10 +410,10 @@ void module_handler::on_send_osc(QJsonObject obj) } } -int module_handler::init() +int module_handler::init(QString label) { QJsonObject obj; - obj["label"] = "OSCPoint"; + obj["label"] = label; obj["isFirstInit"] = true; QJsonObject config; for(auto& [k, v] : this->m_model.config) @@ -412,9 +442,9 @@ void module_handler::on_saveConfig(QJsonObject obj) { } void module_handler::on_parseVariablesInString(QJsonValue id, QJsonObject obj) { QJsonObject p; - p["text"] = ""; // FIXME + p["text"] = obj["text"].toString(); // FIXME p["variableIds"] = QJsonArray{}; - writeReply(id, p); + writeReply(id, p, true); } void module_handler::on_updateFeedbackValues(QJsonObject obj) { } void module_handler::on_recordAction(QJsonObject obj) { } @@ -447,13 +477,31 @@ void module_handler::updateConfigAndLabel(QString label, module_configuration co int module_handler::requestConfigFields() { - qDebug() << Q_FUNC_INFO; return writeRequest("getConfigFields", jsonToString(QJsonObject{})); } void module_handler::updateFeedbacks() { - qDebug() << "TODO" << Q_FUNC_INFO; + QJsonObject feedbacks; + for(const auto& [k, v] : this->m_model.feedbacks) + { + const module_data::feedback_definition& fb = v; + QJsonObject fb_options; + // QJsonObject::fromVariantMap(fb.second.options); + + feedbacks[k] = QJsonObject{ + {"id", k}, + {"controlId", 0}, + {"feedbackId", fb.type}, + {"options", fb_options}, + {"isInverted", fb.isInverted}, + {"upgradeIndex", QJsonValue(QJsonValue::Null)}, + {"disabled", fb.disabled}, + {"image", QJsonArray{1, 1}}, + }; + } + + writeRequest("updateFeedbacks", jsonToString(QJsonObject{{"feedbacks", feedbacks}})); } void module_handler::feedbackLearnValues() diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.hpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.hpp index 60e92fc8d9..cdbb7d8b72 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.hpp +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.hpp @@ -45,13 +45,13 @@ struct module_data double min = 0; double max = 1; std::vector choices; - QVariant default_value; // true, a number, a string etc - double width; + QVariant default_value{}; // true, a number, a string etc + double width{}; }; struct action_definition { - bool hasLearn; + bool hasLearn{}; QString name; std::vector options; }; @@ -62,9 +62,12 @@ struct module_data }; struct feedback_definition { - bool hasLearn; + bool hasLearn{}; + bool isInverted{false}; + bool disabled{false}; + int upgradeIndex{0}; QString name; - std::vector options; + std::vector options; QString type; struct { @@ -96,7 +99,7 @@ struct win32_handles; struct module_handler_base : public QObject { std::unique_ptr handles{}; - explicit module_handler_base(QString module_path); + explicit module_handler_base(QString module_path, QString entrypoint); virtual ~module_handler_base(); void do_write(std::string_view res); virtual void processMessage(std::string_view) = 0; @@ -109,7 +112,7 @@ struct module_handler_base : public QObject QSocketNotifier* socket{}; int pfd[2]{}; - explicit module_handler_base(QString module_path); + explicit module_handler_base(QString module_path, QString entrypoint); virtual ~module_handler_base(); void on_read(QSocketDescriptor, QSocketNotifier::Type); @@ -123,18 +126,23 @@ struct module_handler final : public module_handler_base { W_OBJECT(module_handler) public: - explicit module_handler(QString path, QString apiversion, module_configuration config); + explicit module_handler( + QString path, QString entrypoint, QString apiversion, module_configuration config); virtual ~module_handler(); using module_handler_base::do_write; void do_write(QString res); QString jsonToString(QJsonObject obj); + void afterRegistration(std::function); + void processMessage(std::string_view v) override; int writeRequest(QString name, QString p); void writeReply(QJsonValue id, QString p); void writeReply(QJsonValue id, QJsonObject p); + void writeReply(QJsonValue id, QString p, bool success); + void writeReply(QJsonValue id, QJsonObject p, bool success); // Module -> app (requests handling) void on_register(QJsonValue id); @@ -161,7 +169,7 @@ struct module_handler final : public module_handler_base // App -> module - int init(); + int init(QString label); void send_success(QJsonValue id); void updateConfigAndLabel(QString label, module_configuration conf); int requestConfigFields(); @@ -188,12 +196,15 @@ struct module_handler final : public module_handler_base boost::asio::io_context m_send_service; boost::asio::ip::udp::socket m_socket{m_send_service}; - int cbid{1}; - int init_msg_id{-1}; - int req_cfg_id{-1}; + std::vector> m_afterRegistrationQueue; + int m_cbid{1}; + + int m_init_msg_id{-1}; + int m_req_cfg_id{-1}; bool m_expects_label_updates{true}; + bool m_registered{false}; }; } // namespace bitfocus diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.unix.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.unix.cpp index f545dee2f2..4deb70134e 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.unix.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.unix.cpp @@ -1,9 +1,10 @@ #include "BitfocusContext.hpp" +// #include namespace bitfocus { -module_handler_base::module_handler_base(QString module_path) +module_handler_base::module_handler_base(QString module_path, QString entrypoint) { // Create socketpair socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd); @@ -22,7 +23,7 @@ module_handler_base::module_handler_base(QString module_path) process.setProcessChannelMode(QProcess::ForwardedChannels); process.setProgram("node"); - process.setArguments({"main.js"}); // FIXME entrypoint from spec + process.setArguments({entrypoint}); process.setWorkingDirectory(module_path); process.setProcessEnvironment(genv); @@ -51,7 +52,7 @@ void module_handler_base::on_read(QSocketDescriptor, QSocketNotifier::Type) { std::ptrdiff_t diff = idx - pos; std::string_view message(pos, diff); - fprintf(stderr, "\n=========================\n <-- \n%s\n", message.data()); + // std::cerr << "\n=========================\n <-- " << message << "\n"; this->processMessage(message); pos = idx + 1; continue; @@ -61,7 +62,7 @@ void module_handler_base::on_read(QSocketDescriptor, QSocketNotifier::Type) void module_handler_base::do_write(std::string_view res) { - fprintf(stderr, "\n=========================\n --> \n%s\n", res.data()); + // std::cerr << "\n=========================\n --> " << res << "\n"; ::write(pfd[0], res.data(), res.size()); } } diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.win32.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.win32.cpp index 5a57f9fac3..cfc1512b24 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.win32.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusContext.win32.cpp @@ -1,7 +1,6 @@ #include "BitfocusContext.hpp" #include -#include #include #include @@ -254,7 +253,7 @@ struct win32_handles QByteArray readbuf; }; -module_handler_base::module_handler_base(QString module_path) +module_handler_base::module_handler_base(QString module_path, QString entrypoint) { handles = std::make_unique(); if(!handles->ready) @@ -289,7 +288,10 @@ module_handler_base::module_handler_base(QString module_path) genv.insert("NODE_CHANNEL_SERIALIZATION_MODE", "json"); genv.insert("NODE_CHANNEL_FD", "3"); - handles->startProcess("node.exe main.js", module_path.toStdString(), genv); + std::string cmdline; + cmdline = "node.exe "; + cmdline += entrypoint.toStdString(); + handles->startProcess(cmdline, module_path.toStdString(), genv); } module_handler_base::~module_handler_base() { } diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusDevice.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusDevice.cpp index 68c22624dd..bc012d02f9 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusDevice.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusDevice.cpp @@ -77,9 +77,9 @@ class bitfocus_protocol m->actionRun(name, options_map); }); } - else if(parent == nodes.presets) - { - } + // else if(parent == nodes.presets) + // { + // } else if(parent == nodes.feedbacks) { } @@ -89,14 +89,82 @@ class bitfocus_protocol bool observe(ossia::net::parameter_base&, bool) override { return true; } bool update(ossia::net::node_base& node_base) override { return true; } - void set_device(ossia::net::device_base& dev) override + void setup_node(auto& config, ossia::net::node_base* node) + { + ossia::net::set_description(*node, config.name.toStdString()); + switch(config.options.size()) + { + case 0: { + node->create_parameter(ossia::val_type::IMPULSE); + break; + } + // FIXME when we have only one parameter, simplify things? + // We need to store the option name somewhere though + // case 1: { + // auto& opt = config.options[0]; + // if(opt.type == "static-text") + // node->create_parameter(ossia::val_type::IMPULSE); + // else + // setup_option_parameter(opt, node); + // break; + // } + default: { + node->create_parameter(ossia::val_type::IMPULSE); + for(auto& opt : config.options) + { + if(opt.type == "static-text") + continue; + + auto cld = node->create_child(opt.id.toStdString()); + setup_option_parameter(opt, cld); + } + } + } + } + + void setup_option_parameter( + const bitfocus::module_data::config_field& opt, ossia::net::node_base* cld) { + ossia::net::set_description(*cld, opt.label.toStdString()); + if(opt.type == "textinput" || opt.type == "bonjourdevice") + { + auto p = cld->create_parameter(ossia::val_type::STRING); + p->set_value(opt.default_value.toString().toStdString()); + } + else if(opt.type == "number") + { + auto p = cld->create_parameter(ossia::val_type::FLOAT); + p->set_value(opt.default_value.toDouble()); + } + else if(opt.type == "checkbox" || opt.type == "boolean") + { + auto p = cld->create_parameter(ossia::val_type::BOOL); + p->set_value(opt.default_value.toBool()); + } + else if(opt.type == "choices" || opt.type == "dropdown") + { + auto p = cld->create_parameter(ossia::val_type::STRING); + auto dom = ossia::domain_base{}; + for(const auto& choice : opt.choices) + dom.values.push_back(choice.id.toStdString()); + p->set_value(opt.default_value.toString().toStdString()); + } + } + + void init_device() + { + if(!m_dev) + return; + if(nodes.actions || nodes.feedbacks || nodes.variables) + return; + + auto& dev = *m_dev; // Start creating the tree auto& m = m_rc->model(); nodes.actions = m.actions.empty() ? nullptr : dev.get_root_node().create_child("action"); - nodes.presets - = m.presets.empty() ? nullptr : dev.get_root_node().create_child("presets"); + // nodes.presets + // = m.presets.empty() ? nullptr : dev.get_root_node().create_child("presets"); nodes.feedbacks = m.feedbacks.empty() ? nullptr : dev.get_root_node().create_child("feedback"); nodes.variables @@ -105,53 +173,20 @@ class bitfocus_protocol for(auto& v : m.actions) { auto node = nodes.actions->create_child(v.first.toStdString()); - ossia::net::set_description(*node, v.second.name.toStdString()); - auto param = node->create_parameter(ossia::val_type::IMPULSE); - for(auto& opt : v.second.options) - { - if(opt.type == "static-text") - continue; - - auto cld = node->create_child(opt.id.toStdString()); - ossia::net::set_description(*node, opt.label.toStdString()); - if(opt.type == "textinput" || opt.type == "bonjourdevice") - { - auto p = cld->create_parameter(ossia::val_type::STRING); - p->set_value(opt.default_value.toString().toStdString()); - } - else if(opt.type == "number") - { - auto p = cld->create_parameter(ossia::val_type::FLOAT); - p->set_value(opt.default_value.toDouble()); - } - else if(opt.type == "checkbox") - { - auto p = cld->create_parameter(ossia::val_type::BOOL); - p->set_value(opt.default_value.toBool()); - } - else if(opt.type == "choices" || opt.type == "dropdown") - { - auto p = cld->create_parameter(ossia::val_type::STRING); - auto dom = ossia::domain_base{}; - for(const auto& choice : opt.choices) - dom.values.push_back(choice.id.toStdString()); - p->set_value(opt.default_value.toString().toStdString()); - } - } + setup_node(v.second, node); } - for(auto& v : m.presets) - { - auto node = nodes.presets->create_child(v.first.toStdString()); - ossia::net::set_description(*node, v.second.name.toStdString()); - auto param = node->create_parameter(ossia::val_type::IMPULSE); - } + // for(auto& v : m.presets) + // { + // auto node = nodes.presets->create_child(v.first.toStdString()); + // ossia::net::set_description(*node, v.second.name.toStdString()); + // auto param = node->create_parameter(ossia::val_type::IMPULSE); + // } for(auto& v : m.feedbacks) { auto node = nodes.feedbacks->create_child(v.first.toStdString()); - ossia::net::set_description(*node, v.second.name.toStdString()); - auto param = node->create_parameter(ossia::val_type::IMPULSE); + setup_node(v.second, node); } for(auto& v : m.variables) @@ -170,18 +205,25 @@ class bitfocus_protocol } } + void set_device(ossia::net::device_base& dev) override + { + m_dev = &dev; + init_device(); + } + std::shared_ptr m_rc; ossia::net::network_context_ptr m_context; + ossia::net::device_base* m_dev{}; struct { ossia::net::node_base* actions{}; - ossia::net::node_base* presets{}; + // ossia::net::node_base* presets{}; ossia::net::node_base* feedbacks{}; ossia::net::node_base* variables{}; } nodes; ossia::flat_map m_actions; - ossia::flat_map m_presets; + // ossia::flat_map m_presets; ossia::flat_map m_variables_recv; ossia::flat_map m_variables_send; }; @@ -199,7 +241,7 @@ BitfocusDevice::BitfocusDevice( m_capas.canRemoveNode = false; m_capas.canRenameNode = false; m_capas.canSetProperties = false; - m_capas.canSerialize = false; + m_capas.canSerialize = true; m_capas.canLearn = false; m_capas.hasCallbacks = false; } @@ -221,26 +263,36 @@ bool BitfocusDevice::reconnect() } for(auto& [k, v] : stgs.configuration) { - conf[k] = v; + conf[k] = v.apply(ossia::qt::ossia_to_qvariant{}); } } if(!stgs.handler) { stgs.handler = std::make_shared( - stgs.path, stgs.apiVersion, std::move(conf)); + stgs.path, stgs.entrypoint, stgs.apiVersion, std::move(conf)); m_settings.deviceSpecificSettings = QVariant::fromValue(stgs); } - else - { - stgs.handler->updateConfigAndLabel(stgs.name, conf); - } + + stgs.handler->afterRegistration( + [name = stgs.name, conf, h = std::weak_ptr{stgs.handler}] { + if(auto handler = h.lock()) + { + handler->updateConfigAndLabel(name, conf); + handler->updateFeedbacks(); + } + }); const auto& name = settings().name.toStdString(); if(auto proto = std::make_unique(stgs.handler, m_ctx)) { - m_dev = std::make_unique(std::move(proto), name); + auto pproto = proto.get(); + m_dev = std::make_shared(std::move(proto), name); + stgs.handler->afterRegistration([dev = std::weak_ptr{m_dev}, pproto] { + if(auto d = dev.lock()) + pproto->init_device(); + }); deviceChanged(nullptr, m_dev.get()); setLogging_impl(Device::get_cur_logging(isLogging())); } diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.cpp index cc79ed9afc..80bec5b9e8 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.cpp @@ -1,18 +1,16 @@ #include "BitfocusEnumerator.hpp" -#include - #include #include namespace Protocols { -BitfocusEnumerator::BitfocusEnumerator(const score::DocumentContext& ctx) +BitfocusEnumerator::BitfocusEnumerator(QString path, const score::DocumentContext& ctx) : SubfolderDeviceEnumerator{ - ctx.app.settings().getPackagesPath() - + "/default/Devices/Bitfocus", + {path, path + "/_legacy"}, BitfocusProtocolFactory::static_concreteKey(), - [this](const QString& path) { return loadSettings(path); }, ctx} + [this](const QString& path) { return loadSettings(path); }, + ctx} { } @@ -25,6 +23,7 @@ static inline QString json_to_qstr(const rapidjson::Value& other) auto BitfocusEnumerator::loadSettings(const QString& path) -> ret_type { + using namespace std::string_view_literals; ret_type devices; QFile f{path + "/companion/manifest.json"}; if(f.open(QIODevice::ReadOnly)) @@ -50,15 +49,31 @@ auto BitfocusEnumerator::loadSettings(const QString& path) -> ret_type return {}; } - if(auto m_runtime = doc.FindMember("id"); + if(auto m_runtime = doc.FindMember("runtime"); m_runtime != doc.MemberEnd() && m_runtime->value.IsObject()) { auto runtime = m_runtime->value.GetObject(); + if(auto ipc = runtime.FindMember("api"); + ipc != runtime.MemberEnd() && ipc->value.IsString()) + { + if(ipc->value.GetString() != "nodejs-ipc"sv) + return {}; + } if(auto ver = runtime.FindMember("apiVersion"); ver != runtime.MemberEnd() && ver->value.IsString()) { set.apiVersion = json_to_qstr(ver->value); } + if(auto ep = runtime.FindMember("entrypoint"); + ep != runtime.MemberEnd() && ep->value.IsString()) + { + set.entrypoint = json_to_qstr(ep->value); + set.entrypoint.remove("../"); + } + } + else + { + return {}; } if(auto m_name = doc.FindMember("shortname"); m_name != doc.MemberEnd() && m_name->value.IsString()) diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.hpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.hpp index c692d5724b..210267f7ed 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.hpp +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusEnumerator.hpp @@ -9,7 +9,7 @@ namespace Protocols class BitfocusEnumerator final : public SubfolderDeviceEnumerator { public: - explicit BitfocusEnumerator(const score::DocumentContext& ctx); + explicit BitfocusEnumerator(QString path, const score::DocumentContext& ctx); ret_type 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 index 110ed5f496..97e164078c 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolFactory.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolFactory.cpp @@ -8,11 +8,14 @@ #include +#include #include #include #include #include +#include + #include #include @@ -45,7 +48,9 @@ QUrl BitfocusProtocolFactory::manual() const noexcept Device::DeviceEnumerators BitfocusProtocolFactory::getEnumerators(const score::DocumentContext& ctx) const { - return {{"Devices", new BitfocusEnumerator{ctx}}}; + auto path = ctx.app.settings().getPackagesPath() + + "/default/Devices/Bitfocus"; + return {{"Devices", new BitfocusEnumerator{path, ctx}}}; } Device::DeviceInterface* BitfocusProtocolFactory::makeDevice( diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.cpp index 8f5bcf4206..2df53ba06f 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.cpp @@ -12,6 +12,8 @@ #include +#include + #include #include #include @@ -61,7 +63,9 @@ Device::DeviceSettings BitfocusProtocolSettingsWidget::getSettings() const for(auto& widg : m_widgets) { if(widg.second.getValue) + { osc.configuration.emplace_back(widg.first, widg.second.getValue()); + } } s.deviceSpecificSettings = QVariant::fromValue(osc); @@ -98,7 +102,8 @@ void BitfocusProtocolSettingsWidget::updateFields() static_text->setText(str); m_subForm->addWidget(static_text); - m_widgets[field.id] = widget{.label = lab, .widg = static_text, .getValue = {}}; + m_widgets[field.id] + = widget{.label = lab, .widg = static_text, .getValue = {}, .setValue = {}}; } } else if(field.type == "textinput" || field.type == "bonjourdevice") @@ -114,8 +119,10 @@ void BitfocusProtocolSettingsWidget::updateFields() widg->setText(field.default_value.toString()); m_subForm->addWidget(widg); m_widgets[field.id] - = {.label = lab, .widg = widg, .getValue = [widg]() -> QVariant { - return widg->text(); + = {.label = lab, .widg = widg, .getValue = [widg]() -> ossia::value { + return widg->text().toStdString(); + }, .setValue = [widg](ossia::value v) { + widg->setText(QString::fromStdString(ossia::convert(v))); }}; } else if(field.type == "number") @@ -125,8 +132,10 @@ void BitfocusProtocolSettingsWidget::updateFields() widg->setValue(field.default_value.toDouble()); m_subForm->addWidget(widg); m_widgets[field.id] - = {.label = lab, .widg = widg, .getValue = [widg]() -> QVariant { + = {.label = lab, .widg = widg, .getValue = [widg]() -> ossia::value { return widg->value(); + }, .setValue = [widg](ossia::value v) { + widg->setValue(ossia::convert(v)); }}; } else if(field.type == "checkbox") @@ -135,8 +144,10 @@ void BitfocusProtocolSettingsWidget::updateFields() widg->setChecked(field.default_value.toBool() == true); m_subForm->addWidget(widg); m_widgets[field.id] - = {.label = lab, .widg = widg, .getValue = [widg]() -> QVariant { + = {.label = lab, .widg = widg, .getValue = [widg]() -> ossia::value { return widg->isChecked(); + }, .setValue = [widg](ossia::value v) { + widg->setChecked(ossia::convert(v)); }}; } else if(field.type == "choices" || field.type == "dropdown") @@ -157,8 +168,21 @@ void BitfocusProtocolSettingsWidget::updateFields() m_subForm->addWidget(widg); m_widgets[field.id] - = {.label = lab, .widg = widg, .getValue = [widg]() -> QVariant { - return widg->currentData(); + = {.label = lab, .widg = widg, .getValue = [widg]() -> ossia::value { + return widg->currentData().toString().toStdString(); + }, .setValue = [widg](ossia::value v) { + auto id = QString::fromStdString(ossia::convert(v)); + int idx = -1; + for(int i = 0; i < widg->count(); i++) + { + if(widg->itemData(i) == id) + { + idx = i; + break; + } + } + if(idx != -1) + widg->setCurrentIndex(idx); }}; } } @@ -181,27 +205,53 @@ void BitfocusProtocolSettingsWidget::setSettings(const Device::DeviceSettings& s m_deviceNameEdit->setText(settings.name); + bool mustLoad = false; if(settings.deviceSpecificSettings.canConvert()) { auto stgs = settings.deviceSpecificSettings.value(); if(!stgs.path.isEmpty() && QDir{stgs.path}.exists()) { m_deviceNameEdit->setText(stgs.name); - auto conf = bitfocus::module_configuration{}; + if(!stgs.handler) { - if(!stgs.product.isEmpty()) + // First load + auto conf = bitfocus::module_configuration{}; { - conf["product"] = stgs.product; + if(!stgs.product.isEmpty()) + { + conf["product"] = stgs.product; + } } + stgs.handler = std::make_shared( + stgs.path, stgs.entrypoint, stgs.apiVersion, std::move(conf)); + connect( + stgs.handler.get(), &bitfocus::module_handler::configurationParsed, this, + [this] { updateFields(); }); + } + else + { + // Device already loaded, we're editing + mustLoad = true; } - stgs.handler = std::make_shared( - stgs.path, stgs.apiVersion, std::move(conf)); - connect( - stgs.handler.get(), &bitfocus::module_handler::configurationParsed, this, - [this] { updateFields(); }); } m_settings = stgs; + + if(mustLoad) + { + // 1. Load the fields + updateFields(); + + // 2. Load our saved values + for(auto& [k, v] : stgs.configuration) + { + if(auto member = this->m_widgets.find(k); member != m_widgets.end()) + { + if(member->second.setValue) + member->second.setValue(v); + } + } + } } } } diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.hpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.hpp index cad8364cb7..5449e102d4 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.hpp +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusProtocolSettingsWidget.hpp @@ -47,7 +47,8 @@ class BitfocusProtocolSettingsWidget final : public Device::ProtocolSettingsWidg { QLabel* label{}; QWidget* widg{}; - std::function getValue; + std::function getValue; + std::function setValue; }; std::map m_widgets; diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettings.hpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettings.hpp index c2d86b711e..56a1160a16 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettings.hpp +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettings.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include @@ -16,13 +17,14 @@ namespace Protocols struct BitfocusSpecificSettings { QString path; + QString entrypoint; QString id; QString name; QString brand; QString product; QString apiVersion; - std::vector> configuration; + std::vector> configuration; QString description; std::shared_ptr handler; diff --git a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettingsSerialization.cpp b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettingsSerialization.cpp index ecb9ed3871..26a2c868bc 100644 --- a/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettingsSerialization.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/Bitfocus/BitfocusSpecificSettingsSerialization.cpp @@ -2,6 +2,9 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include "BitfocusSpecificSettings.hpp" +#include +#include + #include #include @@ -11,25 +14,43 @@ template <> void DataStreamReader::read(const Protocols::BitfocusSpecificSettings& n) { - this->m_stream << n.path << n.id << n.name << n.brand << n.product << n.apiVersion - << n.configuration << n.description; + m_stream << n.path << n.entrypoint << n.id << n.name << n.brand << n.product + << n.apiVersion << n.configuration << n.description; insertDelimiter(); } template <> void DataStreamWriter::write(Protocols::BitfocusSpecificSettings& n) { - this->m_stream >> n.path >> n.id >> n.name >> n.brand >> n.product >> n.apiVersion - >> n.configuration >> n.description; + m_stream >> n.path >> n.entrypoint >> n.id >> n.name >> n.brand >> n.product + >> n.apiVersion >> n.configuration >> n.description; checkDelimiter(); } template <> void JSONReader::read(const Protocols::BitfocusSpecificSettings& n) { + obj["Path"] = n.path; + obj["Entrypoint"] = n.entrypoint; + obj["Identifier"] = n.id; + obj["Name"] = n.name; + obj["Brand"] = n.brand; + obj["Product"] = n.product; + obj["APIVersion"] = n.apiVersion; + obj["Configuration"] = n.configuration; + obj["Description"] = n.description; } template <> void JSONWriter::write(Protocols::BitfocusSpecificSettings& n) { + n.path <<= obj["Path"]; + n.entrypoint <<= obj["Entrypoint"]; + n.id <<= obj["Identifier"]; + n.name <<= obj["Name"]; + n.brand <<= obj["Brand"]; + n.product <<= obj["Product"]; + n.apiVersion <<= obj["APIVersion"]; + n.configuration <<= obj["Configuration"]; + n.description <<= obj["Description"]; } diff --git a/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.cpp b/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.cpp index 2eb71a8cd7..60f9858601 100644 --- a/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.cpp +++ b/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.cpp @@ -52,27 +52,33 @@ void LibraryDeviceEnumerator::enumerate( } SubfolderDeviceEnumerator::SubfolderDeviceEnumerator( - QString root, Device::ProtocolFactory::ConcreteKey k, func_type createDev, + QStringList roots, 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(); - for(auto spec : m_createDeviceSettings(filepath)) + for(const auto& root : roots) + { + QTimer::singleShot(10, this, [this, root, n = roots.size()] { + QDirIterator it{ + root, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::NoIteratorFlags}; + while(it.hasNext()) { - Device::DeviceSettings s; - s.name = spec.first; - s.protocol = m_key; - s.deviceSpecificSettings = spec.second; - deviceAdded(s.name, s); + for(auto spec : m_createDeviceSettings(it.next())) + { + Device::DeviceSettings s; + s.name = spec.first; + s.protocol = m_key; + s.deviceSpecificSettings = std::move(spec.second); + deviceAdded(s.name, s); + } } - } - }); + + m_finished++; + if(m_finished == n) + this->sort(); + }); + } } void SubfolderDeviceEnumerator::next(std::string_view path) { } diff --git a/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.hpp b/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.hpp index c9980003b8..d160193758 100644 --- a/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.hpp +++ b/src/plugins/score-plugin-protocols/Protocols/LibraryDeviceEnumerator.hpp @@ -37,12 +37,14 @@ class SCORE_PLUGIN_PROTOCOLS_EXPORT SubfolderDeviceEnumerator func_type m_createDeviceSettings; SubfolderDeviceEnumerator( - QString rootFolder, Device::ProtocolFactory::ConcreteKey k, func_type createDev, - const score::DocumentContext& ctx); + QStringList 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; + + int m_finished = 0; }; }