diff --git a/CMakeLists.txt b/CMakeLists.txt index 7aea3f6..d104c9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ option(SPIX_BUILD_TESTS "Build Spix unit tests." OFF) set(SPIX_QT_MAJOR "6" CACHE STRING "Major Qt version to build Spix against") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake/modules") -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) # Hide symbols unless explicitly flagged with SPIX_EXPORT set(CMAKE_CXX_VISIBILITY_PRESET hidden) diff --git a/examples/Basic/CMakeLists.txt b/examples/Basic/CMakeLists.txt index 23885d3..8018713 100644 --- a/examples/Basic/CMakeLists.txt +++ b/examples/Basic/CMakeLists.txt @@ -1,7 +1,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt${SPIX_QT_MAJOR} COMPONENTS Core Quick REQUIRED) diff --git a/examples/BasicStandalone/CMakeLists.txt b/examples/BasicStandalone/CMakeLists.txt index 13dfeb7..9e0d296 100644 --- a/examples/BasicStandalone/CMakeLists.txt +++ b/examples/BasicStandalone/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../cmake/modules") set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # diff --git a/examples/GTest/CMakeLists.txt b/examples/GTest/CMakeLists.txt index 385bcdf..8225d32 100644 --- a/examples/GTest/CMakeLists.txt +++ b/examples/GTest/CMakeLists.txt @@ -1,7 +1,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(SPIX_QT_MAJOR "6" CACHE STRING "Major Qt version to build Spix against") diff --git a/examples/ListGridView/CMakeLists.txt b/examples/ListGridView/CMakeLists.txt index a253032..e6d288a 100644 --- a/examples/ListGridView/CMakeLists.txt +++ b/examples/ListGridView/CMakeLists.txt @@ -1,7 +1,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(SPIX_QT_MAJOR "6" CACHE STRING "Major Qt version to build Spix against") diff --git a/examples/RemoteCtrl/CMakeLists.txt b/examples/RemoteCtrl/CMakeLists.txt index 1675293..b59d021 100644 --- a/examples/RemoteCtrl/CMakeLists.txt +++ b/examples/RemoteCtrl/CMakeLists.txt @@ -1,7 +1,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(SPIX_QT_MAJOR "6" CACHE STRING "Major Qt version to build Spix against") diff --git a/examples/RepeaterLoader/CMakeLists.txt b/examples/RepeaterLoader/CMakeLists.txt index 5df7ef5..434d2f6 100644 --- a/examples/RepeaterLoader/CMakeLists.txt +++ b/examples/RepeaterLoader/CMakeLists.txt @@ -1,7 +1,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(SPIX_QT_MAJOR "6" CACHE STRING "Major Qt version to build Spix against") diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 723de5e..7a270d3 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -54,6 +54,8 @@ set(SOURCES src/Commands/GetTestStatus.h src/Commands/InputText.cpp src/Commands/InputText.h + src/Commands/InvokeMethod.cpp + src/Commands/InvokeMethod.h src/Commands/Quit.cpp src/Commands/Quit.h src/Commands/Screenshot.cpp @@ -92,8 +94,10 @@ set(SOURCES src/Scene/Qt/QtScene.cpp src/Scene/Qt/QtScene.h src/Scene/Scene.h - + + src/Utils/AnyRpcUtils.cpp src/Utils/AnyRpcUtils.h + src/Utils/AnyRpcFunction.h src/Utils/DebugDump.cpp src/Utils/DebugDump.h src/Utils/QtEventRecorder.cpp diff --git a/lib/include/Spix/Data/Variant.h b/lib/include/Spix/Data/Variant.h new file mode 100644 index 0000000..63a3997 --- /dev/null +++ b/lib/include/Spix/Data/Variant.h @@ -0,0 +1,55 @@ +/*** + * Copyright (C) Noah Koontz. All rights reserved. + * Licensed under the MIT license. + * See LICENSE.txt file in the project root for full license information. + ****/ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace spix { + +struct Variant; + +namespace { +using VariantBaseType = std::variant, std::vector, std::map>; +} + +// TODO: make this a block comment +// NOTE: std::visit is broken for this variant for GCC <= 11.2 and clang <= 14.0 . See +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2162r0.html for more info. +SPIX_EXPORT struct Variant : VariantBaseType { + using ListType = std::vector; + using MapType = std::map; + using VariantType = VariantBaseType; + using VariantBaseType::variant; + VariantBaseType const& base() const { return *this; } + VariantBaseType& base() { return *this; } + + enum TypeIndex + { + Nullptr = 0, + Bool, + Int64, + Uint64, + Double, + String, + Time, + List, + Map, + TypeIndexCount + }; +}; + +static_assert( + Variant::TypeIndexCount == std::variant_size_v, "Variant enum does not cover all Variant types"); + +} // namespace spix diff --git a/lib/include/Spix/TestServer.h b/lib/include/Spix/TestServer.h index 65bd32b..f3da289 100644 --- a/lib/include/Spix/TestServer.h +++ b/lib/include/Spix/TestServer.h @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -57,6 +58,7 @@ class SPIX_EXPORT TestServer { std::string getStringProperty(ItemPath path, std::string propertyName); void setStringProperty(ItemPath path, std::string propertyName, std::string propertyValue); + Variant invokeMethod(ItemPath path, std::string method, std::vector args); Rect getBoundingBox(ItemPath path); bool existsAndVisible(ItemPath path); std::vector getErrors(); diff --git a/lib/src/AnyRpcServer.cpp b/lib/src/AnyRpcServer.cpp index 7e275f4..4551daf 100644 --- a/lib/src/AnyRpcServer.cpp +++ b/lib/src/AnyRpcServer.cpp @@ -5,8 +5,8 @@ ****/ #include -#include -#include +#include +#include #include namespace spix { @@ -70,6 +70,12 @@ AnyRpcServer::AnyRpcServer(int anyrpcPort) setStringProperty(std::move(path), std::move(property), std::move(value)); }); + utils::AddFunctionToAnyRpc)>(methodManager, "invokeMethod", + "Invoke a method on a QML object | invokeMethod(string path, string method, any[] args)", + [this](std::string path, std::string method, std::vector args) { + return invokeMethod(std::move(path), std::move(method), std::move(args)); + }); + utils::AddFunctionToAnyRpc(std::string)>(methodManager, "getBoundingBox", "Return the bounding box of an item in screen coordinates | getBoundingBox(string path) : (doubles) " "[topLeft.x, topLeft.y , width, height]", diff --git a/lib/src/Commands/InvokeMethod.cpp b/lib/src/Commands/InvokeMethod.cpp new file mode 100644 index 0000000..1ec0ad8 --- /dev/null +++ b/lib/src/Commands/InvokeMethod.cpp @@ -0,0 +1,39 @@ +/*** + * Copyright (C) Falko Axmann. All rights reserved. + * Licensed under the MIT license. + * See LICENSE.txt file in the project root for full license information. + ****/ + +#include "InvokeMethod.h" + +#include + +namespace spix { +namespace cmd { + +InvokeMethod::InvokeMethod(ItemPath path, std::string method, std::vector args, std::promise promise) +: m_path(std::move(path)) +, m_method(std::move(method)) +, m_args(std::move(args)) +, m_promise(std::move(promise)) +{ +} + +void InvokeMethod::execute(CommandEnvironment& env) +{ + auto item = env.scene().itemAtPath(m_path); + + if (item) { + Variant ret; + bool success = item->invokeMethod(m_method, m_args, ret); + if (!success) + env.state().reportError("InvokeMethod: Failed to invoke method: " + m_method); + m_promise.set_value(ret); + } else { + env.state().reportError("InvokeMethod: Item not found: " + m_path.string()); + m_promise.set_value(Variant(nullptr)); + } +} + +} // namespace cmd +} // namespace spix diff --git a/lib/src/Commands/InvokeMethod.h b/lib/src/Commands/InvokeMethod.h new file mode 100644 index 0000000..e269215 --- /dev/null +++ b/lib/src/Commands/InvokeMethod.h @@ -0,0 +1,34 @@ +/*** + * Copyright (C) Falko Axmann. All rights reserved. + * Licensed under the MIT license. + * See LICENSE.txt file in the project root for full license information. + ****/ + +#pragma once + +#include + +#include "Command.h" +#include +#include + +#include + +namespace spix { +namespace cmd { + +class SPIX_EXPORT InvokeMethod : public Command { +public: + InvokeMethod(ItemPath path, std::string method, std::vector args, std::promise promise); + + void execute(CommandEnvironment& env) override; + +private: + ItemPath m_path; + std::string m_method; + std::vector m_args; + std::promise m_promise; +}; + +} // namespace cmd +} // namespace spix diff --git a/lib/src/QtQmlBot.cpp b/lib/src/QtQmlBot.cpp index 46f8e57..b3f67cd 100644 --- a/lib/src/QtQmlBot.cpp +++ b/lib/src/QtQmlBot.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -24,6 +25,7 @@ QtQmlBot::~QtQmlBot() = default; void QtQmlBot::runTestServer(TestServer& server) { + qInfo() << "Spix server is enabled. Only use this in a safe environment."; server.setCommandExecuter(m_cmdExec.get()); server.start(); } diff --git a/lib/src/Scene/Item.h b/lib/src/Scene/Item.h index c76779d..0d3e887 100644 --- a/lib/src/Scene/Item.h +++ b/lib/src/Scene/Item.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include @@ -29,6 +30,7 @@ class Item { virtual Rect bounds() const = 0; virtual std::string stringProperty(const std::string& name) const = 0; virtual void setStringProperty(const std::string& name, const std::string& value) = 0; + virtual bool invokeMethod(const std::string& method, const std::vector& args, Variant& ret) = 0; virtual bool visible() const = 0; }; diff --git a/lib/src/Scene/Mock/MockItem.cpp b/lib/src/Scene/Mock/MockItem.cpp index e7a266d..1267a57 100644 --- a/lib/src/Scene/Mock/MockItem.cpp +++ b/lib/src/Scene/Mock/MockItem.cpp @@ -40,6 +40,12 @@ void MockItem::setStringProperty(const std::string& name, const std::string& val { } +bool MockItem::invokeMethod(const std::string& method, const std::vector& args, Variant& ret) +{ + ret = Variant(nullptr); + return true; +} + bool MockItem::visible() const { return true; diff --git a/lib/src/Scene/Mock/MockItem.h b/lib/src/Scene/Mock/MockItem.h index fa89092..09ef388 100644 --- a/lib/src/Scene/Mock/MockItem.h +++ b/lib/src/Scene/Mock/MockItem.h @@ -10,8 +10,6 @@ #include -#include - namespace spix { class SPIX_EXPORT MockItem : public Item { @@ -24,6 +22,7 @@ class SPIX_EXPORT MockItem : public Item { Rect bounds() const override; std::string stringProperty(const std::string& name) const override; void setStringProperty(const std::string& name, const std::string& value) override; + bool invokeMethod(const std::string& method, const std::vector& args, Variant& ret) override; bool visible() const override; // MockItem specials diff --git a/lib/src/Scene/Qt/QtItem.cpp b/lib/src/Scene/Qt/QtItem.cpp index cd364aa..e848686 100644 --- a/lib/src/Scene/Qt/QtItem.cpp +++ b/lib/src/Scene/Qt/QtItem.cpp @@ -6,8 +6,13 @@ #include "QtItem.h" +#include + +#include #include +#include + namespace spix { QtItem::QtItem(QQuickItem* item) @@ -49,6 +54,41 @@ void QtItem::setStringProperty(const std::string& name, const std::string& value m_item->setProperty(name.c_str(), value.c_str()); } +bool QtItem::invokeMethod(const std::string& method, const std::vector& args, Variant& ret) +{ + if (args.size() > 10) + return false; + + std::vector qtVars; + for (auto arg : args) + qtVars.push_back(qt::VariantToQVariant(arg)); + + QMetaMethod match; + bool matched = spix::qt::GetMethodMetaForArgs(*m_item, method, qtVars, match); + if (!matched) + return false; + // qDebug() << "Method: " << match.methodSignature(); + // qDebug() << "Args: "; + // qDebug() << "Return type:" << match.returnMetaType().name(); + + qt::QMLReturnVariant retVar; + QGenericReturnArgument retArg = qt::GetReturnArgForQMetaType(match.returnType(), retVar); + std::vector qtArgs = qt::ConvertAndCreateQArgumentsForMethod(match, qtVars); + + bool success = match.invoke(m_item, Qt::ConnectionType::DirectConnection, retArg, qtArgs[0], qtArgs[1], qtArgs[2], + qtArgs[3], qtArgs[4], qtArgs[5], qtArgs[6], qtArgs[7], qtArgs[8], qtArgs[9]); + if (success) { + ret = qt::QMLReturnVariantToVariant(retVar); + + // std::stringstream log; + // ret.WriteStream(log); + // qDebug() << "Ret: " << log.str().data(); + + return true; + } + return false; +} + bool QtItem::visible() const { return m_item->isVisible(); diff --git a/lib/src/Scene/Qt/QtItem.h b/lib/src/Scene/Qt/QtItem.h index 134e1a2..1ee4711 100644 --- a/lib/src/Scene/Qt/QtItem.h +++ b/lib/src/Scene/Qt/QtItem.h @@ -22,6 +22,7 @@ class QtItem : public Item { Rect bounds() const override; std::string stringProperty(const std::string& name) const override; void setStringProperty(const std::string& name, const std::string& value) override; + bool invokeMethod(const std::string& method, const std::vector& args, Variant& ret) override; bool visible() const override; QQuickItem* qquickitem(); diff --git a/lib/src/Scene/Qt/QtItemTools.cpp b/lib/src/Scene/Qt/QtItemTools.cpp index ef54c33..8b80d9c 100644 --- a/lib/src/Scene/Qt/QtItemTools.cpp +++ b/lib/src/Scene/Qt/QtItemTools.cpp @@ -6,6 +6,7 @@ #include "QtItemTools.h" +#include #include #include @@ -85,5 +86,209 @@ QObject* FindChildItem(QObject* object, const QString& name) return nullptr; } +QGenericReturnArgument GetReturnArgForQMetaType(int type, QMLReturnVariant& retVar) +{ + switch (type) { + case QMetaType::Type::Void: + retVar = nullptr; + return QGenericReturnArgument(); + case QMetaType::Type::Bool: + retVar = bool(); + return Q_RETURN_ARG(bool, std::get(retVar)); + case QMetaType::Type::Int: + retVar = int(); + return Q_RETURN_ARG(int, std::get(retVar)); + case QMetaType::Type::Float: + retVar = float(); + return Q_RETURN_ARG(float, std::get(retVar)); + case QMetaType::Type::Double: + retVar = double(); + return Q_RETURN_ARG(double, std::get(retVar)); + case QMetaType::Type::QString: + retVar = QString(); + return Q_RETURN_ARG(QString, std::get(retVar)); + case QMetaType::Type::QDateTime: + retVar = QDateTime(); + return Q_RETURN_ARG(QDateTime, std::get(retVar)); + default: + retVar = QVariant(); + return Q_RETURN_ARG(QVariant, std::get(retVar)); + } +} + +QVariant VariantToQVariant(const Variant& var) +{ + static_assert(Variant::TypeIndexCount == 9, "VariantToQVariant does not cover all Variant types"); + + switch (var.index()) { + case Variant::Nullptr: + return QVariant(); + case Variant::Bool: + return QVariant(std::get(var)); + case Variant::Int64: + return QVariant(static_cast(std::get(var))); + case Variant::Uint64: + return QVariant(static_cast(std::get(var))); + case Variant::Double: + return QVariant(std::get(var)); + case Variant::String: + return QVariant(QString::fromStdString(std::get(var))); + case Variant::Time: { + auto time = std::get>(var); + std::time_t timet = std::chrono::system_clock::to_time_t(time); + return QVariant(QDateTime::fromSecsSinceEpoch(timet)); + } + case Variant::List: { + QVariantList list; + for (const auto& elem : std::get(var)) + list.push_back(VariantToQVariant(elem)); + return QVariant(list); + } + case Variant::Map: { + QVariantMap map; + for (const auto& [key, value] : std::get(var)) + map.insert(QString::fromStdString(key), VariantToQVariant(value)); + return QVariant(map); + } + default: + throw std::range_error("VariantToQVariant received Variant with unknown type"); + } +} + +Variant QVariantToVariant(const QVariant& var) +{ + qDebug() << var.typeName(); + switch (static_cast(var.type())) { + case QMetaType::Type::Bool: + return Variant(var.toBool()); + case QMetaType::Type::Char: + case QMetaType::Type::SChar: + case QMetaType::Type::Short: + case QMetaType::Type::Int: + case QMetaType::Type::Long: + case QMetaType::Type::LongLong: + return Variant(static_cast(var.toLongLong())); + case QMetaType::Type::UChar: + case QMetaType::Type::UShort: + case QMetaType::Type::UInt: + case QMetaType::Type::ULong: + case QMetaType::Type::ULongLong: + return Variant(static_cast(var.toULongLong())); + case QMetaType::Type::Float: + case QMetaType::Type::Double: + return Variant(var.toDouble()); + case QMetaType::Type::QDateTime: { + std::time_t time = var.toDateTime().currentSecsSinceEpoch(); + return Variant(std::chrono::system_clock::from_time_t(time)); + } + case QMetaType::Type::QString: + return Variant(var.toString().toStdString()); + case QMetaType::Type::Nullptr: + case QMetaType::Type::Void: + case QMetaType::Type::UnknownType: + return Variant(nullptr); + default: + break; + } + + if (var.type() == qMetaTypeId()) { + QJSValue jsval = var.value(); + return QVariantToVariant(jsval.toVariant()); + } + + if (var.canConvert(QMetaType::Type::QVariantList)) { + const QVariantList& list = var.toList(); + Variant::ListType ret; + for (const QVariant& elem : list) { + Variant convertedElem = QVariantToVariant(elem); + ret.push_back(convertedElem); + } + return Variant(ret); + } + + if (var.canConvert(QMetaType::Type::QVariantMap)) { + const QVariantMap& map = var.toMap(); + Variant::MapType ret; + for (auto ptr = map.constBegin(); ptr != map.constEnd(); ptr++) { + Variant convertedElem = QVariantToVariant(ptr.value()); + ret[ptr.key().toStdString()] = convertedElem; + } + return Variant(ret); + } + + return Variant(var.toString().toStdString()); +} + +Variant QMLReturnVariantToVariant(const QMLReturnVariant& var) +{ + return std::visit( + [](auto&& arg) { + using T = std::decay_t; + + if constexpr (std::is_integral_v) { + return Variant(static_cast(arg)); + } else if constexpr (std::is_unsigned_v) { + return Variant(static_cast(arg)); + } else if constexpr (std::is_floating_point_v) { + return Variant(static_cast(arg)); + } else if constexpr (std::is_same_v) { + return Variant(arg.toStdString()); + } else if constexpr (std::is_same_v) { + return Variant(std::chrono::system_clock::from_time_t(arg.toLocalTime().toSecsSinceEpoch())); + } else if constexpr (std::is_same_v) { + return QVariantToVariant(arg); + } else { + return Variant(arg); + } + }, + var); +} + +bool CanConvertArgTypes(const QMetaMethod& metaMethod, const std::vector& varargs) +{ + if (metaMethod.parameterCount() != varargs.size()) + return false; + for (size_t i = 0; i < metaMethod.parameterCount(); i++) { + int targetType = metaMethod.parameterType(i); + if (targetType != QMetaType::Type::QVariant && !varargs[i].canConvert(targetType)) + return false; + } + return true; +} + +bool GetMethodMetaForArgs( + const QObject& obj, const std::string& method, const std::vector& varargs, QMetaMethod& ret) +{ + const QMetaObject* itemMeta = obj.metaObject(); + for (size_t i = 0; i < itemMeta->methodCount(); i++) { + const QMetaMethod methodMeta = itemMeta->method(i); + qDebug() << "Signature: " << methodMeta.methodSignature(); + if (methodMeta.name().compare(method.data()) == 0 && CanConvertArgTypes(methodMeta, varargs)) { + ret = methodMeta; + return true; + } + } + return false; +} + +std::vector ConvertAndCreateQArgumentsForMethod( + const QMetaMethod& metaMethod, std::vector& varargs) +{ + std::vector qtArgs; + for (size_t i = 0; i < 10; i++) { + if (i < varargs.size()) { + int targetType = metaMethod.parameterType(i); + if (targetType != QMetaType::Type::QVariant) { + varargs[i].convert(targetType); + qtArgs.push_back(QGenericArgument(varargs[i].typeName(), varargs[i].data())); + } else { + qtArgs.push_back(Q_ARG(QVariant, varargs[i])); + } + } else + qtArgs.push_back(QGenericArgument()); + } + return qtArgs; +} + } // namespace qt } // namespace spix diff --git a/lib/src/Scene/Qt/QtItemTools.h b/lib/src/Scene/Qt/QtItemTools.h index e125f87..dce53ef 100644 --- a/lib/src/Scene/Qt/QtItemTools.h +++ b/lib/src/Scene/Qt/QtItemTools.h @@ -6,8 +6,12 @@ #pragma once +#include + +#include #include #include +#include class QString; @@ -37,5 +41,18 @@ T FindChildItem(QObject* object, const QString& name) return qobject_cast(FindChildItem(object, name)); } +using QMLReturnVariant = std::variant; +QGenericReturnArgument GetReturnArgForQMetaType(int type, QMLReturnVariant& toInitialize); + +QVariant VariantToQVariant(const Variant& var); +Variant QVariantToVariant(const QVariant& var); +Variant QMLReturnVariantToVariant(const QMLReturnVariant& var); + +bool CanConvertArgTypes(const QMetaMethod& metaMethod, const std::vector& varargs); +bool GetMethodMetaForArgs( + const QObject& obj, const std::string& method, const std::vector& varargs, QMetaMethod& ret); +std::vector ConvertAndCreateQArgumentsForMethod( + const QMetaMethod& metaMethod, std::vector& varargs); + } // namespace qt } // namespace spix diff --git a/lib/src/TestServer.cpp b/lib/src/TestServer.cpp index c2ccc84..45eee1e 100644 --- a/lib/src/TestServer.cpp +++ b/lib/src/TestServer.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -119,6 +120,16 @@ void TestServer::setStringProperty(ItemPath path, std::string propertyName, std: m_cmdExec->enqueueCommand(path, std::move(propertyName), std::move(propertyValue)); } +Variant TestServer::invokeMethod(ItemPath path, std::string method, std::vector args) +{ + std::promise promise; + auto result = promise.get_future(); + auto cmd = std::make_unique(path, std::move(method), std::move(args), std::move(promise)); + m_cmdExec->enqueueCommand(std::move(cmd)); + + return result.get(); +} + Rect TestServer::getBoundingBox(ItemPath path) { std::promise promise; diff --git a/lib/src/Utils/AnyRpcFunction.h b/lib/src/Utils/AnyRpcFunction.h new file mode 100644 index 0000000..e3870be --- /dev/null +++ b/lib/src/Utils/AnyRpcFunction.h @@ -0,0 +1,190 @@ +/*** + * Copyright (C) Falko Axmann. All rights reserved. + * Licensed under the MIT license. + * See LICENSE.txt file in the project root for full license information. + ****/ + +#pragma once + +#include +#include +#include +#include + +/** + * Utility type traits + */ + +namespace { +template class Template> +struct is_specialization : std::false_type { +}; + +template