diff --git a/src/io/ipc.cpp b/src/io/ipc.cpp index 37a37eb3..768299ed 100644 --- a/src/io/ipc.cpp +++ b/src/io/ipc.cpp @@ -1,9 +1,12 @@ #include "ipc.hpp" +#include <cstring> #include <utility> #include <qcolor.h> #include <qmetatype.h> #include <qobjectdefs.h> +#include <qtypes.h> +#include <qvariant.h> namespace qs::io::ipc { @@ -14,6 +17,12 @@ const BoolIpcType BoolIpcType::INSTANCE {}; const DoubleIpcType DoubleIpcType::INSTANCE {}; const ColorIpcType ColorIpcType::INSTANCE {}; +void* IpcType::copyStorage(const void* data) const { + auto* storage = this->createStorage(); + memcpy(storage, data, this->size()); + return storage; +} + const IpcType* IpcType::ipcType(const QMetaType& metaType) { if (metaType.id() == QMetaType::Void) return &VoidIpcType::INSTANCE; if (metaType.id() == QMetaType::QString) return &StringIpcType::INSTANCE; @@ -70,12 +79,18 @@ void IpcTypeSlot::replace(void* value) { this->storage = value; } +void IpcTypeSlot::replace(const QVariant& value) { + this->replace(this->mType->copyStorage(value.constData())); +} + const char* VoidIpcType::name() const { return "void"; } const char* VoidIpcType::genericArgumentName() const { return "void"; } +qsizetype VoidIpcType::size() const { return 0; } // string const char* StringIpcType::name() const { return "string"; } const char* StringIpcType::genericArgumentName() const { return "QString"; } +qsizetype StringIpcType::size() const { return sizeof(QString); } void* StringIpcType::fromString(const QString& string) const { return new QString(string); } QString StringIpcType::toString(void* slot) const { return *static_cast<QString*>(slot); } void* StringIpcType::createStorage() const { return new QString(); } @@ -84,6 +99,7 @@ void StringIpcType::destroyStorage(void* slot) const { delete static_cast<QStrin // int const char* IntIpcType::name() const { return "int"; } const char* IntIpcType::genericArgumentName() const { return "int"; } +qsizetype IntIpcType::size() const { return sizeof(int); } void* IntIpcType::fromString(const QString& string) const { auto ok = false; @@ -100,6 +116,7 @@ void IntIpcType::destroyStorage(void* slot) const { delete static_cast<int*>(slo // bool const char* BoolIpcType::name() const { return "bool"; } const char* BoolIpcType::genericArgumentName() const { return "bool"; } +qsizetype BoolIpcType::size() const { return sizeof(bool); } void* BoolIpcType::fromString(const QString& string) const { if (string == "true") return new bool(true); @@ -121,6 +138,7 @@ void BoolIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(s // double const char* DoubleIpcType::name() const { return "real"; } const char* DoubleIpcType::genericArgumentName() const { return "double"; } +qsizetype DoubleIpcType::size() const { return sizeof(double); } void* DoubleIpcType::fromString(const QString& string) const { auto ok = false; @@ -139,6 +157,7 @@ void DoubleIpcType::destroyStorage(void* slot) const { delete static_cast<double // color const char* ColorIpcType::name() const { return "color"; } const char* ColorIpcType::genericArgumentName() const { return "QColor"; } +qsizetype ColorIpcType::size() const { return sizeof(QColor); } void* ColorIpcType::fromString(const QString& string) const { auto color = QColor::fromString(string); @@ -167,6 +186,10 @@ QString WireFunctionDefinition::toString() const { return "function " % this->name % '(' % paramString % "): " % this->returnType; } +QString WirePropertyDefinition::toString() const { + return "property " % this->name % ": " % this->type; +} + QString WireTargetDefinition::toString() const { QString accum = "target " % this->name; @@ -174,6 +197,10 @@ QString WireTargetDefinition::toString() const { accum += "\n " % func.toString(); } + for (const auto& prop: this->properties) { + accum += "\n " % prop.toString(); + } + return accum; } diff --git a/src/io/ipc.hpp b/src/io/ipc.hpp index 924f045e..d2b865a2 100644 --- a/src/io/ipc.hpp +++ b/src/io/ipc.hpp @@ -3,6 +3,7 @@ #include <qcontainerfwd.h> #include <qobjectdefs.h> #include <qtclasshelpermacros.h> +#include <qtypes.h> #include "../ipc/ipc.hpp" @@ -21,10 +22,12 @@ public: [[nodiscard]] virtual const char* name() const = 0; [[nodiscard]] virtual const char* genericArgumentName() const = 0; + [[nodiscard]] virtual qsizetype size() const = 0; [[nodiscard]] virtual void* fromString(const QString& /*string*/) const { return nullptr; } [[nodiscard]] virtual QString toString(void* /*slot*/) const { return ""; } [[nodiscard]] virtual void* createStorage() const { return nullptr; } virtual void destroyStorage(void* /*slot*/) const {} + void* copyStorage(const void* data) const; static const IpcType* ipcType(const QMetaType& metaType); }; @@ -43,6 +46,7 @@ public: [[nodiscard]] QGenericReturnArgument asGenericReturnArgument(); void replace(void* value); + void replace(const QVariant& value); private: const IpcType* mType = nullptr; @@ -53,6 +57,7 @@ class VoidIpcType: public IpcType { public: [[nodiscard]] const char* name() const override; [[nodiscard]] const char* genericArgumentName() const override; + [[nodiscard]] qsizetype size() const override; static const VoidIpcType INSTANCE; }; @@ -61,6 +66,7 @@ class StringIpcType: public IpcType { public: [[nodiscard]] const char* name() const override; [[nodiscard]] const char* genericArgumentName() const override; + [[nodiscard]] qsizetype size() const override; [[nodiscard]] void* fromString(const QString& string) const override; [[nodiscard]] QString toString(void* slot) const override; [[nodiscard]] void* createStorage() const override; @@ -73,6 +79,7 @@ class IntIpcType: public IpcType { public: [[nodiscard]] const char* name() const override; [[nodiscard]] const char* genericArgumentName() const override; + [[nodiscard]] qsizetype size() const override; [[nodiscard]] void* fromString(const QString& string) const override; [[nodiscard]] QString toString(void* slot) const override; [[nodiscard]] void* createStorage() const override; @@ -85,6 +92,7 @@ class BoolIpcType: public IpcType { public: [[nodiscard]] const char* name() const override; [[nodiscard]] const char* genericArgumentName() const override; + [[nodiscard]] qsizetype size() const override; [[nodiscard]] void* fromString(const QString& string) const override; [[nodiscard]] QString toString(void* slot) const override; [[nodiscard]] void* createStorage() const override; @@ -97,6 +105,7 @@ class DoubleIpcType: public IpcType { public: [[nodiscard]] const char* name() const override; [[nodiscard]] const char* genericArgumentName() const override; + [[nodiscard]] qsizetype size() const override; [[nodiscard]] void* fromString(const QString& string) const override; [[nodiscard]] QString toString(void* slot) const override; [[nodiscard]] void* createStorage() const override; @@ -109,6 +118,7 @@ class ColorIpcType: public IpcType { public: [[nodiscard]] const char* name() const override; [[nodiscard]] const char* genericArgumentName() const override; + [[nodiscard]] qsizetype size() const override; [[nodiscard]] void* fromString(const QString& string) const override; [[nodiscard]] QString toString(void* slot) const override; [[nodiscard]] void* createStorage() const override; @@ -127,13 +137,23 @@ struct WireFunctionDefinition { DEFINE_SIMPLE_DATASTREAM_OPS(WireFunctionDefinition, data.name, data.returnType, data.arguments); -struct WireTargetDefinition { +struct WirePropertyDefinition { QString name; - QVector<WireFunctionDefinition> functions; + QString type; [[nodiscard]] QString toString() const; }; -DEFINE_SIMPLE_DATASTREAM_OPS(WireTargetDefinition, data.name, data.functions); +DEFINE_SIMPLE_DATASTREAM_OPS(WirePropertyDefinition, data.name, data.type); + +struct WireTargetDefinition { + QString name; + QVector<WireFunctionDefinition> functions; + QVector<WirePropertyDefinition> properties; + + [[nodiscard]] QString toString() const; +}; + +DEFINE_SIMPLE_DATASTREAM_OPS(WireTargetDefinition, data.name, data.functions, data.properties); } // namespace qs::io::ipc diff --git a/src/io/ipccomm.cpp b/src/io/ipccomm.cpp index f2a9118a..7203a307 100644 --- a/src/io/ipccomm.cpp +++ b/src/io/ipccomm.cpp @@ -21,16 +21,17 @@ namespace qs::io::ipc::comm { struct NoCurrentGeneration: std::monostate {}; struct TargetNotFound: std::monostate {}; -struct FunctionNotFound: std::monostate {}; +struct EntryNotFound: std::monostate {}; using QueryResponse = std::variant< std::monostate, NoCurrentGeneration, TargetNotFound, - FunctionNotFound, + EntryNotFound, QVector<WireTargetDefinition>, WireTargetDefinition, - WireFunctionDefinition>; + WireFunctionDefinition, + WirePropertyDefinition>; void QueryMetadataCommand::exec(qs::ipc::IpcServerConnection* conn) const { auto resp = conn->responseStream<QueryResponse>(); @@ -44,16 +45,24 @@ void QueryMetadataCommand::exec(qs::ipc::IpcServerConnection* conn) const { auto* handler = registry->findHandler(this->target); if (handler) { - if (this->function.isEmpty()) { + if (this->name.isEmpty()) { resp << handler->wireDef(); } else { - auto* func = handler->findFunction(this->function); + auto* func = handler->findFunction(this->name); if (func) { resp << func->wireDef(); - } else { - resp << FunctionNotFound(); + return; } + + auto* prop = handler->findProperty(this->name); + + if (prop) { + resp << prop->wireDef(); + return; + } + + resp << EntryNotFound(); } } else { resp << TargetNotFound(); @@ -64,8 +73,8 @@ void QueryMetadataCommand::exec(qs::ipc::IpcServerConnection* conn) const { } } -int queryMetadata(IpcClient* client, const QString& target, const QString& function) { - client->sendMessage(IpcCommand(QueryMetadataCommand {.target = target, .function = function})); +int queryMetadata(IpcClient* client, const QString& target, const QString& name) { + client->sendMessage(IpcCommand(QueryMetadataCommand {.target = target, .name = name})); QueryResponse slot; if (!client->waitForResponse(slot)) return -1; @@ -82,9 +91,11 @@ int queryMetadata(IpcClient* client, const QString& target, const QString& funct qCInfo(logBare).noquote() << std::get<WireTargetDefinition>(slot).toString(); } else if (std::holds_alternative<WireFunctionDefinition>(slot)) { qCInfo(logBare).noquote() << std::get<WireFunctionDefinition>(slot).toString(); + } else if (std::holds_alternative<WirePropertyDefinition>(slot)) { + qCInfo(logBare).noquote() << std::get<WirePropertyDefinition>(slot).toString(); } else if (std::holds_alternative<TargetNotFound>(slot)) { qCCritical(logBare) << "Target not found."; - } else if (std::holds_alternative<FunctionNotFound>(slot)) { + } else if (std::holds_alternative<EntryNotFound>(slot)) { qCCritical(logBare) << "Function not found."; } else if (std::holds_alternative<NoCurrentGeneration>(slot)) { qCCritical(logBare) << "Not ready to accept queries yet."; @@ -119,7 +130,7 @@ using StringCallResponse = std::variant< std::monostate, NoCurrentGeneration, TargetNotFound, - FunctionNotFound, + EntryNotFound, ArgParseFailed, Completed>; @@ -137,7 +148,7 @@ void StringCallCommand::exec(qs::ipc::IpcServerConnection* conn) const { auto* func = handler->findFunction(this->function); if (!func) { - resp << FunctionNotFound(); + resp << EntryNotFound(); return; } @@ -223,7 +234,7 @@ int callFunction( qCCritical(logBare).noquote() << "Function definition:" << error.definition.toString(); } else if (std::holds_alternative<TargetNotFound>(slot)) { qCCritical(logBare) << "Target not found."; - } else if (std::holds_alternative<FunctionNotFound>(slot)) { + } else if (std::holds_alternative<EntryNotFound>(slot)) { qCCritical(logBare) << "Function not found."; } else if (std::holds_alternative<NoCurrentGeneration>(slot)) { qCCritical(logBare) << "Not ready to accept queries yet."; @@ -233,4 +244,74 @@ int callFunction( return -1; } + +struct PropertyValue { + QString value; +}; + +DEFINE_SIMPLE_DATASTREAM_OPS(PropertyValue, data.value); + +using StringPropReadResponse = + std::variant<std::monostate, NoCurrentGeneration, TargetNotFound, EntryNotFound, PropertyValue>; + +void StringPropReadCommand::exec(qs::ipc::IpcServerConnection* conn) const { + auto resp = conn->responseStream<StringPropReadResponse>(); + + if (auto* generation = EngineGeneration::currentGeneration()) { + auto* registry = IpcHandlerRegistry::forGeneration(generation); + + auto* handler = registry->findHandler(this->target); + if (!handler) { + resp << TargetNotFound(); + return; + } + + auto* prop = handler->findProperty(this->property); + if (!prop) { + resp << EntryNotFound(); + return; + } + + auto slot = IpcTypeSlot(prop->type); + prop->read(handler, slot); + + resp << PropertyValue { + .value = slot.type()->toString(slot.get()), + }; + } else { + conn->respond(StringCallResponse(NoCurrentGeneration())); + } +} + +int getProperty(IpcClient* client, const QString& target, const QString& property) { + if (target.isEmpty()) { + qCCritical(logBare) << "Target required to send message."; + return -1; + } else if (property.isEmpty()) { + qCCritical(logBare) << "Property required to send message."; + return -1; + } + + client->sendMessage(IpcCommand(StringPropReadCommand {.target = target, .property = property})); + + StringPropReadResponse slot; + if (!client->waitForResponse(slot)) return -1; + + if (std::holds_alternative<PropertyValue>(slot)) { + auto& result = std::get<PropertyValue>(slot); + QTextStream(stdout) << result.value << Qt::endl; + return 0; + } else if (std::holds_alternative<TargetNotFound>(slot)) { + qCCritical(logBare) << "Target not found."; + } else if (std::holds_alternative<EntryNotFound>(slot)) { + qCCritical(logBare) << "Property not found."; + } else if (std::holds_alternative<NoCurrentGeneration>(slot)) { + qCCritical(logBare) << "Not ready to accept queries yet."; + } else { + qCCritical(logIpc) << "Received invalid IPC response from" << client; + } + + return -1; +} + } // namespace qs::io::ipc::comm diff --git a/src/io/ipccomm.hpp b/src/io/ipccomm.hpp index 69463983..bc7dbf97 100644 --- a/src/io/ipccomm.hpp +++ b/src/io/ipccomm.hpp @@ -2,6 +2,7 @@ #include <qcontainerfwd.h> #include <qflags.h> +#include <qtypes.h> #include "../ipc/ipc.hpp" @@ -9,12 +10,12 @@ namespace qs::io::ipc::comm { struct QueryMetadataCommand { QString target; - QString function; + QString name; void exec(qs::ipc::IpcServerConnection* conn) const; }; -DEFINE_SIMPLE_DATASTREAM_OPS(QueryMetadataCommand, data.target, data.function); +DEFINE_SIMPLE_DATASTREAM_OPS(QueryMetadataCommand, data.target, data.name); struct StringCallCommand { QString target; @@ -27,7 +28,7 @@ struct StringCallCommand { DEFINE_SIMPLE_DATASTREAM_OPS(StringCallCommand, data.target, data.function, data.arguments); void handleMsg(qs::ipc::IpcServerConnection* conn); -int queryMetadata(qs::ipc::IpcClient* client, const QString& target, const QString& function); +int queryMetadata(qs::ipc::IpcClient* client, const QString& target, const QString& name); int callFunction( qs::ipc::IpcClient* client, @@ -36,4 +37,15 @@ int callFunction( const QVector<QString>& arguments ); +struct StringPropReadCommand { + QString target; + QString property; + + void exec(qs::ipc::IpcServerConnection* conn) const; +}; + +DEFINE_SIMPLE_DATASTREAM_OPS(StringPropReadCommand, data.target, data.property); + +int getProperty(qs::ipc::IpcClient* client, const QString& target, const QString& property); + } // namespace qs::io::ipc::comm diff --git a/src/io/ipchandler.cpp b/src/io/ipchandler.cpp index 510b2055..517e4505 100644 --- a/src/io/ipchandler.cpp +++ b/src/io/ipchandler.cpp @@ -107,6 +107,32 @@ WireFunctionDefinition IpcFunction::wireDef() const { return wire; } +bool IpcProperty::resolve(QString& error) { + this->type = IpcType::ipcType(this->property.metaType()); + + if (!this->type) { + error = QString("Type %1 cannot be used across IPC.").arg(this->property.metaType().name()); + return false; + } + + return true; +} + +void IpcProperty::read(QObject* target, IpcTypeSlot& slot) const { + slot.replace(this->property.read(target)); +} + +QString IpcProperty::toString() const { + return QString("property ") % this->property.name() % ": " % this->type->name(); +} + +WirePropertyDefinition IpcProperty::wireDef() const { + WirePropertyDefinition wire; + wire.name = this->property.name(); + wire.type = this->type->name(); + return wire; +} + IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function.returnType) { for (const auto& arg: function.argumentTypes) { this->argumentSlots.emplace_back(arg); @@ -153,6 +179,21 @@ void IpcHandler::onPostReload() { } } + for (auto i = smeta.propertyCount(); i != meta->propertyCount(); i++) { + const auto& property = meta->property(i); + if (!property.isReadable() || !property.hasNotifySignal()) continue; + + auto ipcProp = IpcProperty(property); + QString error; + + if (!ipcProp.resolve(error)) { + qmlWarning(this).nospace().noquote() + << "Error parsing property \"" << property.name() << "\": " << error; + } else { + this->propertyMap.insert(property.name(), ipcProp); + } + } + this->complete = true; this->updateRegistration(); @@ -270,6 +311,10 @@ WireTargetDefinition IpcHandler::wireDef() const { wire.functions += func.wireDef(); } + for (const auto& prop: this->propertyMap.values()) { + wire.properties += prop.wireDef(); + } + return wire; } @@ -307,6 +352,13 @@ IpcFunction* IpcHandler::findFunction(const QString& name) { else return &*itr; } +IpcProperty* IpcHandler::findProperty(const QString& name) { + auto itr = this->propertyMap.find(name); + + if (itr == this->propertyMap.end()) return nullptr; + else return &*itr; +} + IpcHandler* IpcHandlerRegistry::findHandler(const QString& target) { return this->handlers.value(target); } diff --git a/src/io/ipchandler.hpp b/src/io/ipchandler.hpp index cc4ee5f4..e6b24ba1 100644 --- a/src/io/ipchandler.hpp +++ b/src/io/ipchandler.hpp @@ -53,14 +53,28 @@ private: friend class IpcFunction; }; +class IpcProperty { +public: + explicit IpcProperty(QMetaProperty property): property(property) {} + + bool resolve(QString& error); + void read(QObject* target, IpcTypeSlot& slot) const; + + [[nodiscard]] QString toString() const; + [[nodiscard]] WirePropertyDefinition wireDef() const; + + QMetaProperty property; + const IpcType* type = nullptr; +}; + class IpcHandlerRegistry; ///! Handler for IPC message calls. /// Each IpcHandler is registered into a per-instance map by its unique @@target. -/// Functions defined on the IpcHandler can be called by `qs msg`. +/// Functions and properties defined on the IpcHandler can be accessed via `qs ipc`. /// /// #### Handler Functions -/// IPC handler functions can be called by `qs msg` as long as they have at most 10 +/// IPC handler functions can be called by `qs ipc call` as long as they have at most 10 /// arguments, and all argument types along with the return type are listed below. /// /// **Argument and return types must be explicitly specified or they will not @@ -112,9 +126,9 @@ class IpcHandlerRegistry; /// } /// } /// ``` -/// The list of registered targets can be inspected using `qs msg -s`. +/// The list of registered targets can be inspected using `qs ipc show`. /// ```sh -/// $ qs msg -s +/// $ qs ipc show /// target rect /// function setColor(color: color): void /// function getColor(): color @@ -124,18 +138,22 @@ class IpcHandlerRegistry; /// function getRadius(): int /// ``` /// -/// and then invoked using `qs msg`. +/// and then invoked using `qs ipc call`. /// ```sh -/// $ qs msg rect setColor orange -/// $ qs msg rect setAngle 40.5 -/// $ qs msg rect setRadius 30 -/// $ qs msg rect getColor +/// $ qs ipc call rect setColor orange +/// $ qs ipc call rect setAngle 40.5 +/// $ qs ipc call rect setRadius 30 +/// $ qs ipc call rect getColor /// #ffffa500 -/// $ qs msg rect getAngle +/// $ qs ipc call rect getAngle /// 40.5 -/// $ qs msg rect getRadius +/// $ qs ipc call rect getRadius /// 30 /// ``` +/// +/// #### Properties +/// Properties of an IpcHanlder can be read using `qs ipc prop get` as long as they are +/// of an IPC compatible type. See the table above for compatible types. class IpcHandler : public QObject , public PostReloadHook { @@ -162,12 +180,16 @@ public: QString listMembers(qsizetype indent); [[nodiscard]] IpcFunction* findFunction(const QString& name); + [[nodiscard]] IpcProperty* findProperty(const QString& name); [[nodiscard]] WireTargetDefinition wireDef() const; signals: void enabledChanged(); void targetChanged(); +private slots: + //void handleIpcPropertyChange(); + private: void updateRegistration(bool destroying = false); @@ -183,6 +205,7 @@ private: bool complete = false; QHash<QString, IpcFunction> functionMap; + QHash<QString, IpcProperty> propertyMap; friend class IpcHandlerRegistry; }; diff --git a/src/ipc/ipccommand.hpp b/src/ipc/ipccommand.hpp index c2e5059f..b221b460 100644 --- a/src/ipc/ipccommand.hpp +++ b/src/ipc/ipccommand.hpp @@ -15,6 +15,7 @@ using IpcCommand = std::variant< std::monostate, IpcKillCommand, qs::io::ipc::comm::QueryMetadataCommand, - qs::io::ipc::comm::StringCallCommand>; + qs::io::ipc::comm::StringCallCommand, + qs::io::ipc::comm::StringPropReadCommand>; } // namespace qs::ipc diff --git a/src/launch/command.cpp b/src/launch/command.cpp index f814b5ff..00ad613e 100644 --- a/src/launch/command.cpp +++ b/src/launch/command.cpp @@ -293,6 +293,8 @@ int ipcCommand(CommandState& cmd) { return IpcClient::connect(instance.instance.instanceId, [&](IpcClient& client) { if (*cmd.ipc.show || cmd.ipc.showOld) { return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.name); + } else if (*cmd.ipc.getprop) { + return qs::io::ipc::comm::getProperty(&client, *cmd.ipc.target, *cmd.ipc.name); } else { QVector<QString> arguments; for (auto& arg: cmd.ipc.arguments) { diff --git a/src/launch/launch_p.hpp b/src/launch/launch_p.hpp index a9a515c4..77808450 100644 --- a/src/launch/launch_p.hpp +++ b/src/launch/launch_p.hpp @@ -71,6 +71,7 @@ struct CommandState { CLI::App* ipc = nullptr; CLI::App* show = nullptr; CLI::App* call = nullptr; + CLI::App* getprop = nullptr; bool showOld = false; QStringOption target; QStringOption name; diff --git a/src/launch/parsecommand.cpp b/src/launch/parsecommand.cpp index 2c082fec..1edbf01e 100644 --- a/src/launch/parsecommand.cpp +++ b/src/launch/parsecommand.cpp @@ -194,6 +194,18 @@ int parseCommand(int argc, char** argv, CommandState& state) { ->description("Arguments to the called function.") ->allow_extra_args(); } + + { + auto* prop = + sub->add_subcommand("prop", "Manipulate IpcHandler properties.")->require_subcommand(); + + { + auto* get = prop->add_subcommand("get", "Read the value of a property."); + state.ipc.getprop = get; + get->add_option("target", state.ipc.target, "The target to read the property of."); + get->add_option("property", state.ipc.name)->description("The property to read."); + } + } } {