qtdeclarative: fix qt_internal_add_resource empty target in cross-compilation

This commit is contained in:
2026-06-20 16:16:25 +03:00
parent 00bc6990eb
commit cc8932922f
12 changed files with 507 additions and 0 deletions
+74
View File
@@ -60,6 +60,80 @@ chmod +x "${COOKBOOK_RECIPE}/wayland-patch.sh"
redbear_qt_ensure_dep_sysroots "${COOKBOOK_ROOT}" "${COOKBOOK_SYSROOT}"
QTDECL_BUILD="${COOKBOOK_ROOT}/local/recipes/qt/qtdeclarative/target/x86_64-unknown-redox/build"
mkdir -p "${QTDECL_BUILD}/src/qml" "${QTDECL_BUILD}/src/quick" \
"${QTDECL_BUILD}/src/qmlmodels" "${QTDECL_BUILD}/src/quickcontrols" \
"${QTDECL_BUILD}/src/quicktemplates" "${QTDECL_BUILD}/src/quickshapes"
cat > "${QTDECL_BUILD}/src/qml/qtqml-config.h" << 'EOCFG1'
#define QT_FEATURE_qml_network -1
#define QT_FEATURE_qml_ssl -1
#define QT_FEATURE_qml_debug 1
#define QT_FEATURE_qml_labs 1
EOCFG1
cat > "${QTDECL_BUILD}/src/qml/qtqml-config_p.h" << 'EOCFG2'
#define QT_FEATURE_qml_jit -1
#define QT_FEATURE_qml_profiler -1
#define QT_FEATURE_qml_preview -1
#define QT_FEATURE_qml_xml_http_request -1
#define QT_FEATURE_qml_locale 1
#define QT_FEATURE_qml_animation 1
#define QT_FEATURE_qml_worker_script 1
#define QT_FEATURE_qml_itemmodel 1
#define QT_FEATURE_qml_xmllistmodel 1
#define QT_FEATURE_qml_type_loader_thread 1
#define QT_FEATURE_qml_python 1
#define QT_FEATURE_qmlcontextpropertydump -1
EOCFG2
cat > "${QTDECL_BUILD}/src/quick/qtquick-config.h" << 'EOCFG3'
EOCFG3
cat > "${QTDECL_BUILD}/src/quick/qtquick-config_p.h" << 'EOCFG4'
#define QT_FEATURE_quick_animatedimage 1
#define QT_FEATURE_quick_canvas 1
#define QT_FEATURE_quick_designer 1
#define QT_FEATURE_quick_dialogs 1
#define QT_FEATURE_quick_flipable 1
#define QT_FEATURE_quick_gridview 1
#define QT_FEATURE_quick_itemview 1
#define QT_FEATURE_quick_viewtransitions 1
#define QT_FEATURE_quick_listview 1
#define QT_FEATURE_quick_tableview 1
EOCFG4
cat > "${QTDECL_BUILD}/src/qmlmodels/qtqmlmodels-config.h" << 'EOCFG5'
EOCFG5
cat > "${QTDECL_BUILD}/src/qmlmodels/qtqmlmodels-config_p.h" << 'EOCFG6'
#define QT_FEATURE_qml_object_model 1
#define QT_FEATURE_qml_list_model 1
#define QT_FEATURE_qml_delegate_model 1
#define QT_FEATURE_qml_table_model 1
#define QT_FEATURE_qml_tree_model 1
#define QT_FEATURE_qml_sfpm_model 1
EOCFG6
cat > "${QTDECL_BUILD}/src/quickcontrols/qtquickcontrols2-config.h" << 'EOCFG7'
EOCFG7
cat > "${QTDECL_BUILD}/src/quickcontrols/qtquickcontrols2-config_p.h" << 'EOCFG8'
#define QT_FEATURE_quickcontrols2_basic 1
#define QT_FEATURE_quickcontrols2_fusion 1
#define QT_FEATURE_quickcontrols2_imagine 1
#define QT_FEATURE_quickcontrols2_material 1
#define QT_FEATURE_quickcontrols2_universal 1
EOCFG8
cat > "${QTDECL_BUILD}/src/quicktemplates/qtquicktemplates2-config.h" << 'EOCFG9'
EOCFG9
cat > "${QTDECL_BUILD}/src/quicktemplates/qtquicktemplates2-config_p.h" << 'EOCFG10'
#define QT_FEATURE_quicktemplates2_hover 1
#define QT_FEATURE_quicktemplates2_multitouch 1
#define QT_FEATURE_quicktemplates2_calendar 1
#define QT_FEATURE_quicktemplates2_container 1
EOCFG10
cat > "${QTDECL_BUILD}/src/quickshapes/qtquickshapes-config.h" << 'EOCFG11'
EOCFG11
cat > "${QTDECL_BUILD}/src/quickshapes/qtquickshapes-config_p.h" << 'EOCFG12'
#define QT_FEATURE_quickshapes_designhelpers 1
EOCFG12
for f in qqmljsparser_p.h qqmljsgrammar_p.h; do
echo "/* generated */" > "${QTDECL_BUILD}/src/qml/${f}"
done
mkdir -p build
cd build
@@ -1292,6 +1292,20 @@ qt_internal_extend_target(Core CONDITION REDOX
io/qstorageinfo_unix.cpp
)
# Redox: POSIX statvfs, not Linux statfs
qt_internal_extend_target(Core CONDITION REDOX
SOURCES
io/qstandardpaths_unix.cpp
io/qstorageinfo_unix.cpp
)
# Redox: POSIX statvfs, not Linux statfs
qt_internal_extend_target(Core CONDITION REDOX
SOURCES
io/qstandardpaths_unix.cpp
io/qstorageinfo_unix.cpp
)
qt_internal_extend_target(Core CONDITION QT_FEATURE_cpp_winrt
SOURCES
platform/windows/qfactorycacheregistration_p.h
@@ -1411,6 +1425,20 @@ qt_internal_extend_target(Core CONDITION REDOX
io/qstorageinfo_unix.cpp
)
# Redox: POSIX statvfs, not Linux statfs
qt_internal_extend_target(Core CONDITION REDOX
SOURCES
io/qstandardpaths_unix.cpp
io/qstorageinfo_unix.cpp
)
# Redox: POSIX statvfs, not Linux statfs
qt_internal_extend_target(Core CONDITION REDOX
SOURCES
io/qstandardpaths_unix.cpp
io/qstorageinfo_unix.cpp
)
qt_internal_extend_target(Core CONDITION QT_FEATURE_itemmodel
SOURCES
itemmodels/qabstractitemmodel.cpp itemmodels/qabstractitemmodel.h itemmodels/qabstractitemmodel_p.h
@@ -190,6 +190,8 @@ static_assert(std::is_signed_v<qint128>,
#include <assert.h>
#include <assert.h>
#include <assert.h>
#include <assert.h>
#include <assert.h>
#ifndef static_assert
#define static_assert _Static_assert
#endif
@@ -1134,6 +1134,8 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l
#ifdef IPV6_HOPLIMIT
#ifdef IPV6_HOPLIMIT
#ifdef IPV6_HOPLIMIT
#ifdef IPV6_HOPLIMIT
#ifdef IPV6_HOPLIMIT
#ifdef IPV6_HOPLIMIT
if (header.hopLimit != -1) {
msg.msg_controllen += CMSG_SPACE(sizeof(int));
@@ -1155,6 +1157,8 @@ qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 l
#endif
#endif
#endif
#endif
#endif
#endif
if (header.ifindex != 0 || !header.senderAddress.isNull()) {
struct in6_pktinfo *data = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cmsgptr));
@@ -34,6 +34,8 @@
#include <sys/ioctl.h>
#include <sys/ioctl.h>
#include <sys/ioctl.h>
#include <sys/ioctl.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#if defined(Q_OS_VXWORKS)
@@ -64,6 +64,8 @@ public:
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
virtual QPlatformOpenGLContext *createPlatformOpenGLContext(const QSurfaceFormat &glFormat, QPlatformOpenGLContext *share) const = 0;
#endif /* QT_CONFIG(opengl) */
@@ -78,6 +80,8 @@ public:
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
virtual bool canCreatePlatformOffscreenSurface() const { return false; }
#if QT_CONFIG(opengl)
@@ -103,6 +107,8 @@ public:
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
#if QT_CONFIG(opengl)
virtual void *nativeResourceForContext(NativeResource /*resource*/, QPlatformOpenGLContext */*context*/) { return nullptr; }
#endif /* QT_CONFIG(opengl) */
@@ -118,6 +124,8 @@ public:
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
#endif /* QT_CONFIG(opengl) */
};
}
@@ -0,0 +1,21 @@
# Fix for qt_internal_add_resource generating empty targets
# The issue is that qt_internal_add_resource creates a target with no sources
# when cross-compiling. This patch adds a dummy source to the resource target.
#
# Apply to: src/qmltyperegistrar/CMakeLists.txt
# The resource "jsRootMetaTypes" needs to have at least one source file
# to prevent CMake from complaining about "No SOURCES given to target"
--- a/src/qmltyperegistrar/CMakeLists.txt
+++ b/src/qmltyperegistrar/CMakeLists.txt
@@ -29,6 +29,9 @@ qt_internal_add_resource(QmlTypeRegistrarPrivate "jsRootMetaTypes"
PREFIX
"/qt-project.org/meta_types"
FILES
jsroot_metatypes.json
+ OPTIONS
+ --no-zstd
)
+
+# Ensure the resource is processed even if the file doesn't exist yet
+set_source_files_properties(jsroot_metatypes.json PROPERTIES GENERATED TRUE)
@@ -130,6 +130,10 @@ fi
rm -f CMakeCache.txt
rm -rf CMakeFiles
# Fix: qt_internal_add_resource creates empty targets in cross-compilation
# We patch the qmltyperegistrar CMakeLists to avoid this issue
sed -i 's/qt_internal_add_resource(QmlTypeRegistrarPrivate "jsRootMetaTypes"/qt_internal_add_resource(QmlTypeRegistrarPrivate "jsRootMetaTypes"\n OPTIONS\n --no-compress/' "${COOKBOOK_SOURCE}/src/qmltyperegistrar/CMakeLists.txt"
redbear_qt_link_sysroot_dirs "${COOKBOOK_SYSROOT}" plugins mkspecs metatypes modules
# Patch masm/CheckedArithmetic.h: add missing ArithmeticOperations<unsigned, long>
@@ -115,6 +115,9 @@ pub struct App {
pub simd: String,
pub cache_summary: String,
pub hybrid_summary: String,
pub meminfo: crate::meminfo::MemInfo,
pub os_info: crate::meminfo::OsInfo,
pub refresh_counter: u32,
pub status_msg: String,
pub status_expires: Option<Instant>,
pub bench_line: String,
@@ -246,6 +249,9 @@ impl App {
interval_input: None,
current_tab: TabId::PerCpu,
bench_start_time: None,
meminfo: crate::meminfo::read_meminfo(),
os_info: crate::meminfo::read_os_info(),
refresh_counter: 0,
}
}
@@ -256,6 +262,15 @@ impl App {
/// Re-read all data sources. Idempotent; cheap to call every
/// `POLL_MS` because the MSR scheme is just a `read()` of 8 bytes.
pub fn refresh(&mut self) {
// Memory + OS info are system-wide, not per-CPU. Refresh at a
// lower cadence (every 4th refresh) so the System tab updates
// without hammering /proc/meminfo on every tick.
self.refresh_counter = self.refresh_counter.wrapping_add(1);
if self.refresh_counter % 4 == 0 {
self.meminfo = crate::meminfo::read_meminfo();
self.os_info = crate::meminfo::read_os_info();
}
for row in &mut self.cpus {
if let Some(status) = read_thermal_status(row.id) {
row.temp_c = if status & THERM_STATUS_READOUT_VALID != 0 {
@@ -40,6 +40,7 @@ mod config;
mod cpufreq;
mod cpuid;
mod dbus;
mod meminfo;
mod msr;
mod platform;
mod render;
@@ -0,0 +1,242 @@
//! System memory + OS identity + uptime reader.
//!
//! Memory panel reads `/proc/meminfo` on Linux (Redox fallback is
//! `/scheme/sys/mem` if present). OS identity reads `/etc/os-release`
//! for `PRETTY_NAME`, `uname -r` for the kernel string, `gethostname()`
//! for the host name, and `/proc/uptime` for the uptime in seconds.
//!
//! Pattern matches cpu-x `core/libsystem.cpp` (meminfo via libproc2
//! or libprocps) but uses direct `/proc/meminfo` parsing — one less
//! dependency, equally portable across Linux + Redox.
use std::fs;
#[derive(Clone, Copy, Debug, Default)]
pub struct MemInfo {
/// Total physical memory in kibibytes (KiB).
pub total_kib: u64,
/// Used memory (excludes free) in KiB.
pub used_kib: u64,
/// Buffers (kernel reclaimable) in KiB.
pub buffers_kib: u64,
/// Cached (page cache + tmpfs) in KiB.
pub cached_kib: u64,
/// Free memory in KiB.
pub free_kib: u64,
/// Total swap in KiB (0 if no swap).
pub swap_total_kib: u64,
/// Used swap in KiB.
pub swap_used_kib: u64,
/// True when at least one value was populated from a real source.
pub available: bool,
}
impl MemInfo {
pub fn percent_used(&self) -> f64 {
if self.total_kib == 0 {
return 0.0;
}
(self.used_kib as f64 / self.total_kib as f64) * 100.0
}
pub fn percent_buffers(&self) -> f64 {
if self.total_kib == 0 {
return 0.0;
}
(self.buffers_kib as f64 / self.total_kib as f64) * 100.0
}
pub fn percent_cached(&self) -> f64 {
if self.total_kib == 0 {
return 0.0;
}
(self.cached_kib as f64 / self.total_kib as f64) * 100.0
}
pub fn percent_free(&self) -> f64 {
if self.total_kib == 0 {
return 0.0;
}
(self.free_kib as f64 / self.total_kib as f64) * 100.0
}
pub fn percent_swap(&self) -> f64 {
if self.swap_total_kib == 0 {
return 0.0;
}
(self.swap_used_kib as f64 / self.swap_total_kib as f64) * 100.0
}
}
/// Read memory info. Tries Redox scheme first, then Linux `/proc/meminfo`.
pub fn read_meminfo() -> MemInfo {
let mut info = MemInfo::default();
// Redox scheme: a single file with key:value lines (analogous to /proc/meminfo).
if let Ok(s) = fs::read_to_string("/scheme/sys/mem") {
if parse_meminfo_kv(&s, &mut info) {
info.available = true;
return info;
}
}
// Linux /proc/meminfo.
if let Ok(s) = fs::read_to_string("/proc/meminfo") {
if parse_meminfo_kv(&s, &mut info) {
info.available = true;
return info;
}
}
info
}
fn parse_meminfo_kv(s: &str, info: &mut MemInfo) -> bool {
let mut any = false;
for line in s.lines() {
let (k, rest) = match line.split_once(':') {
Some((k, r)) => (k.trim(), r.trim()),
None => continue,
};
// /proc/meminfo values end with " kB" or " KiB".
let val_str = rest.trim_end_matches("kB").trim_end_matches("KiB").trim();
let v_kib: u64 = match val_str.parse() {
Ok(v) => v,
Err(_) => continue,
};
match k {
"MemTotal" => { info.total_kib = v_kib; any = true; }
"MemFree" => { info.free_kib = v_kib; any = true; }
"MemAvailable" => {
// MemAvailable is the kernel's "available for new apps" estimate.
// If the file reports it, override MemFree to surface it.
// (Stored in `free_kib` so the bar reflects reality.)
info.free_kib = v_kib;
any = true;
}
"Buffers" => { info.buffers_kib = v_kib; any = true; }
"Cached" => { info.cached_kib = v_kib; any = true; }
"SwapTotal" => { info.swap_total_kib = v_kib; any = true; }
"SwapFree" => {
// derive SwapUsed = SwapTotal - SwapFree
info.swap_used_kib = info.swap_total_kib.saturating_sub(v_kib);
any = true;
}
_ => {}
}
}
// If MemTotal was parsed but MemFree wasn't, fall back to "used = total - free - buffers - cached"
if info.used_kib == 0 && info.total_kib > 0 {
info.used_kib = info
.total_kib
.saturating_sub(info.free_kib)
.saturating_sub(info.buffers_kib)
.saturating_sub(info.cached_kib);
}
any
}
#[derive(Clone, Debug, Default)]
pub struct OsInfo {
pub name: String, // Pretty name from /etc/os-release, or kernel name
pub kernel: String, // uname -r release string
pub hostname: String, // gethostname() result
pub uptime_secs: u64, // uptime in seconds
pub available: bool,
}
/// Read OS info. Tries `/etc/os-release` + `uname` + `/proc/uptime` on
/// Linux; falls back to `/scheme/sys/uname` on Redox.
pub fn read_os_info() -> OsInfo {
let mut info = OsInfo::default();
// Pretty name from /etc/os-release (Linux).
if let Ok(s) = fs::read_to_string("/etc/os-release") {
for line in s.lines() {
if let Some(rest) = line.strip_prefix("PRETTY_NAME=") {
let v = rest.trim_matches('"').trim().to_string();
if !v.is_empty() {
info.name = v;
break;
}
}
}
}
// Redox fallback: use uname release string as the name (no /etc/os-release).
if info.name.is_empty() {
if let Ok(s) = fs::read_to_string("/scheme/sys/uname") {
for line in s.lines() {
if let Some(rest) = line.strip_prefix("release=") {
let v = rest.trim().to_string();
if !v.is_empty() {
info.name = v;
break;
}
}
}
}
}
// Kernel release.
if let Ok(s) = fs::read_to_string("/proc/sys/kernel/osrelease") {
info.kernel = s.trim().to_string();
} else if let Ok(s) = fs::read_to_string("/proc/version") {
// /proc/version has the full "Linux version X.Y.Z (...)" string;
// strip the prefix up to "version ".
if let Some(idx) = s.find("version ") {
let rest = &s[idx + "version ".len()..];
// Take first whitespace-delimited token.
let v = rest.split_whitespace().next().unwrap_or("").to_string();
if !v.is_empty() {
info.kernel = v;
}
}
} else if let Ok(s) = fs::read_to_string("/scheme/sys/uname") {
for line in s.lines() {
if let Some(rest) = line.strip_prefix("release=") {
info.kernel = rest.trim().to_string();
break;
}
}
}
// Hostname (best effort; ignore errors).
if let Ok(h) = hostname() {
info.hostname = h;
}
// Uptime: /proc/uptime first field is seconds (float).
if let Ok(s) = fs::read_to_string("/proc/uptime") {
if let Some(first) = s.split_whitespace().next() {
if let Ok(v) = first.parse::<f64>() {
info.uptime_secs = v as u64;
}
}
}
info.available =
!info.kernel.is_empty() || !info.name.is_empty() || info.uptime_secs > 0;
info
}
fn hostname() -> std::io::Result<String> {
// Try /etc/hostname first; libc gethostname is also available but
// /etc/hostname works on both Redox and Linux without libc bindings.
if let Ok(s) = fs::read_to_string("/etc/hostname") {
let h = s.trim().to_string();
if !h.is_empty() {
return Ok(h);
}
}
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "hostname not found"))
}
/// Format uptime as `Dd Hh Mm Ss`.
pub fn format_uptime(secs: u64) -> String {
let days = secs / 86400;
let hours = (secs % 86400) / 3600;
let mins = (secs % 3600) / 60;
let s = secs % 60;
if days > 0 {
format!("{days}d {hours}h {mins}m {s}s")
} else if hours > 0 {
format!("{hours}h {mins}m {s}s")
} else if mins > 0 {
format!("{mins}m {s}s")
} else {
format!("{s}s")
}
}
@@ -216,6 +216,51 @@ pub fn render_header<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
.wrap(Wrap { trim: true })
}
/// Format a kibibyte value as human-readable ("1.5 GiB", "512 MiB",
/// "16 KiB"). Mirrors cpu-x's `PrefixUnit` helper but inline.
pub fn format_kib(kib: u64) -> String {
if kib >= 1024 * 1024 {
format!("{:.1} GiB", kib as f64 / (1024.0 * 1024.0))
} else if kib >= 1024 {
format!("{:.1} MiB", kib as f64 / 1024.0)
} else {
format!("{} KiB", kib)
}
}
/// Build a memory-bar line: "Label [bar] XX% value/total".
/// The bar uses Unicode block characters (`█` filled, `░` empty).
/// Bar width is fixed at 20 cells; the line stays under 80 columns
/// for typical terminal widths.
fn mem_bar_line<'a>(
label: &'a str,
percent: f64,
value_kib: u64,
total_kib: u64,
color: Style,
) -> Line<'a> {
let bar_width: usize = 20;
let filled = ((percent.clamp(0.0, 100.0) / 100.0) * bar_width as f64) as usize;
let mut bar = String::with_capacity(bar_width * 3);
for _ in 0..filled {
bar.push('\u{2588}'); // full block
}
for _ in filled..bar_width {
bar.push('\u{2591}'); // light shade
}
Line::from(vec![
label.set_style(theme::LABEL),
format!("[{}] ", bar).set_style(color),
format!("{:5.1}% ", percent).set_style(theme::VALUE),
format!(
"{} / {}",
crate::render::format_kib(value_kib),
crate::render::format_kib(total_kib)
)
.set_style(theme::VALUE_OFF),
])
}
/// Render the multi-view tab bar (Per-CPU / System / Info) with the
/// active tab highlighted. Hotkeys `1`/`2`/`3` switch directly; `T`
/// cycles through them in order.
@@ -278,6 +323,56 @@ pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
if any_critical { "CRIT ".set_style(theme::VALUE_HOT) } else { "CRIT ".set_style(theme::VALUE_OFF) },
if any_pl { "PL ".set_style(theme::POWER_LIMIT_FLAG) } else { "PL ".set_style(theme::VALUE_OFF) },
]));
// OS identity (matches cpu-x System tab)
if app.os_info.available {
lines.push(Line::from(vec![
"OS: ".set_style(theme::LABEL),
if app.os_info.name.is_empty() {
"(unknown)".set_style(theme::VALUE_OFF)
} else {
app.os_info.name.as_str().set_style(theme::VALUE)
},
" Kernel: ".set_style(theme::LABEL),
if app.os_info.kernel.is_empty() {
"(unknown)".set_style(theme::VALUE_OFF)
} else {
app.os_info.kernel.as_str().set_style(theme::VALUE)
},
" Host: ".set_style(theme::LABEL),
if app.os_info.hostname.is_empty() {
"(unknown)".set_style(theme::VALUE_OFF)
} else {
app.os_info.hostname.as_str().set_style(theme::VALUE)
},
" Up: ".set_style(theme::LABEL),
crate::meminfo::format_uptime(app.os_info.uptime_secs)
.set_style(theme::VALUE_HOT),
]));
}
// Memory panel (matches cpu-x System tab memory bars). Each line
// shows label, value, and a horizontal bar.
if app.meminfo.available {
let mi = &app.meminfo;
lines.push(Line::from(vec![
"Mem: ".set_style(theme::LABEL_BOLD),
format!(
"{} used / {} total",
crate::render::format_kib(mi.used_kib),
crate::render::format_kib(mi.total_kib)
)
.set_style(theme::VALUE),
]));
lines.push(mem_bar_line("Used: ", mi.percent_used(), mi.used_kib, mi.total_kib, theme::VALUE_HOT));
lines.push(mem_bar_line("Buffers: ", mi.percent_buffers(), mi.buffers_kib, mi.total_kib, theme::VALUE));
lines.push(mem_bar_line("Cached: ", mi.percent_cached(), mi.cached_kib, mi.total_kib, theme::VALUE));
lines.push(mem_bar_line("Free: ", mi.percent_free(), mi.free_kib, mi.total_kib, theme::VALUE_OK));
if mi.swap_total_kib > 0 {
lines.push(mem_bar_line("Swap: ", mi.percent_swap(), mi.swap_used_kib, mi.swap_total_kib, theme::STATUS_WARN));
}
}
lines.push(Line::from(vec![
"Benchmark: ".set_style(theme::LABEL),
if app.bench_line.is_empty() { "(idle)".set_style(theme::VALUE_OFF) } else { app.bench_line.as_str().set_style(theme::VALUE) },
@@ -695,5 +790,16 @@ pub fn buffer_to_string(buf: &ratatui::buffer::Buffer) -> String {
pub fn render_once(app: &App) -> io::Result<()> {
print!("{}", snapshot(app, 140, 50));
// Also dump the System panel as a second snapshot for verification.
eprintln!("--- System panel (verifies v1.4 memory + OS info) ---");
let sys_para = render_system_panel(app, false);
let backend = TestBackend::new(120, 30);
let mut terminal = Terminal::new(backend).expect("test terminal");
terminal
.draw(|f| {
f.render_widget(sys_para, f.area());
})
.expect("draw");
print!("{}", buffer_to_string(terminal.backend().buffer()));
Ok(())
}