修复:完善终端开机及串流交接并接入云管平台
This commit is contained in:
1 parent
8276da0d45
commit
18a9ce995d
25 files changed
+922
-48
No files matched your search
@@ -2,6 +2,8 @@
|
||||
|
||||
本目录维护当前 RK3528 / Xorg POC 私有客户端的源码修改,不替换系统 Moonlight。
|
||||
|
||||
当前部署与进出交接见[终端说明](../../docs/terminal-guide.md),性能/光标结论见[云桌面说明](../../docs/desktop-guide.md)。原日期专题的完整过程与旧版本回退步骤见[历史归档](../../docs/archive/README.md);下文带日期的 SHA 和试验状态按当时版本理解。
|
||||
|
||||
基线:Moonlight Qt v6.2.0 `de2467e433821664cdd2224aad8c89a625be1ad9`,其检出的 moonlight-common-c 子模块保持对应版本。MPP、FFmpeg 版本仍由 `tools/build-rkmpp-client.sh` 固定。
|
||||
|
||||
## 补丁
|
||||
@@ -12,8 +14,9 @@
|
||||
- `0004-vdi-power-key-policy.patch` 在 Moonlight Qt 根目录应用。客户端丢弃电源/睡眠键,以及 `Ctrl+Alt+Delete`、`Alt+F4`、`Win+L`、`Win+X`,防止云桌面被关机、重启、锁定或打开包含电源操作的系统菜单;按键不会发送到 Windows、Ubuntu 或麒麟客体系统。
|
||||
- `0005-vdi-native-linux-cursor.patch` 在 Qt 根目录应用,为已配置的单座席Linux/X11/2K/绝对鼠标会话创建原生SDL光标,形状/热点/显隐经认证SSH同步,位置不等待视频帧;主机以精确SHA固定的捕获适配器在新连接前去除视频光标。状态过期等故障结束整次会话,恢复原服务后重连。Windows无对应profile,继续原逻辑。安装和限制见[模块说明](../native-cursor/README.md)。
|
||||
- `0006-vdi-release-stuck-mouse-buttons.patch` 在焦点丢失和会话退出时显式释放五个远程鼠标按钮,避免一次中断丢失 `BUTTONUP` 后把 Ubuntu 留在“按住拖拽/点击”状态。
|
||||
- `0007-vdi-kiosk-launch-surface.patch` 在 Qt 根目录应用。kiosk 通过 `VDI_KIOSK_STREAM=1` 启用:CLI 启动/错误界面保持隐藏,SDL 串流窗口创建时就覆盖物理输出,防止无窗口管理器会话先显示左上角小窗。启动错误通过退出码交给 kiosk 显示;正常 Moonlight GUI 不受该模式影响。2026-11-09 部署 SHA256 为 `2e7441df1f6af5c4ddc79b89af15eed3b6b63cf265bea294ffc090483d8c6c51`,Linux 光标配置经复核后更新到该版本;进出验证见 `docs/terminal-guide.md#session`。
|
||||
|
||||
2026-11-03 的现场 RKMPP/EGL 使用独立 PacerRender 线程,诊断会话主线程 render 调用数为 0;因此第三补丁的新调度没有已证明的现场收益,未启用。当时默认客户端恢复至 SHA256 `35de7b1a4ade583823b23b5f2e012307f6d0a3848c8eb97bd6d4de9660afa00f`,该版本现保留作备份。2026-11-05加入0005后现场SHA为 `5d4330f703d7f8e053bc3795ca42bdaf231c9e9dd8ebb00a612013adbcccee03`,Linux profile在获取远端租约前严格校验此SHA;独立光标回归见[实施记录](../../docs/linux-native-cursor-20261105.md)。现场构建保留已有修改并加入0005,尚未重建部署其他任务的0004;构建器0001~0005清洁应用通过与现场补丁集合应分开判断。
|
||||
2026-11-03 的现场 RKMPP/EGL 使用独立 PacerRender 线程,诊断会话主线程 render 调用数为 0;因此第三补丁的新调度没有已证明的现场收益,未启用。当时默认客户端恢复至 SHA256 `35de7b1a4ade583823b23b5f2e012307f6d0a3848c8eb97bd6d4de9660afa00f`,该版本现保留作备份。2026-11-05加入0005后现场SHA为 `5d4330f703d7f8e053bc3795ca42bdaf231c9e9dd8ebb00a612013adbcccee03`,Linux profile在获取远端租约前严格校验此SHA;独立光标回归见[实施记录](../../docs/desktop-guide.md#cursor)。现场构建保留已有修改并加入0005,尚未重建部署其他任务的0004;构建器0001~0005清洁应用通过与现场补丁集合应分开判断。
|
||||
|
||||
`VDI_LATEST_FRAME=1` 只在没有 VSync pacing source 的呈现队列生效。最多保留一个待呈现解码帧,丢弃数计入原 `pacerDroppedFrames`。不丢压缩包,不改变 H.264 参考链,也不提前释放正在呈现或延迟释放的上一帧。未设置或设为 `0` 时保留上游帧队列策略;kiosk 私有 wrapper 默认设为 `1`。smooth 档仍使用原 pacing 行为。
|
||||
|
||||
@@ -40,8 +43,8 @@ VDI_TEST_SANITIZE=1 ASAN_OPTIONS=detect_leaks=1 bash tools/test-moonlight-latenc
|
||||
|
||||
新增 `test_input_priority.cpp` 验证优先调度不越过焦点/窗口/退出/解码器重置事件,保持输入 FIFO,隔离其他窗口与合成触摸,并验证数量与时间限制。普通与 ASAN 版本均通过。当前 RKMPP/EGL 没有主线程呈现事件,新调度开关不能用作该路径的性能提升宣传。
|
||||
|
||||
现场数据、局限与完整回退入口见 `docs/input-rendering-optimization-20261103.md`。
|
||||
输入优化结论与边界见[云桌面说明](../../docs/desktop-guide.md#cursor),原始数据与逐步回退记录见上述历史归档中的 `input-rendering-optimization-20261103.md`。
|
||||
|
||||
GPU修复后实测见`docs/2k60-validation-20261103.md`:当前正常二进制保持不变,VDD60Hz/串流45FPS已配置并从kiosk重连;约43~44FPS渲染。2K60请求受当前盒子呈现路径限制,不能宣称稳定60。`experiments/0004-vdi-egl-exact-size-sampling.patch`为未采用的精确尺寸采样实验,未进入正常构建器;默认仍为原GL_LINEAR取样。该实验候选二进制也未被正常wrapper使用。
|
||||
GPU修复后实测见`docs/desktop-guide.md#windows`:当前正常二进制保持不变,VDD60Hz/串流45FPS已配置并从kiosk重连;约43~44FPS渲染。2K60请求受当前盒子呈现路径限制,不能宣称稳定60。`experiments/0004-vdi-egl-exact-size-sampling.patch`为未采用的精确尺寸采样实验,未进入正常构建器;默认仍为原GL_LINEAR取样。该实验候选二进制也未被正常wrapper使用。
|
||||
|
||||
2026-11-03的Windows受控X11辅助程序原型见`docs/independent-cursor-feasibility-20261103.md`;该Windows原型没有自动形状同步或可靠帧交接,未进入正常启动流程。2026-11-05的Linux方案由0005和`terminal/native-cursor/`实现自动形状同步,通过连接前/整次会话后交接避开运行中帧切换;完整失焦和广泛缩放等场景尚未验收。不能把固定本地箭头和Sunshine全局N切换当作该方案。Windows保持单个远程白色光标。
|
||||
2026-11-03的Windows受控X11辅助程序原型见`docs/desktop-guide.md#cursor`;该Windows原型没有自动形状同步或可靠帧交接,未进入正常启动流程。2026-11-05的Linux方案由0005和`terminal/native-cursor/`实现自动形状同步,通过连接前/整次会话后交接避开运行中帧切换;完整失焦和广泛缩放等场景尚未验收。不能把固定本地箭头和Sunshine全局N切换当作该方案。Windows保持单个远程白色光标。
|
||||
@@ -0,0 +1,128 @@
|
||||
--- a/app/main.cpp
|
||||
+++ b/app/main.cpp
|
||||
@@ -1033,6 +1033,14 @@
|
||||
}
|
||||
|
||||
if (hasGUI) {
|
||||
+ // The VDI shell owns all connection/error UI. Keep the CLI Qt window
|
||||
+ // unmapped; only the full-screen SDL stream may appear over the shell.
|
||||
+ const bool vdiKioskLaunch = commandLineParserResult == GlobalCommandLineParser::StreamRequested &&
|
||||
+ qEnvironmentVariableIntValue("VDI_KIOSK_STREAM") == 1;
|
||||
+ engine.rootContext()->setContextProperty("vdiKioskLaunch", vdiKioskLaunch);
|
||||
+ if (vdiKioskLaunch) {
|
||||
+ app.setQuitOnLastWindowClosed(false);
|
||||
+ }
|
||||
engine.rootContext()->setContextProperty("initialView", initialView);
|
||||
engine.rootContext()->setContextProperty("runConfigChecks", commandLineParserResult == GlobalCommandLineParser::NormalStartRequested);
|
||||
|
||||
--- a/app/gui/main.qml
|
||||
+++ b/app/gui/main.qml
|
||||
@@ -35,6 +35,9 @@
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
+ if (vdiKioskLaunch) {
|
||||
+ return
|
||||
+ }
|
||||
// Show the window according to the user's preferences
|
||||
if (SystemProperties.hasDesktopEnvironment) {
|
||||
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) {
|
||||
--- a/app/gui/CliStartStreamSegue.qml
|
||||
+++ b/app/gui/CliStartStreamSegue.qml
|
||||
@@ -23,12 +23,21 @@
|
||||
}
|
||||
|
||||
function onLaunchFailed(message) {
|
||||
+ if (vdiKioskLaunch) {
|
||||
+ console.error(message)
|
||||
+ Qt.exit(1)
|
||||
+ return
|
||||
+ }
|
||||
errorDialog.text = message
|
||||
errorDialog.open()
|
||||
console.error(message)
|
||||
}
|
||||
|
||||
function onAppQuitRequired(appName) {
|
||||
+ if (vdiKioskLaunch) {
|
||||
+ onLaunchFailed(qsTr("Another application is already running: %1").arg(appName))
|
||||
+ return
|
||||
+ }
|
||||
quitAppDialog.appName = appName
|
||||
quitAppDialog.open()
|
||||
}
|
||||
--- a/app/gui/StreamSegue.qml
|
||||
+++ b/app/gui/StreamSegue.qml
|
||||
@@ -56,11 +56,19 @@
|
||||
stackView.replace(stackView.currentItem, component.createObject(stackView, {"appName": appName}), StackView.Immediate)
|
||||
|
||||
// Show the Qt window again to show quit segue
|
||||
- window.visible = true
|
||||
+ window.visible = !vdiKioskLaunch
|
||||
}
|
||||
|
||||
function sessionFinished(portTestResult)
|
||||
{
|
||||
+ if (vdiKioskLaunch) {
|
||||
+ // Never restore a hidden CLI dialog over the product surface.
|
||||
+ if (streamSegueErrorDialog.text) {
|
||||
+ console.error(streamSegueErrorDialog.text)
|
||||
+ }
|
||||
+ Qt.exit(streamSegueErrorDialog.text ? 1 : 0)
|
||||
+ return
|
||||
+ }
|
||||
if (portTestResult !== 0 && portTestResult !== -1 && streamSegueErrorDialog.text) {
|
||||
streamSegueErrorDialog.text += "\n\n" + qsTr("This PC's Internet connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network.")
|
||||
}
|
||||
@@ -145,7 +153,8 @@
|
||||
Loader {
|
||||
id: streamLoader
|
||||
active: false
|
||||
- asynchronous: true
|
||||
+ // A hidden Qt window has no render frames to drive incubation.
|
||||
+ asynchronous: !vdiKioskLaunch
|
||||
|
||||
onLoaded: {
|
||||
// Set the hint text. We do this here rather than
|
||||
@@ -178,6 +187,9 @@
|
||||
for (var i = 0; i < session.launchWarnings.length; i++) {
|
||||
var text = session.launchWarnings[i]
|
||||
console.warn(text)
|
||||
+ if (vdiKioskLaunch) {
|
||||
+ continue
|
||||
+ }
|
||||
|
||||
// Show the tooltip for 3 seconds
|
||||
var toast = Qt.createQmlObject('import QtQuick.Controls 2.2; ToolTip {}', parent, '')
|
||||
--- a/app/streaming/session.cpp
|
||||
+++ b/app/streaming/session.cpp
|
||||
@@ -1365,6 +1365,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ if (m_IsFullScreen && qEnvironmentVariableIntValue("VDI_KIOSK_STREAM") == 1) {
|
||||
+ // No EWMH window manager: size the surface before SDL maps it so the
|
||||
+ // first mapped frame already covers the output.
|
||||
+ SDL_Rect bounds;
|
||||
+ if (SDL_GetDisplayBounds(displayIndex, &bounds) == 0) {
|
||||
+ x = bounds.x;
|
||||
+ y = bounds.y;
|
||||
+ width = bounds.w;
|
||||
+ height = bounds.h;
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
SDL_Rect usableBounds;
|
||||
if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) == 0) {
|
||||
// If the stream resolution fits within the usable display area, use it directly
|
||||
@@ -1817,6 +1830,9 @@
|
||||
|
||||
// We always want a resizable window with High DPI enabled
|
||||
Uint32 defaultWindowFlags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE;
|
||||
+ if (m_IsFullScreen && qEnvironmentVariableIntValue("VDI_KIOSK_STREAM") == 1) {
|
||||
+ defaultWindowFlags |= SDL_WINDOW_BORDERLESS;
|
||||
+ }
|
||||
|
||||
// If we're starting in windowed mode and the Moonlight GUI is maximized or
|
||||
// minimized, match that with the streaming window.
|
||||
Reference in new issue
Block a user