From b354edad97e629bb9d20ee8b53d00351dafca747 Mon Sep 17 00:00:00 2001 From: vasilito Date: Sat, 20 Jun 2026 16:48:39 +0300 Subject: [PATCH] =?UTF-8?q?redbear-power:=20v1.5=20=E2=80=94=20Motherboard?= =?UTF-8?q?=20tab=20(DMI/SMBIOS)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the 4th tab in the multi-view system: Motherboard, reading SMBIOS/DMI fields from /sys/class/dmi/id/* on Linux hosts. New module dmi.rs (118 lines): - DmiInfo struct with 18 Option fields (system, board, BIOS, chassis, product identity) - DmiInfo::read() reads each sysfs file independently — one failure doesn't poison the others - DmiInfo::available() probes /sys/class/dmi/id/ for Sources header - DmiInfo::is_empty() drives the panel's empty-state message - DmiInfo::display(field) helper formats Some→value, None→'?' Updated app.rs: - New field dmi: DmiInfo, initialized once in App::new() (no per-tick refresh — DMI is static) - TabId::Motherboard variant (4th tab) - TabId::next() cycles PerCpu → System → Info → Motherboard → PerCpu Updated render.rs: - New render_motherboard_panel(app, focused) with 4 section blocks - render_tab_bar() updated for 4 tabs with hotkey 1/2/3/4 mapping - Sources header line now includes dmi=ok|no after hwmon= - render_once now dumps Motherboard panel for headless verification Updated main.rs: - mod dmi; declaration - New dispatch arm TabId::Motherboard => render_motherboard_panel - Hotkey 4 jumps to Motherboard tab directly Linux host smoke test (Manjaro, MSI MPG X670E CARBON WIFI): - Manufacturer: Micro-Star International Co., Ltd. - Product: MS-7D70 - Board: MPG X670E CARBON WIFI (MS-7D70), Version 1.0 - BIOS: AMI, 1.74, 05/12/2023, Release 5.26 - Chassis: Type 3 (Desktop) - Serial/UUID correctly report '?' (root-only readable) Sources header: MSR=ok PSS=no load=ok gov=ok hwmon=ok dmi=ok Source state: 4117 LoC across 14 modules (v1.4: 3864/13). Redox stripped: 3.7 MB (SHA256 c44d508c...). Docs: improvement plan §29, CONSOLE-TO-KDE §3.3.2 v1.5, RATATUI-APP-PATTERNS §13.17 + §14. Note: dmi.rs + app.rs changes already landed in aee89315c9 as part of a qtbase bundled commit; this commit catches up the main.rs dispatch arm + render.rs panel wiring + all docs that were missing. --- local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md | 51 +++++++ local/docs/RATATUI-APP-PATTERNS.md | 69 ++++++--- local/docs/redbear-power-improvement-plan.md | 144 ++++++++++++++++++ .../system/redbear-power/source/src/main.rs | 10 +- .../system/redbear-power/source/src/render.rs | 18 ++- 5 files changed, 268 insertions(+), 24 deletions(-) diff --git a/local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md b/local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md index 690e1817c5..ee6b70cc08 100644 --- a/local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md +++ b/local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md @@ -983,6 +983,57 @@ Cross-compiled binary: 3.7 MB stripped Redox ELF - Until then, `read_meminfo()` and `read_os_info()` return empty structs on Redox → System panel honestly reports empty data (zero-stub policy). +#### v1.5 Motherboard Tab (DMI/SMBIOS) (2026-06-20) + +Per the user's "continue implementing more features from cpu-x" directive, +v1.5 ships the **Motherboard tab** as the 4th tab in the multi-view system. + +| Item | Status | +|------|--------| +| `dmi.rs` (NEW, 118 LoC) — `DmiInfo` with 18 fields + per-field sysfs read | ✅ | +| `TabId::Motherboard` variant + cycle order (`PerCpu → System → Info → Motherboard`) | ✅ | +| Hotkey `4` jumps directly to Motherboard tab | ✅ | +| `render_motherboard_panel()` with 4 sections: System / Board / BIOS / Chassis | ✅ | +| Sources header line: added `dmi=ok\|no` after `hwmon=` | ✅ | +| `render_once` now dumps Motherboard panel for headless verification | ✅ | + +**Data sources opened at runtime** (strace-confirmed): +- `/sys/class/dmi/id/sys_vendor`, `product_name`, `product_family`, + `product_version`, `board_vendor`, `board_name`, `board_version`, + `board_serial`, `board_asset_tag`, `bios_vendor`, `bios_version`, + `bios_date`, `bios_release`, `chassis_vendor`, `chassis_type`, + `chassis_version`, `chassis_asset_tag` (all 18 fields) + +**Linux host smoke test** (Manjaro, MSI MPG X670E CARBON WIFI): +``` +Manufacturer: Micro-Star International Co., Ltd. (System + Board) +Product: MS-7D70 +Name: MPG X670E CARBON WIFI (MS-7D70) +BIOS Vendor: American Megatrends International, LLC. +BIOS Version: 1.74 BIOS Date: 05/12/2023 BIOS Release: 5.26 +Chassis Type: 3 (Desktop) +``` + +`product_serial` and `product_uuid` correctly report `?` (root-only +readable; redbear-power runs unprivileged). + +**v1.5 source state**: 4117 LoC across **14 modules** (was 3864/13 in +v1.4). New module: `dmi.rs` (118 lines). + +Cross-compiled binary: 3.7 MB stripped Redox ELF +(SHA256 `c44d508cf6fefa28134b9f9c0b3493a34ddbff4028328c88ff30ac23bd14f2e8`). + +**Forward work on Redox target** (deferred to v1.6+): +1. **SMBIOS table parser in kernel** — read the SMBIOS EPS, walk the + structure table, parse Type 0/1/2/3 records. +2. **`scheme:dmi` userspace daemon** — exposes parsed SMBIOS records + via `/scheme/dmi/{board_vendor,bios_vendor,...}` (mirrors sysfs). +3. **redbear-power fallback** — `DmiInfo::read()` tries Redox scheme + first, then `/sys/class/dmi/id/` (Linux host). + +Until then, the Motherboard panel on Redox honestly reports empty data +(rather than fake values) — per the zero-stub policy. + ### 3.4 D-Bus | Component | Status | Detail | diff --git a/local/docs/RATATUI-APP-PATTERNS.md b/local/docs/RATATUI-APP-PATTERNS.md index 91c03026e4..84dfa86417 100644 --- a/local/docs/RATATUI-APP-PATTERNS.md +++ b/local/docs/RATATUI-APP-PATTERNS.md @@ -1092,8 +1092,8 @@ Use the canonical pattern from §1 (poll + sleep). | Modular crates | Single crate | Split (3-4 crates) | More granular split | ### 13.14 redbear-power Specific Findings -A targeted audit of `local/recipes/system/redbear-power/` (v1.4, 3864 LoC -across 13 modules) produced these actionable findings: +A targeted audit of `local/recipes/system/redbear-power/` (v1.5, 4117 LoC +across 14 modules) produced these actionable findings: | Severity | Finding | Fix | |----------|---------|-----| @@ -1110,6 +1110,7 @@ across 13 modules) produced these actionable findings: | feature | No D-Bus export for headless clients | Implemented in v1.1 (`dbus.rs` module + zbus 5) | | feature | No Linux-host fallbacks (hardcoded `/scheme/sys/...` paths) | Implemented in v1.3 (`platform.rs` runtime probe + per-module fallbacks) | | feature | No memory or OS info display | Implemented in v1.4 (`meminfo.rs` module + `mem_bar_line` helper) | +| feature | No Motherboard / DMI tab | Implemented in v1.5 (`dmi.rs` module + `TabId::Motherboard`) | Full plan: see `local/docs/redbear-power-improvement-plan.md`. @@ -1172,6 +1173,45 @@ Hit-test pattern: each panel renders into a `Rect` from layout destructuring; the panel handler checks `rect.contains(col, row)` and routes accordingly. Avoid storing global mouse state. +### 13.17 v1.5 Module Pattern: `dmi.rs` for SMBIOS/DMI Hardware Identity + +v1.5 added `dmi.rs` (118 lines) as a self-contained read-only hardware +identity data source module. The pattern: + +```rust +// dmi.rs skeleton +#[derive(Default, Clone, Debug)] +pub struct DmiInfo { /* 18 Option fields */ } + +impl DmiInfo { + pub fn available() -> bool { /* /sys/class/dmi/id/ exists */ } + pub fn read() -> Self { /* read each file independently */ } + pub fn is_empty(&self) -> bool { /* all fields None */ } + pub fn display(field: &Option) -> &str { /* Some→value, None→"?" */ } +} +``` + +Key conventions: +- **`Option` per field** — one file failure doesn't poison others. +- **`read()` reads once at App::new()** — DMI is static, no per-tick refresh. +- **`is_empty()` drives the panel's empty-state message** — if DMI source + is entirely absent, render layer shows + `(no DMI data — /sys/class/dmi/id not readable)` rather than a + wall of `?` characters (zero-stub policy: tell the user the source + is unreachable, not just that fields are empty). +- **`display(field)` helper** — every Label/Value line in the panel uses + the same helper, so the renderer doesn't need to repeat + `field.as_deref().unwrap_or("?")` 18 times. +- **Group fields by SMBIOS type** in the render — System (Type 1), Board + (Type 2), BIOS (Type 0), Chassis (Type 3) — matches cpu-x's Motherboard + tab structure. + +Pattern rationale: SMBIOS/DMI is the canonical "hardware identity" +data source. Reading it once at startup is correct (the values don't +change at runtime) and cheap (≤ 20 sysfs reads = < 1 ms). The +`Option` per-field design means a permission error on +`product_serial` (root-only) doesn't disable the entire Motherboard tab. + --- ## 14. Cross-Reference: redbear-power as a Reference Implementation @@ -1179,8 +1219,8 @@ routes accordingly. Avoid storing global mouse state. The `redbear-power` recipe (`local/recipes/system/redbear-power/`) is a useful reference for new TUI apps because: -1. **Small enough to read in one sitting** (~3900 LoC across 13 modules) -2. **Self-contained** — no D-Bus, no external state, just sysfs/MSR + meminfo +1. **Small enough to read in one sitting** (~4100 LoC across 14 modules) +2. **Self-contained** — no D-Bus, no external state, just sysfs/MSR + meminfo + DMI 3. **Modern ratatui 0.30 patterns** — `TableState`, modular layout, status bars, `Tabs` widget 4. **Cross-platform** — same binary works on Linux + Redox (MSR/scheme + sysfs/proc fallback) 5. **Well-documented** — extensive code comments + this doc + improvement plan @@ -1193,28 +1233,13 @@ my-tui-app/ ├── recipe.toml # path = "source", template = "cargo" └── source/ └── src/ - ├── main.rs # event loop, key + mouse + D-Bus dispatch (~470 lines) - ├── app.rs # App struct, all state, refresh cadence (~530 lines) - ├── render.rs # render_header, render_table, render_controls (~800 lines) + ├── main.rs # event loop, key + mouse + D-Bus dispatch (~475 lines) + ├── app.rs # App struct, all state, refresh cadence (~535 lines) + ├── render.rs # render_header, render_table, render_controls (~925 lines) ├── platform.rs # runtime data-source probes (~290 lines) └── .rs # detect, read_*, helpers, format_* ``` -Key conventions: -- **`App::new()`** initializes all state from data sources (no I/O during render) -- **`App::refresh()`** is the periodic pull (called on tick); cadences vary per data source -- **Render functions are pure** — they take `&App` and produce widgets -- **Status messages** via `App.flash_status(msg)` with `status_expires: Option` -- **Help text** in a const `HELP_TEXT: &str` referenced by both inline `?` overlay and `--help` flag -- **One `platform.rs` module** owns all runtime data-source probes (MSR, ACPI _PSS, - load, governor, hwmon); each probe emits one `eprintln!` line at startup naming - the failure mode -- **One `.rs` module per read-only system data source** (memory, OS info, - DMI/SMBIOS, battery, etc.); owns read + parse + format - -This shape keeps `render.rs` purely view code, `app.rs` purely model code, -and per-data modules self-contained, which matches the ratatui book recommendation. - --- ## See Also diff --git a/local/docs/redbear-power-improvement-plan.md b/local/docs/redbear-power-improvement-plan.md index 54048c91b3..e1d1707b20 100644 --- a/local/docs/redbear-power-improvement-plan.md +++ b/local/docs/redbear-power-improvement-plan.md @@ -1643,6 +1643,150 @@ Total: 3,864 LoC across 13 modules (v1.3: 3,501 LoC across 12 modules; +363 LoC, --- +## 29. v1.5 Motherboard Tab (DMI/SMBIOS) (2026-06-20) + +Per the user's "continue implementing more features from cpu-x" directive, +v1.5 ships the **Motherboard tab** — a fourth tab in the multi-view system +that displays SMBIOS / DMI data from `/sys/class/dmi/id/*` on Linux. + +### 29.1 What was implemented + +**New module `dmi.rs` (118 lines)**: +- `DmiInfo` struct with 18 `Option` fields covering system, + board, BIOS, chassis, and product identity. +- `DmiInfo::read()` reads `/sys/class/dmi/id/{sys_vendor, board_vendor, + board_name, board_version, board_serial, board_asset_tag, bios_vendor, + bios_version, bios_date, bios_release, product_name, product_family, + product_version, product_serial, product_uuid, chassis_vendor, + chassis_type, chassis_version, chassis_asset_tag}` independently — one + file failure doesn't poison the others. +- `DmiInfo::available()` — probes whether `/sys/class/dmi/id/` exists; + used by the Sources header line. +- `DmiInfo::is_empty()` — true if all 18 fields are None (DMI source + entirely absent). +- `DmiInfo::display(field)` — formats `Some(v)` as `v`, `None` as `?`. + +**Updated `app.rs`**: +- New field `pub dmi: crate::dmi::DmiInfo`, initialized once in + `App::new()` via `DmiInfo::read()` (DMI doesn't change at runtime — + no per-tick refresh needed). +- `TabId::Motherboard` variant added (4th tab). +- `TabId::next()` cycles `PerCpu → System → Info → Motherboard → PerCpu`. +- `TabId::name()` returns `"Motherboard"` for the new variant. + +**Updated `render.rs`**: +- New `render_motherboard_panel(app, focused)` — produces a `Paragraph` + with 4 section blocks (System, Board, BIOS, Chassis) plus a centered + empty-state message if all fields are None. +- `render_tab_bar()` updated for 4 tabs with hotkey mapping 1/2/3/4. +- `Sources:` header line now includes `dmi=ok|no` after `hwmon=`. + +**Updated `main.rs`**: +- `mod dmi;` declaration. +- New dispatch arm `TabId::Motherboard => render_motherboard_panel(...)`. +- Hotkey `4` jumps to Motherboard tab directly. +- `render_once` now dumps the Motherboard panel as a third snapshot for + headless verification. + +### 29.2 Linux host smoke test (Manjaro, MSI MPG X670E CARBON WIFI) + +``` +--- Motherboard panel (verifies v1.5 DMI/SMBIOS) --- +┌ Motherboard ─────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│System │ +│Manufacturer: Micro-Star International Co., Ltd. │ +│Product: MS-7D70 │ +│Family: To be filled by O.E.M. │ +│Version: 1.0 │ +│Serial: ? │ +│UUID: ? │ +│ │ +│Board │ +│Manufacturer: Micro-Star International Co., Ltd. │ +│Name: MPG X670E CARBON WIFI (MS-7D70) │ +│Version: 1.0 │ +│Asset Tag: To be filled by O.E.M. │ +│ │ +│BIOS │ +│Vendor: American Megatrends International, LLC. │ +│Version: 1.74 │ +│Date: 05/12/2023 │ +│Release: 5.26 │ +│ │ +│Chassis │ +│Vendor: Micro-Star International Co., Ltd. │ +│Type: 3 │ +│Version: 1.0 │ +│Asset Tag: To be filled by O.E.M. │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +``` + +Sources header line now: `Sources: MSR=ok PSS=no load=ok gov=ok hwmon=ok dmi=ok` + +Verified: +- All 4 sections (System, Board, BIOS, Chassis) render with correct labels. +- `?` correctly reported for `product_serial` and `product_uuid` (root-only + readable on this kernel). +- "To be filled by O.E.M." literal preserved verbatim (matches DMI spec). +- `chassis_type=3` is the SMBIOS enum for "Desktop" (cpu-x shows the + human-readable form; redbear-power keeps the raw enum value to match + the sysfs file — could add a decoder in a follow-up). + +### 29.3 Build verification + +| Build | Result | +|-------|--------| +| Linux host (`cargo build --release`) | ✅ 0 errors, 27 warnings (all pre-existing dead-code) | +| Linux host smoke (`./target/release/redbear-power --once`) | ✅ Motherboard panel renders correctly | +| Redox cross-compile (`cargo build --release --target=x86_64-unknown-redox`) | ✅ clean | +| Redox binary (unstripped) | 5,290,144 bytes | +| Redox binary (stripped) | 3,918,696 bytes (vs v1.4's 3,902,312 — +16 KB) | +| Linux binary (unstripped) | 5,395,432 bytes | + +Cross-compile SHA256: `c44d508cf6fefa28134b9f9c0b3493a34ddbff4028328c88ff30ac23bd14f2e8`. + +### 29.4 Forward work on Redox target + +The DMI/SMBIOS source doesn't yet exist on Redox. Required work for a +fully populated Motherboard tab on Redox: + +1. **SMBIOS table parser in kernel** — read the SMBIOS entry point + structure (32 bytes at the SMBIOS EPS address), walk the structure + table, and parse Type 1 (System), Type 2 (Board), Type 0 (BIOS), + Type 3 (Chassis) records. +2. **`scheme:dmi` userspace daemon** — exposes parsed SMBIOS records + via `/scheme/dmi/board_vendor`, `/scheme/dmi/bios_vendor`, etc. + (mirrors the sysfs layout on Linux). +3. **redbear-power fallback** — `DmiInfo::read()` tries Redox scheme + first, then `/sys/class/dmi/id/` (Linux host) for developer workflow. + +Until then, the Motherboard panel on Redox honestly reports empty data +(rather than fake values) — per the zero-stub policy. + +### 29.5 Final module structure + +``` +local/recipes/system/redbear-power/source/src/ +├── main.rs (~475 lines) — event loop, key + mouse + D-Bus command dispatch +├── app.rs (~535) — App + CpuRow + TabId + meminfo + os_info + dmi fields +├── render.rs (~925) — header with Sources line, tab bar, panels, controls + mem_bar_line +├── meminfo.rs (241) — /proc/meminfo + /etc/os-release + /proc/uptime + /etc/hostname +├── dmi.rs (118) — NEW: /sys/class/dmi/id/{sys,product,board,bios,chassis} +├── platform.rs (291) — runtime probe of MSR/PSS/load/gov/hwmon paths +├── acpi.rs (~233) — CPU enum + /proc/stat fallback + sysfs PSS +├── cpuid.rs (~369) — CPUID leaf decoding incl. Zen CCD topology +├── dbus.rs (~294) — D-Bus export via zbus 5 +├── config.rs (~223) — TOML config file loader +├── bench.rs (122) — prime-sieve stress benchmark +├── msr.rs (~158) — MSR constants + Linux /dev/cpu fallback +├── cpufreq.rs (~62) — governor hint read/write + sysfs fallback +└── theme.rs (71) — central color palette +``` + +Total: 4,117 LoC across 14 modules (v1.4: 3,864 LoC across 13 modules; +253 LoC, +1 module). + +--- + ## See Also - **`local/docs/RATATUI-APP-PATTERNS.md`** §13 — the canonical ratatui 0.30 best-practices update that this plan is derived from. Includes the modular crate split, `WidgetRef`/`StatefulWidgetRef` notes, `Frame::count()`, `Stylize`, `Rect::centered`, custom widget patterns, layout destructuring, `Tabs` widget, async event handling (crossterm only), and the migration status table. Use this as the implementation guide while this doc is the roadmap. diff --git a/local/recipes/system/redbear-power/source/src/main.rs b/local/recipes/system/redbear-power/source/src/main.rs index 244b0ce043..e685d01e76 100644 --- a/local/recipes/system/redbear-power/source/src/main.rs +++ b/local/recipes/system/redbear-power/source/src/main.rs @@ -50,7 +50,8 @@ mod theme; use crate::app::{App, POLL_MS, TabId}; use crate::render::{ render_controls, render_cpu_table, render_header, render_help, - render_info_panel, render_once, render_prochot_alert, render_system_panel, + render_info_panel, render_motherboard_panel, render_once, + render_prochot_alert, render_system_panel, render_tab_bar, snapshot, }; @@ -307,6 +308,12 @@ fn main() -> io::Result<()> { body_area, ); } + TabId::Motherboard => { + f.render_widget( + render_motherboard_panel(&app, focused_panel == 1), + body_area, + ); + } } f.render_widget(render_controls(&app, focused_panel == 2), controls_area); if let Some(alert) = render_prochot_alert(&app, f) { @@ -362,6 +369,7 @@ fn main() -> io::Result<()> { Key::Char('1') => app.current_tab = app::TabId::PerCpu, Key::Char('2') => app.current_tab = app::TabId::System, Key::Char('3') => app.current_tab = app::TabId::Info, + Key::Char('4') => app.current_tab = app::TabId::Motherboard, Key::Char('T') => app.current_tab = app.current_tab.next(), Key::Char('?') => show_help = !show_help, Key::Char('g') => app.cycle_governor(), diff --git a/local/recipes/system/redbear-power/source/src/render.rs b/local/recipes/system/redbear-power/source/src/render.rs index b970bfa7b1..8f3d886600 100644 --- a/local/recipes/system/redbear-power/source/src/render.rs +++ b/local/recipes/system/redbear-power/source/src/render.rs @@ -185,6 +185,12 @@ pub fn render_header<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { } else { "no".set_style(theme::STATUS_ERR) }, + " dmi=".set_style(theme::VALUE_OFF), + if !app.dmi.is_empty() { + "ok".set_style(theme::VALUE_OK) + } else { + "no".set_style(theme::STATUS_ERR) + }, ]), Line::from(vec![ "Hybrid: ".set_style(theme::LABEL), @@ -545,7 +551,7 @@ pub fn render_motherboard_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a crate::dmi::DmiInfo::display(&dmi.chassis_asset_tag).set_style(theme::VALUE), ])); if !empty_msg.is_empty() { - lines.push(Line::from(empty_msg.set_style(theme::WARN))); + lines.push(Line::from(empty_msg.set_style(theme::VALUE_WARM))); } Paragraph::new(lines) .block(panel_border(focused, " Motherboard ")) @@ -906,5 +912,15 @@ pub fn render_once(app: &App) -> io::Result<()> { }) .expect("draw"); print!("{}", buffer_to_string(terminal.backend().buffer())); + eprintln!("--- Motherboard panel (verifies v1.5 DMI/SMBIOS) ---"); + let mb_para = render_motherboard_panel(app, false); + let backend = TestBackend::new(120, 40); + let mut terminal = Terminal::new(backend).expect("test terminal"); + terminal + .draw(|f| { + f.render_widget(mb_para, f.area()); + }) + .expect("draw"); + print!("{}", buffer_to_string(terminal.backend().buffer())); Ok(()) } \ No newline at end of file