diff --git a/apps/cli/docs/terminal-client.md b/apps/cli/docs/terminal-client.md index ad761446..18e418fa 100644 --- a/apps/cli/docs/terminal-client.md +++ b/apps/cli/docs/terminal-client.md @@ -59,6 +59,8 @@ Windows 的可见传统控制台优先读取当前字体尺寸;Windows Termina 快捷键行使用 `KEYS |`、`VIEW |` 等固定宽度标题,标题和按键组之间用竖线分隔;键名用 ``、`<+/->` 等标签标识。宽窗口中的分组分隔线按列对齐,窄窗口保留标题和完整按键标签,按可用宽度合并或减少分组。 +顶部状态栏以整数显示实时帧率(例如 `FPS 30`),约每秒按最近统计区间内完整输出到终端的帧数计算;没有新画面输出时更新为 `FPS 0`。该数值是实际提交速率,不是 `--fps` 目标值或终端 GPU 呈现频率;窄窗口优先省略画面参数和网络信息,保留帧率。 + ANSI 在完整桌面缩略图中仍无法保留普通字号的文字。先增加终端列数(缩小终端字体或扩大窗口),阅读局部时用 `--zoom native`。只读全景中点击目标位置可直接进入该区域的细节模式;按 `d` 则以当前中心进入。细节模式依据视口和字符宽高比选择源区域,避免为了完整装入桌面而把文字缩小;ANSI 视野较小,借助方向键平移阅读。`--zoom 1..32` 是相对整桌面的倍率;在细节模式中 `+/-` 是相对原始细节尺寸的倍率。画面缩放完全在本地进行,控制鼠标坐标映射回原始桌面的正确区域。 auto 模式发送 Kitty 查询与设备属性查询,等待最多 250ms;没有有效 Kitty 回应就用 ANSI。显式 kitty 模式探测失败时报错。tmux/screen 中 auto 使用 ANSI,显式 kitty 报不支持;不修改用户终端配置。 diff --git a/apps/cli/main.cpp b/apps/cli/main.cpp index 7f8ca230..7261cc61 100644 --- a/apps/cli/main.cpp +++ b/apps/cli/main.cpp @@ -411,6 +411,9 @@ int View(SessionOptions options, const std::string &device, uint64_t view_revision = input.view_revision; bool redraw = true; uint64_t submitted = 0; + uint64_t fps_submitted = 0; + auto fps_sample_at = started; + long output_fps = 0; std::optional connected_at, first_frame_at, first_output_at; bool waiting_visible = false; @@ -550,6 +553,14 @@ int View(SessionOptions options, const std::string &device, size_query_tick = now + std::chrono::seconds(2); } if (output.Idle() && !shared_upload && now >= status_tick) { + const double fps_seconds = + std::chrono::duration(now - fps_sample_at).count(); + // Mode changes can redraw the status early; keep a full sampling window. + if (fps_seconds >= 1) { + output_fps = std::lround((submitted - fps_submitted) / fps_seconds); + fps_submitted = submitted; + fps_sample_at = now; + } auto st = s.Status(); const auto last_state = st["state"].get(); std::string progress = @@ -577,6 +588,7 @@ int View(SessionOptions options, const std::string &device, bar.error = input.error; bar.control = input.control; bar.command_mode = input.command_mode; + bar.fps = "FPS " + std::to_string(output_fps); bar.view = std::string(kitty ? "KITTY" : detail == AnsiDetail::Quadrant ? "QUAD" : "HALF") + diff --git a/libs/terminal/include/terminal_renderer.h b/libs/terminal/include/terminal_renderer.h index 9e34e3c4..65917a84 100644 --- a/libs/terminal/include/terminal_renderer.h +++ b/libs/terminal/include/terminal_renderer.h @@ -86,7 +86,7 @@ inline constexpr char kKittyDelete[] = "\033_Ga=d,d=I,i=31,q=2\033\\\033_Ga=d,d=I,i=32,q=2\033\\"; bool KittyReply(const std::string &response); struct StatusBar { - std::string progress, compact_progress, error, view, network; + std::string progress, compact_progress, error, view, network, fps; bool control = false, command_mode = false; }; // Three padded ASCII rows. Shortcuts are compacted as whole groups to fit. diff --git a/libs/terminal/src/terminal_status.cpp b/libs/terminal/src/terminal_status.cpp index 1fcdc7b7..988b9bd1 100644 --- a/libs/terminal/src/terminal_status.cpp +++ b/libs/terminal/src/terminal_status.cpp @@ -59,11 +59,16 @@ std::vector StatusLines(const StatusBar &bar, int columns) { const auto progress = bar.error.empty() ? bar.progress : "Error: " + bar.error; auto left = mode + " | " + progress; - auto right = bar.view + (bar.network.empty() ? "" : " | " + bar.network); + auto metrics = bar.fps; + if (!bar.network.empty()) + metrics += (metrics.empty() ? "" : " | ") + bar.network; + auto right = bar.view + (metrics.empty() ? "" : " | " + metrics); if (left.size() + right.size() + 2 > inner) - right = bar.network; + right = metrics; if (left.size() + right.size() + 2 > inner && bar.error.empty()) left = mode + " | " + bar.compact_progress; + if (left.size() + right.size() + 2 > inner) + right = bar.fps; if (left.size() + right.size() + 2 > inner) right.clear(); std::string status = Clip(left, inner);