#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../core/generation.hpp" #include "../core/reload.hpp" #include "ipc.hpp" namespace qs::io::ipc { class IpcCallStorage; class IpcFunction { public: explicit IpcFunction(QMetaMethod method): method(method) {} bool resolve(QString& error); void invoke(QObject* target, IpcCallStorage& storage) const; [[nodiscard]] QString toString() const; [[nodiscard]] WireFunctionDefinition wireDef() const; QMetaMethod method; QVector argumentTypes; const IpcType* returnType = nullptr; }; class IpcCallStorage { public: explicit IpcCallStorage(const IpcFunction& function); bool setArgumentStr(size_t i, const QString& value); [[nodiscard]] QString getReturnStr(); private: std::vector argumentSlots; IpcTypeSlot returnSlot; 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 IpcSignalListener: public QObject { Q_OBJECT; public: IpcSignalListener(QString signal): signal(std::move(signal)) {} static const int SLOT_VOID; static const int SLOT_STRING; static const int SLOT_INT; static const int SLOT_BOOL; static const int SLOT_REAL; static const int SLOT_COLOR; signals: void triggered(const QString& signal, const QString& value); private slots: void invokeVoid() { this->triggered(this->signal, "void"); } void invokeString(const QString& value) { this->triggered(this->signal, value); } void invokeInt(int value) { this->triggered(this->signal, QString::number(value)); } void invokeBool(bool value) { this->triggered(this->signal, value ? "true" : "false"); } void invokeReal(double value) { this->triggered(this->signal, QString::number(value)); } void invokeColor(QColor value) { this->triggered(this->signal, value.name(QColor::HexArgb)); } private: QString signal; }; class IpcHandler; class IpcSignal { public: explicit IpcSignal(QMetaMethod signal): signal(signal) {} bool resolve(QString& error); [[nodiscard]] WireSignalDefinition wireDef() const; QMetaMethod signal; int targetSlot = -1; void connectListener(IpcHandler* handler); private: void connectListener(QObject* handler, IpcSignalListener* listener) const; std::shared_ptr listener; }; class IpcHandlerRegistry; ///! Handler for IPC message calls. /// Each IpcHandler is registered into a per-instance map by its unique @@target. /// Functions and properties defined on the IpcHandler can be accessed via `qs ipc`. /// /// #### Handler Functions /// 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 /// be registered.** /// /// ##### Arguments /// - `string` will be passed to the parameter as is. /// - `int` will only accept parameters that can be parsed as an integer. /// - `bool` will only accept parameters that are "true", "false", or an integer, /// where 0 will be converted to false, and anything else to true. /// - `real` will only accept parameters that can be parsed as a number with /// or without a decimal. /// - `color` will accept [named colors] or hex strings (RGB, RRGGBB, AARRGGBB) with /// an optional `#` prefix. /// /// [named colors]: https://doc.qt.io/qt-6/qml-color.html#svg-color-reference /// /// ##### Return Type /// - `void` will return nothing. /// - `string` will be returned as is. /// - `int` will be converted to a string and returned. /// - `bool` will be converted to "true" or "false" and returned. /// - `real` will be converted to a string and returned. /// - `color` will be converted to a hex string in the form `#AARRGGBB` and returned. /// /// #### Signals /// IPC handler signals can be observed remotely using `qs ipc wait` (one call) /// and `qs ipc listen` (many calls). IPC signals may have zero or one argument, where /// the argument is one of the types listed above, or no arguments for void. /// /// #### Example /// The following example creates ipc functions to control and retrieve the appearance /// of a Rectangle. /// /// ```qml /// FloatingWindow { /// Rectangle { /// id: rect /// anchors.centerIn: parent /// width: 100 /// height: 100 /// color: "red" /// } /// /// IpcHandler { /// target: "rect" /// /// function setColor(color: color): void { rect.color = color; } /// function getColor(): color { return rect.color; } /// /// function setAngle(angle: real): void { rect.rotation = angle; } /// function getAngle(): real { return rect.rotation; } /// /// function setRadius(radius: int): void { /// rect.radius = radius; /// this.radiusChanged(radius); /// } /// /// function getRadius(): int { return rect.radius; } /// /// signal radiusChanged(newRadius: int); /// } /// } /// ``` /// The list of registered targets can be inspected using `qs ipc show`. /// ```sh /// $ qs ipc show /// target rect /// function setColor(color: color): void /// function getColor(): color /// function setAngle(angle: real): void /// function getAngle(): real /// function setRadius(radius: int): void /// function getRadius(): int /// signal radiusChanged(newRadius: int) /// ``` /// /// and then invoked using `qs ipc call`. /// ```sh /// $ 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 ipc call rect getAngle /// 40.5 /// $ 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 PostReloadHook { Q_OBJECT; /// If the handler should be able to receive calls. Defaults to true. Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged); /// The target this handler should be accessible from. /// Required and must be unique. May be changed at runtime. Q_PROPERTY(QString target READ target WRITE setTarget NOTIFY targetChanged); QML_ELEMENT; public: explicit IpcHandler(QObject* parent = nullptr): PostReloadHook(parent) {} ~IpcHandler() override; Q_DISABLE_COPY_MOVE(IpcHandler); void onPostReload() override; [[nodiscard]] bool enabled() const; void setEnabled(bool enabled); [[nodiscard]] QString target() const; void setTarget(const QString& target); QString listMembers(qsizetype indent); [[nodiscard]] IpcFunction* findFunction(const QString& name); [[nodiscard]] IpcProperty* findProperty(const QString& name); [[nodiscard]] IpcSignal* findSignal(const QString& name); [[nodiscard]] WireTargetDefinition wireDef() const; signals: void enabledChanged(); void targetChanged(); public slots: void onSignalTriggered(const QString& signal, const QString& value) const; private: void updateRegistration(bool destroying = false); struct RegistrationState { explicit RegistrationState(bool enabled = false): enabled(enabled) {} bool enabled = false; QString target; }; RegistrationState registeredState; RegistrationState targetState {true}; bool complete = false; QHash functionMap; QHash propertyMap; QHash signalMap; friend class IpcHandlerRegistry; }; class IpcHandlerRegistry: public EngineGenerationExt { public: static IpcHandlerRegistry* forGeneration(EngineGeneration* generation); void registerHandler(IpcHandler* handler); void deregisterHandler(IpcHandler* handler); QString listMembers(const QString& target, qsizetype indent); QString listTargets(qsizetype indent); IpcHandler* findHandler(const QString& target); [[nodiscard]] QVector wireTargets() const; private: QHash handlers; QHash> knownHandlers; }; class IpcSignalRemoteListener: public QObject { Q_OBJECT; public: static IpcSignalRemoteListener* instance(); signals: void triggered(const QString& target, const QString& signal, const QString& value); }; } // namespace qs::io::ipc