qtdeclarative: fix qt_internal_add_resource empty target in cross-compilation
This commit is contained in:
@@ -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)
|
||||
|
||||
+8
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user