修复:完善终端开机及串流交接并接入云管平台

This commit is contained in:
songyiming committed 2026-11-09 17:54:18 +08:00
1 parent 8276da0d45
commit 18a9ce995d
25 files changed
+922 -48

No files matched your search

+111 -20
View File
@@ -46,16 +46,45 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
setupHeartbeat();
config_ = loadConfig();
fsm_.transitionTo(State::kConfig, QStringLiteral("就绪"));
desktopService_.setRuntimeHandlers([this](std::function<void(bool)> done) {
showReturnCover(); stopSessions();
if (!fsm_.isIn(State::kDesktopList)) fsm_.transitionTo(State::kDesktopList,QStringLiteral("管理员已结束会话"));
presentKioskSurface(); releaseReturnCoverWhenReady();
if (desktopListPage_) desktopListPage_->setBusy(true);
auto* wait=new QTimer(this); wait->setInterval(100);
connect(wait,&QTimer::timeout,this,[this,wait,done,attempts=0]() mutable {
const bool stopped=!moonlightSession_ || !moonlightSession_->isRunning();
if (!stopped && ++attempts<100) return;
wait->stop(); wait->deleteLater();
if (desktopListPage_) desktopListPage_->setBusy(false);
done(stopped);
});
wait->start();
},[this]() { return QString::fromLatin1(stateToString(fsm_.current())); });
if (desktopService_.platformManaged()) {
auto* refresh=new QTimer(this); refresh->setInterval(15000);
connect(refresh,&QTimer::timeout,this,[this]{ if (fsm_.isIn(State::kDesktopList)) refreshDesktopList(); });
refresh->start();
}
const QString deviceUser = QString::fromStdString(config_.sn).trimmed();
desktopService_.login(deviceUser.isEmpty() ? QStringLiteral("terminal") : deviceUser,
QStringLiteral("kiosk"), [this](const AuthResult& result) {
if (result.ok) {
currentUser_ = result.displayName;
if (desktopListPage_) {
desktopListPage_->setCurrentUser(currentUser_);
}
if (fsm_.isIn(State::kDesktopList)) refreshDesktopList();
return;
}
// A real platform authentication failure must still return to the
// login page. The normal device-authenticated boot path never shows
// that page, so the first painted kiosk frame is the desktop list.
if (fsm_.isIn(State::kDesktopList)) {
fsm_.transitionTo(State::kConfig, result.message);
}
fsm_.transitionTo(State::kAuthenticating, QStringLiteral("自动登录"));
fsm_.transitionTo(State::kDesktopList, QStringLiteral("请选择要进入的云桌面"));
});
fsm_.transitionTo(State::kDesktopList, QStringLiteral("请选择要进入的云桌面"));
}
MainWindow::~MainWindow() {
@@ -87,6 +116,18 @@ void MainWindow::setupUi() {
setStyleSheet(QStringLiteral("QMainWindow { background: #0f1420; }"));
qApp->installEventFilter(this);
// The display-guard service may restore the physical output mode after
// this process has already mapped its window. Track screen geometry so
// the opaque kiosk surface immediately follows that mode change instead
// of leaving an exposed root-desktop strip at the old size.
for (QScreen* screen : QApplication::screens()) {
connect(screen, &QScreen::geometryChanged, this, [this](const QRect&) {
if (isVisible()) {
applyKioskSurface();
}
});
}
Qt::WindowFlags coverFlags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool;
#ifdef Q_OS_UNIX
coverFlags |= Qt::X11BypassWindowManagerHint;
@@ -200,15 +241,13 @@ void MainWindow::onStateChanged(State from, State to, const QString& detail) {
// state keeps the kiosk keyboard grab, including the connecting overlay.
const bool streamSurface = to == State::kSession || to == State::kReconnect;
if (streamSurface) {
if (isVisible()) {
hide();
}
// Moonlight owns Ctrl+Alt+Shift+Q while streaming and can destroy its
// window before QProcess notifies us. Keep our painted, full-screen
// list mapped underneath it: restoring a hidden window here is too
// late to prevent a frame of the host desktop from being scanned out.
setKioskInputGuard(false);
} else {
setKioskInputGuard(true);
if (to != State::kBoot) {
presentKioskSurface();
}
}
switch (to) {
@@ -234,15 +273,33 @@ void MainWindow::onStateChanged(State from, State to, const QString& detail) {
desktopListPage_->setBusy(false);
desktopListPage_->setStatus(QStringLiteral("连接失败:%1").arg(detail), true);
}
break;
return;
case State::kBoot:
break;
}
// Select the destination page before mapping/painting the window. In
// particular, the first boot frame must never paint the old login page.
if (!streamSurface && to != State::kBoot) {
presentKioskSurface();
}
}
void MainWindow::showEvent(QShowEvent* event) {
QMainWindow::showEvent(event);
applyKioskSurface();
// With the production X11 kiosk session there is no window manager to
// perform the initial activation handshake. Merely raising the Qt
// surface leaves _NET_ACTIVE_WINDOW unset and pointer/keyboard events can
// keep going to the desktop root. Use the same direct X11 map/focus path
// as the return-from-stream transition on the first paint as well.
presentKioskSurface();
// HDMI mode restoration can happen shortly after kiosk-shell maps. A
// delayed pass covers that race even on X servers that do not emit a
// geometryChanged signal for the mode switch.
QTimer::singleShot(1200, this, [this]() {
if (isVisible()) {
applyKioskSurface();
}
});
if (kioskInputGuard_) {
retryKeyboardGrab();
}
@@ -252,8 +309,15 @@ void MainWindow::applyKioskSurface() {
if (!isVisible()) {
return;
}
raise();
activateWindow();
// Geometry updates and delayed show callbacks must not steal the stream's
// focus or raise this persistent backing surface over Moonlight.
if (kioskInputGuard_) {
raise();
activateWindow();
if (overlay_ && overlay_->isVisible()) {
overlay_->raise();
}
}
if (QScreen* screen = windowHandle() ? windowHandle()->screen() : QApplication::primaryScreen()) {
// An override-redirect window does not receive the WM fullscreen
// negotiation; explicitly cover the complete output geometry.
@@ -441,11 +505,15 @@ void MainWindow::refreshDesktopList() {
desktopService_.listDesktops([this](std::vector<DesktopInfo> list, const QString& error) {
if (error.isEmpty()) {
desktops_ = list;
} else if (desktopService_.platformManaged()) {
desktops_.clear();
}
if (desktopListPage_) {
desktopListPage_->setDesktops(desktops_);
if (!error.isEmpty()) {
desktopListPage_->setStatus(error, true);
} else {
desktopListPage_->setStatus(QString());
}
}
});
@@ -491,6 +559,7 @@ void MainWindow::onDesktopSelected(const QString& desktopId) {
desktopService_.createTicket(desktopId, [this, attempt](const ConnectionTicket& ticket,
const QString& error) {
if (attempt != connectionAttempt_ || connectionPhase_ != ConnectionPhase::kTicket) {
desktopService_.sessionEnded(ticket.sessionId);
return;
}
connectionTimer_->stop();
@@ -507,6 +576,10 @@ void MainWindow::onDesktopSelected(const QString& desktopId) {
}
void MainWindow::onEditDesktopRequested(const QString& desktopId) {
if (desktopService_.platformManaged()) {
if (desktopListPage_) desktopListPage_->setStatus(QStringLiteral("桌面与连接参数由管理平台维护"));
return;
}
DesktopSettingsDialog dialog(desktops_, desktopId, this);
dialog.setStyleSheet(
QStringLiteral("QWidget { background: #0f1420; color: #e6e9ef; }"
@@ -568,6 +641,7 @@ void MainWindow::onMoonlightEvent(quint64 attempt, const MoonlightSession::Event
failConnection(attempt, event.message);
break;
case MoonlightSession::EventType::kFinished: {
desktopService_.sessionEnded(activeTicket_.sessionId);
connectionTimer_->stop();
logConnectionPhase(attempt, QStringLiteral("session"), QStringLiteral("finished"));
++connectionAttempt_;
@@ -590,6 +664,14 @@ void MainWindow::waitForMoonlightWindow(quint64 attempt) {
if (attempt != connectionAttempt_ || connectionPhase_ != ConnectionPhase::kStreamReady) {
return;
}
// Paint the return destination while our surface is still mapped, before
// handing the top of the X11 stack to Moonlight. A native client shortcut,
// normal disconnect, or client crash then reveals this page immediately.
showDesktopList();
desktopListPage_->setBusy(true);
desktopListPage_->setStatus(QString());
repaint();
QGuiApplication::sync();
if (!moonlightSession_->activateStreamWindow()) {
QTimer::singleShot(100, this, [this, attempt]() { waitForMoonlightWindow(attempt); });
return;
@@ -598,6 +680,7 @@ void MainWindow::waitForMoonlightWindow(quint64 attempt) {
connectionTimer_->stop();
logConnectionPhase(attempt, QStringLiteral("stream_ready"), QStringLiteral("ready"));
connectionPhase_ = ConnectionPhase::kSession;
desktopService_.sessionConnected(activeTicket_.sessionId);
fsm_.transitionTo(State::kSession, QStringLiteral("云桌面画面已就绪"));
overlay_->onFirstFrame();
}
@@ -613,6 +696,7 @@ void MainWindow::failConnection(quint64 attempt, const QString& message) {
if (moonlightSession_) {
moonlightSession_->stop();
}
releasePlatformSessionAfterStop(activeTicket_.sessionId);
overlay_->finish();
if (fsm_.isIn(State::kConnecting) || fsm_.isIn(State::kSession) ||
fsm_.isIn(State::kReconnect)) {
@@ -663,22 +747,29 @@ void MainWindow::stopSessions(bool invalidateAttempt) {
if (moonlightSession_) {
moonlightSession_->stop();
}
releasePlatformSessionAfterStop(activeTicket_.sessionId);
if (overlay_) {
overlay_->finish();
}
}
void MainWindow::releasePlatformSessionAfterStop(const QString& id) {
if (id.isEmpty()) return;
const qint64 pid=moonlightSession_?moonlightSession_->processId():0;
if (!pid) { desktopService_.sessionEnded(id); return; }
auto* wait=new QTimer(this); wait->setInterval(100);
connect(wait,&QTimer::timeout,this,[this,wait,id,pid] {
if (moonlightSession_ && moonlightSession_->isRunning() && moonlightSession_->processId()==pid) return;
wait->stop(); wait->deleteLater(); desktopService_.sessionEnded(id);
});
wait->start();
}
void MainWindow::onMenuAction(int action) {
switch (action) {
case 1:
if (fsm_.canConnect() && !activeTicket_.desktopId.isEmpty()) {
const quint64 attempt = ++connectionAttempt_;
connectingDesktopId_ = activeTicket_.desktopId;
connectionClock_.restart();
if (fsm_.transitionTo(State::kConnecting, activeTicket_.endpoint) &&
!startMoonlightSession(activeTicket_, attempt)) {
failConnection(attempt, QStringLiteral("已有串流进程正在运行"));
}
onDesktopSelected(activeTicket_.desktopId);
}
break;
case 2: