修复:完善终端开机及串流交接并接入云管平台
This commit is contained in:
1 parent
8276da0d45
commit
18a9ce995d
25 files changed
+922
-48
No files matched your search
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user