#pragma once // app/vpn/dragonrelaybackend.h // // DragonRelayBackend is the QObject that QML binds to via the context property // "dragonRelay". It owns a RelayClient and a TunnelManager, coordinates the // login → VPN provision → tunnel-up → host-discovery workflow, and exposes // everything QML needs through Q_PROPERTY / Q_INVOKABLE. // // Registration in main.cpp (or wherever the QML engine is created): // // DragonRelayBackend *backend = new DragonRelayBackend(engine); // engine->rootContext()->setContextProperty("dragonRelay", backend); // // QML usage: // // dragonRelay.connectRelay(url, user, pass) // dragonRelay.disconnectRelay() // dragonRelay.streamHost(ip, app) // dragonRelay.status // int — DragonRelayBackend::Status enum // dragonRelay.statusText // QString // dragonRelay.hosts // QVariantList of {name, ip, port, online, source} // dragonRelay.tunnelIP // QString — local WG address (e.g. "10.99.0.2") #ifndef DRAGONRELAYBACKEND_H #define DRAGONRELAYBACKEND_H #include #include #include #include #include #include #include "relayclient.h" #include "tunnelmanager.h" #include "wireguardconfig.h" class DragonRelayBackend : public QObject { Q_OBJECT // ── Properties exposed to QML ───────────────────────────────────────────── Q_PROPERTY(int status READ status NOTIFY statusChanged) Q_PROPERTY(QString statusText READ statusText NOTIFY statusChanged) Q_PROPERTY(QVariantList hosts READ hosts NOTIFY hostsChanged) Q_PROPERTY(QString tunnelIP READ tunnelIP NOTIFY tunnelIPChanged) public: // Status codes (exposed to QML as dragonRelay.StatusXxx or raw int) enum Status { Disconnected = 0, Connecting = 1, // logging in / provisioning VPN peer TunnelUp = 2, // WireGuard tunnel active, hosts loading Ready = 3, // tunnel up + at least one host visible Error = 4, }; Q_ENUM(Status) explicit DragonRelayBackend(QObject *parent = nullptr); ~DragonRelayBackend() override; // ── Property accessors ──────────────────────────────────────────────────── int status() const { return m_status; } QString statusText() const { return m_statusText; } QVariantList hosts() const { return m_hosts; } QString tunnelIP() const { return m_tunnelIP; } public slots: // ── Invokable from QML ──────────────────────────────────────────────────── // Begin the full connect workflow: login → provision VPN → start tunnel → poll hosts. // Idempotent if already connected. Q_INVOKABLE void connectRelay(const QString &url, const QString &username, const QString &password); // Tear down tunnel, unregister peer, return to Disconnected state. Q_INVOKABLE void disconnectRelay(); // Ask Moonlight to stream to a host discovered via the relay. Q_INVOKABLE void streamHost(const QString &ip, const QString &app = QStringLiteral("Desktop")); // Refresh the host list immediately (called by QML pull-to-refresh). Q_INVOKABLE void refreshHosts(); signals: void statusChanged(); void hostsChanged(); void tunnelIPChanged(); private slots: // RelayClient signals void onLoginDone(bool ok, const QString &err); void onVPNPeerProvisioned(const RelayVPNConf &conf); void onVPNError(const QString &err); void onHostsFetched(const QList &hosts); void onHostsError(const QString &err); // TunnelManager signals void onTunnelUp(const QString &localIP); void onTunnelDown(); void onTunnelError(const QString &err); // Timers void pollHosts(); private: void setStatus(Status s, const QString &text = {}); void startHostPoll(); void stopHostPoll(); RelayClient m_relay; TunnelManager m_tunnel; QTimer m_hostPollTimer; Status m_status = Disconnected; QString m_statusText = QStringLiteral("Not connected"); QVariantList m_hosts; QString m_tunnelIP; QString m_savedURL; QString m_savedUser; QString m_savedPass; QSettings m_settings; // persists last-used URL + username }; #endif // DRAGONRELAYBACKEND_H