#pragma once #include #include #include #include #include #include #include #include "datastream.hpp" #include "reload.hpp" ///! Unix socket listener. class Socket: public DataStream { Q_OBJECT; /// Returns if the socket is currently connected. /// /// Writing to this property will set the target connection state and will not /// update the property immediately. Setting the property to false will begin disconnecting /// the socket, and setting it to true will begin connecting the socket if path is not empty. Q_PROPERTY(bool connected READ isConnected WRITE setConnected NOTIFY connectionStateChanged); /// The path to connect this socket to when `connected` is set to true. /// /// Changing this property will have no effect while the connection is active. Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged); QML_ELEMENT; public: explicit Socket(QObject* parent = nullptr): DataStream(parent) {} /// Write data to the socket. Does nothing if not connected. Q_INVOKABLE void write(const QString& data); // takes ownership void setSocket(QLocalSocket* socket); [[nodiscard]] bool isConnected() const; void setConnected(bool connected); [[nodiscard]] QString path() const; void setPath(QString path); signals: /// This signal is sent whenever a socket error is encountered. void error(QLocalSocket::LocalSocketError error); void connectionStateChanged(); void pathChanged(); protected: [[nodiscard]] QIODevice* ioDevice() const override; private slots: void onSocketConnected(); void onSocketDisconnected(); private: void connectPathSocket(); QLocalSocket* socket = nullptr; bool connected = false; bool disconnecting = false; bool targetConnected = false; QString mPath; }; ///! Unix socket server. /// #### Example /// ```qml /// SocketServer { /// active: true /// path: "/path/too/socket.sock" /// handler: Socket { /// onConnectedChanged: { /// console.log(connected ? "new connection!" : "connection dropped!") /// } /// parser: SplitParser { /// onRead: message => console.log(`read message from socket: ${message}`) /// } /// } /// } /// ``` class SocketServer : public QObject , public PostReloadHook { Q_OBJECT; /// If the socket server is currently active. Defaults to false. /// /// Setting this to false will destory all active connections and delete /// the socket file on disk. /// /// If path is empty setting this property will have no effect. Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeStatusChanged); /// The path to create the socket server at. /// /// Setting this property while the server is active will have no effect. Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged); /// Connection handler component. Must creeate a `Socket`. /// /// The created socket should not set `connected` or `path` or the incoming /// socket connection will be dropped (they will be set by the socket server.) /// Setting `connected` to false on the created socket after connection will /// close and delete it. Q_PROPERTY(QQmlComponent* handler READ handler WRITE setHandler NOTIFY handlerChanged); QML_ELEMENT; public: explicit SocketServer(QObject* parent = nullptr): QObject(parent) {} ~SocketServer() override; Q_DISABLE_COPY_MOVE(SocketServer); void onPostReload() override; [[nodiscard]] bool isActive() const; void setActive(bool active); [[nodiscard]] QString path() const; void setPath(QString path); [[nodiscard]] QQmlComponent* handler() const; void setHandler(QQmlComponent* handler); signals: void activeStatusChanged(); void pathChanged(); void handlerChanged(); private slots: void onNewConnection(); private: bool isActivatable(); void enableServer(); void disableServer(); QLocalServer* server = nullptr; QQmlComponent* mHandler = nullptr; QList mSockets; bool activeTarget = false; bool postReload = false; QString mPath; };