[fix] preserve CLI display aspect ratio across terminals

This commit is contained in:
dijunkun
2026-10-30 17:15:47 +08:00
parent 15092b16d6
commit cf039b8f91
10 changed files with 277 additions and 20 deletions
+17
View File
@@ -21,6 +21,22 @@ struct Viewport {
bool native = false, pixel_mode = false;
int row_offset = 0; // Terminal rows reserved above the video area.
};
// Keep reported cell geometry across queries; never combine partial replies
// from different query cycles. Pixel/cell window sizes use the full text area.
class TerminalMetrics {
public:
void BeginQuery();
void Report(const std::string &sequence);
void Apply(Viewport &) const;
bool Known() const { return cell_width_ > 0; }
private:
bool SetCellSize(double width, double height);
double cell_width_ = 0, cell_height_ = 0;
int pixel_width_ = 0, pixel_height_ = 0, columns_ = 0, rows_ = 0;
bool queried_ = false, direct_reply_ = false;
};
inline constexpr char kTerminalSizeQuery[] = "\033[16t\033[14t\033[18t";
struct Rect {
int x, y, width, height;
};
@@ -72,6 +88,7 @@ public:
uint64_t mouse_reports = 0;
uint64_t mouse_legacy_reports = 0, mouse_forwarded = 0;
uint64_t mouse_send_failures = 0;
TerminalMetrics terminal_metrics;
private:
std::string pending_;
+4
View File
@@ -238,6 +238,10 @@ void Input::Feed(const std::string &bytes, ControllerSession &s,
return;
std::string seq = pending_.substr(0, end + 1);
pending_.erase(0, end + 1);
if (seq.back() == 't') {
terminal_metrics.Report(seq);
continue;
}
if (seq == "\033[O") {
control = false;
s.SetControl(false);
+67
View File
@@ -0,0 +1,67 @@
#include "terminal_renderer.h"
#include <charconv>
#include <cmath>
namespace crossdesk::terminal {
void TerminalMetrics::BeginQuery() {
queried_ = true;
direct_reply_ = false;
pixel_width_ = pixel_height_ = columns_ = rows_ = 0;
}
bool TerminalMetrics::SetCellSize(double width, double height) {
if (width < 1 || height < 1 || width > 1024 || height > 1024 ||
width / height < .2 || width / height > 2)
return false;
cell_width_ = width;
cell_height_ = height;
return true;
}
void TerminalMetrics::Report(const std::string &sequence) {
if (!queried_ || sequence.size() < 9 || sequence.compare(0, 2, "\033[") ||
sequence.back() != 't')
return;
int type = 0, height = 0, width = 0;
const char *cur = sequence.data() + 2;
const char *end = sequence.data() + sequence.size() - 1;
for (int *value : {&type, &height, &width}) {
auto result = std::from_chars(cur, end, *value);
if (result.ec != std::errc{} || *value <= 0 || *value > 1048576)
return;
cur = result.ptr;
if (value != &width) {
if (cur == end || *cur++ != ';')
return;
} else if (cur != end) {
return;
}
}
if (type == 6) {
// CSI 16 t replies with character height, then width (not width/height).
if (SetCellSize(width, height))
direct_reply_ = true;
} else if (type == 4) {
pixel_width_ = width;
pixel_height_ = height;
} else if (type == 8) {
columns_ = width;
rows_ = height;
} else {
return;
}
// VTE supports text-area pixels/cells even when CSI 16 t is unavailable.
// Use reported full rows/columns, not the capped video viewport or status
// rows, and retain fractional cell dimensions for scaled terminal fonts.
if (!direct_reply_ && pixel_width_ && pixel_height_ && columns_ && rows_)
SetCellSize(double(pixel_width_) / columns_, double(pixel_height_) / rows_);
}
void TerminalMetrics::Apply(Viewport &viewport) const {
if (!Known())
return;
viewport.cell_aspect = cell_width_ / cell_height_;
viewport.pixel_width = int(std::lround(cell_width_ * viewport.columns));
viewport.pixel_height = int(std::lround(cell_height_ * viewport.rows));
}
} // namespace crossdesk::terminal