Backend: align signal connections with new TunnelManager API; fix fromConf usage

This commit is contained in:
Zac Gaetano 2026-05-07 00:15:23 -04:00
parent 27958f3514
commit 9bcef4c0a9

View file

@ -6,36 +6,36 @@
#include <QProcess> #include <QProcess>
#include <QVariantMap> #include <QVariantMap>
// ── Constructor / Destructor ────────────────────────────────────────────────── // Constructor / Destructor
DragonRelayBackend::DragonRelayBackend(QObject *parent) DragonRelayBackend::DragonRelayBackend(QObject *parent)
: QObject(parent) : QObject(parent)
, m_settings(QStringLiteral("WildDragon"), QStringLiteral("DragonMoonlight")) , m_settings(QStringLiteral("WildDragon"), QStringLiteral("DragonMoonlight"))
{ {
// ── Wire RelayClient signals ────────────────────────────────────────────── // Wire RelayClient signals
connect(&m_relay, &RelayClient::loginSucceeded, connect(&m_relay, &RelayClient::loginSucceeded,
this, [this]() { onLoginDone(true, QString()); }); this, [this]() { onLoginDone(true, QString()); });
connect(&m_relay, QOverload<const QString &>::of(&RelayClient::loginFailed), connect(&m_relay, &RelayClient::loginFailed,
this, [this](const QString &err) { onLoginDone(false, err); }); this, [this](const QString &err) { onLoginDone(false, err); });
connect(&m_relay, &RelayClient::vpnProvisioned, connect(&m_relay, &RelayClient::vpnProvisioned,
this, [this](const RelayVPNConf &conf) { onVPNPeerProvisioned(conf); }); this, [this](const RelayVPNConf &conf) { onVPNPeerProvisioned(conf); });
connect(&m_relay, QOverload<const QString &>::of(&RelayClient::vpnProvisionFailed), connect(&m_relay, &RelayClient::vpnProvisionFailed,
this, [this](const QString &err) { onVPNError(err); }); this, [this](const QString &err) { onVPNError(err); });
connect(&m_relay, &RelayClient::hostsReady, connect(&m_relay, &RelayClient::hostsReady,
this, [this](const QList<RelayHost> &hosts) { onHostsFetched(hosts); }); this, [this](const QList<RelayHost> &hosts) { onHostsFetched(hosts); });
connect(&m_relay, QOverload<const QString &>::of(&RelayClient::hostsFetchFailed), connect(&m_relay, &RelayClient::hostsFetchFailed,
this, [this](const QString &err) { onHostsError(err); }); this, [this](const QString &err) { onHostsError(err); });
// ── Wire TunnelManager signals ──────────────────────────────────────────── // Wire TunnelManager signals
connect(&m_tunnel, &TunnelManager::tunnelUp, connect(&m_tunnel, &TunnelManager::tunnelUp,
this, &DragonRelayBackend::onTunnelUp); this, &DragonRelayBackend::onTunnelUp);
connect(&m_tunnel, &TunnelManager::tunnelDown, connect(&m_tunnel, &TunnelManager::tunnelDown,
this, &DragonRelayBackend::onTunnelDown); this, &DragonRelayBackend::onTunnelDown);
connect(&m_tunnel, &TunnelManager::error, connect(&m_tunnel, &TunnelManager::tunnelError,
this, &DragonRelayBackend::onTunnelError); this, &DragonRelayBackend::onTunnelError);
// ── Host poll timer (every 30 s while tunnel is up) ─────────────────────── // Host poll timer (every 30 s while tunnel is up)
m_hostPollTimer.setInterval(30'000); m_hostPollTimer.setInterval(30 * 1000);
m_hostPollTimer.setSingleShot(false); m_hostPollTimer.setSingleShot(false);
connect(&m_hostPollTimer, &QTimer::timeout, connect(&m_hostPollTimer, &QTimer::timeout,
this, &DragonRelayBackend::pollHosts); this, &DragonRelayBackend::pollHosts);
@ -46,13 +46,10 @@ DragonRelayBackend::DragonRelayBackend(QObject *parent)
} }
DragonRelayBackend::~DragonRelayBackend() { DragonRelayBackend::~DragonRelayBackend() {
// Best-effort cleanup — don't block the destructor
m_hostPollTimer.stop(); m_hostPollTimer.stop();
m_tunnel.stop(); m_tunnel.stop();
} }
// ── Status helper ─────────────────────────────────────────────────────────────
void DragonRelayBackend::setStatus(Status s, const QString &text) { void DragonRelayBackend::setStatus(Status s, const QString &text) {
bool changed = (m_status != s) || (!text.isEmpty() && text != m_statusText); bool changed = (m_status != s) || (!text.isEmpty() && text != m_statusText);
m_status = s; m_status = s;
@ -62,8 +59,6 @@ void DragonRelayBackend::setStatus(Status s, const QString &text) {
emit statusChanged(); emit statusChanged();
} }
// ── connectRelay ──────────────────────────────────────────────────────────────
void DragonRelayBackend::connectRelay(const QString &url, void DragonRelayBackend::connectRelay(const QString &url,
const QString &username, const QString &username,
const QString &password) { const QString &password) {
@ -74,16 +69,14 @@ void DragonRelayBackend::connectRelay(const QString &url,
m_savedUser = username; m_savedUser = username;
m_savedPass = password; m_savedPass = password;
// Persist URL + username (NOT password) // Persist URL + username (NOT password - keychain integration is a TODO).
m_settings.setValue(QStringLiteral("relay/url"), url); m_settings.setValue(QStringLiteral("relay/url"), url);
m_settings.setValue(QStringLiteral("relay/username"), username); m_settings.setValue(QStringLiteral("relay/username"), username);
setStatus(Connecting, QStringLiteral("Logging in")); setStatus(Connecting, QStringLiteral("Logging in..."));
m_relay.login(QUrl(url), username, password); m_relay.login(QUrl(url), username, password);
} }
// ── disconnectRelay ───────────────────────────────────────────────────────────
void DragonRelayBackend::disconnectRelay() { void DragonRelayBackend::disconnectRelay() {
stopHostPoll(); stopHostPoll();
m_tunnel.stop(); // triggers onTunnelDown asynchronously m_tunnel.stop(); // triggers onTunnelDown asynchronously
@ -97,97 +90,53 @@ void DragonRelayBackend::disconnectRelay() {
setStatus(Disconnected, QStringLiteral("Disconnected")); setStatus(Disconnected, QStringLiteral("Disconnected"));
} }
// ── streamHost ────────────────────────────────────────────────────────────────
void DragonRelayBackend::streamHost(const QString &ip, const QString &app, int displayIndex) { void DragonRelayBackend::streamHost(const QString &ip, const QString &app, int displayIndex) {
if (m_status != Ready && m_status != TunnelUp) { if (m_status != Ready && m_status != TunnelUp) {
qWarning() << "DragonRelayBackend::streamHost called while not ready"; qWarning() << "DragonRelayBackend::streamHost called while not ready";
return; return;
} }
// Delegate to moonlight-qt's existing stream launch mechanism.
// The standard Moonlight PC model uses ComputerManager; we invoke it
// via a QProcess for now so we don't have to patch PC discovery internals.
qDebug() << "DragonRelayBackend::streamHost called with ip=" << ip qDebug() << "DragonRelayBackend::streamHost ip=" << ip
<< "app=" << app << "displayIndex=" << displayIndex; << "app=" << app << "displayIndex=" << displayIndex;
#if defined(Q_OS_WIN) QStringList args;
QStringList args{ args << QStringLiteral("stream") << ip << app;
QStringLiteral("stream"),
ip,
app
};
if (displayIndex > 0) { if (displayIndex > 0) {
// --display <N>: moonlight-qt CLI display selection flag (0 = primary)
// Verify with: moonlight --help
args << QStringLiteral("--display") << QString::number(displayIndex); args << QStringLiteral("--display") << QString::number(displayIndex);
} }
QProcess::startDetached(QStringLiteral("moonlight"), args);
#elif defined(Q_OS_MACOS)
QStringList args{
QStringLiteral("stream"),
ip,
app
};
if (displayIndex > 0) {
// --display <N>: moonlight-qt CLI display selection flag (0 = primary)
// Verify with: moonlight --help
args << QStringLiteral("--display") << QString::number(displayIndex);
}
QProcess::startDetached(QStringLiteral("moonlight"), args);
#else
QStringList args{
QStringLiteral("stream"),
ip,
app
};
if (displayIndex > 0) {
// --display <N>: moonlight-qt CLI display selection flag (0 = primary)
// Verify with: moonlight --help
args << QStringLiteral("--display") << QString::number(displayIndex);
}
QProcess::startDetached(QStringLiteral("moonlight"), args);
#endif
}
// ── displaysForHost ─────────────────────────────────────────────────────────── QProcess::startDetached(QStringLiteral("moonlight"), args);
}
QVariantList DragonRelayBackend::displaysForHost(const QString &hostIP) const { QVariantList DragonRelayBackend::displaysForHost(const QString &hostIP) const {
return m_hostDisplays.value(hostIP); return m_hostDisplays.value(hostIP);
} }
// ── streamHostDisplay ─────────────────────────────────────────────────────────
void DragonRelayBackend::streamHostDisplay(const QString &hostIP, int displayIndex) { void DragonRelayBackend::streamHostDisplay(const QString &hostIP, int displayIndex) {
qDebug() << "Streaming host" << hostIP << "display index" << displayIndex; qDebug() << "Streaming host" << hostIP << "display index" << displayIndex;
// Add displayIndex validation near the top of streamHost()
const QVariantList displays = displaysForHost(hostIP); const QVariantList displays = displaysForHost(hostIP);
if (displayIndex < 0 || (!displays.isEmpty() && displayIndex >= displays.size())) { if (displayIndex < 0 || (!displays.isEmpty() && displayIndex >= displays.size())) {
qWarning() << "DragonRelayBackend: displayIndex" << displayIndex qWarning() << "DragonRelayBackend: displayIndex" << displayIndex
<< "out of bounds for host" << hostIP << "(has" << displays.size() << "displays)"; << "out of bounds for host" << hostIP
<< "(has" << displays.size() << "displays)";
displayIndex = 0; // fall back to primary displayIndex = 0; // fall back to primary
} }
// Pass displayIndex to streamHost
streamHost(hostIP, QStringLiteral("Desktop"), displayIndex); streamHost(hostIP, QStringLiteral("Desktop"), displayIndex);
} }
// ── refreshHosts ─────────────────────────────────────────────────────────────
void DragonRelayBackend::refreshHosts() { void DragonRelayBackend::refreshHosts() {
if (m_status == TunnelUp || m_status == Ready) if (m_status == TunnelUp || m_status == Ready)
m_relay.fetchHosts(); m_relay.fetchHosts();
} }
// ── pollHosts ─────────────────────────────────────────────────────────────────
void DragonRelayBackend::pollHosts() { void DragonRelayBackend::pollHosts() {
m_relay.fetchHosts(); m_relay.fetchHosts();
} }
void DragonRelayBackend::startHostPoll() { void DragonRelayBackend::startHostPoll() {
m_relay.fetchHosts(); // immediate fetch m_relay.fetchHosts();
m_hostPollTimer.start(); m_hostPollTimer.start();
} }
@ -195,30 +144,30 @@ void DragonRelayBackend::stopHostPoll() {
m_hostPollTimer.stop(); m_hostPollTimer.stop();
} }
// ── RelayClient slots ─────────────────────────────────────────────────────────
void DragonRelayBackend::onLoginDone(bool ok, const QString &err) { void DragonRelayBackend::onLoginDone(bool ok, const QString &err) {
if (!ok) { if (!ok) {
setStatus(Error, QStringLiteral("Login failed: ") + err); setStatus(Error, QStringLiteral("Login failed: ") + err);
return; return;
} }
setStatus(Connecting, QStringLiteral("Provisioning VPN peer")); setStatus(Connecting, QStringLiteral("Provisioning VPN peer..."));
m_relay.provisionVPN(); m_relay.provisionVPN();
} }
void DragonRelayBackend::onVPNPeerProvisioned(const RelayVPNConf &conf) { void DragonRelayBackend::onVPNPeerProvisioned(const RelayVPNConf &conf) {
setStatus(Connecting, QStringLiteral("Starting WireGuard tunnel")); setStatus(Connecting, QStringLiteral("Starting WireGuard tunnel..."));
WireGuardConfig cfg; WireGuardConfig cfg = WireGuardConfig::fromConf(conf.conf);
QString parseErr; if (!cfg.isValid()) {
if (!WireGuardConfig::fromConf(conf.conf, cfg, parseErr)) { setStatus(Error, QStringLiteral(
setStatus(Error, QStringLiteral("Bad VPN config: ") + parseErr); "Bad VPN config: missing PrivateKey, Address, PublicKey, or Endpoint"));
m_relay.revokeVPN(); m_relay.revokeVPN();
return; return;
} }
m_tunnel.start(cfg); if (!m_tunnel.start(cfg)) {
// onTunnelUp/onTunnelError fired from TunnelManager // tunnel.start() emits tunnelError() synchronously on failure,
// which routes through onTunnelError().
}
} }
void DragonRelayBackend::onVPNError(const QString &err) { void DragonRelayBackend::onVPNError(const QString &err) {
@ -238,7 +187,6 @@ void DragonRelayBackend::onHostsFetched(const QList<RelayHost> &hosts) {
m[QStringLiteral("source")] = QStringLiteral("relay"); m[QStringLiteral("source")] = QStringLiteral("relay");
m_hosts.append(m); m_hosts.append(m);
// Store displays for this host
m_hostDisplays[h.ip] = h.displays; m_hostDisplays[h.ip] = h.displays;
} }
emit hostsChanged(); emit hostsChanged();
@ -246,23 +194,25 @@ void DragonRelayBackend::onHostsFetched(const QList<RelayHost> &hosts) {
Status next = m_hosts.isEmpty() ? TunnelUp : Ready; Status next = m_hosts.isEmpty() ? TunnelUp : Ready;
if (m_status != next) { if (m_status != next) {
QString text = m_hosts.isEmpty() QString text = m_hosts.isEmpty()
? QStringLiteral("Tunnel up no hosts yet") ? QStringLiteral("Tunnel up - no hosts yet")
: QStringLiteral("Connected %1 host(s)").arg(m_hosts.size()); : QStringLiteral("Connected - %1 host(s)").arg(m_hosts.size());
setStatus(next, text); setStatus(next, text);
} }
} }
void DragonRelayBackend::onHostsError(const QString &err) { void DragonRelayBackend::onHostsError(const QString &err) {
qWarning() << "DragonRelayBackend: hosts fetch error:" << err; qWarning() << "DragonRelayBackend: hosts fetch error:" << err;
// Don't downgrade to Error state — the tunnel is still up; just log it // Don't downgrade to Error state - tunnel is still up.
} }
// ── TunnelManager slots ───────────────────────────────────────────────────────
void DragonRelayBackend::onTunnelUp(const QString &localIP) { void DragonRelayBackend::onTunnelUp(const QString &localIP) {
if (m_tunnelIP == localIP && (m_status == TunnelUp || m_status == Ready))
return; // ignore duplicate signals (first-packet path + 1.5 s fallback timer)
m_tunnelIP = localIP; m_tunnelIP = localIP;
emit tunnelIPChanged(); emit tunnelIPChanged();
setStatus(TunnelUp, QStringLiteral("Tunnel up (%1) — fetching hosts…").arg(localIP)); setStatus(TunnelUp,
QStringLiteral("Tunnel up (%1) - fetching hosts...").arg(localIP));
startHostPoll(); startHostPoll();
} }