redbear-power: v1.5 — Motherboard tab (DMI/SMBIOS)

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<String> 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.
This commit is contained in:
2026-06-20 16:48:39 +03:00
parent aee89315c9
commit b354edad97
5 changed files with 268 additions and 24 deletions
+51
View File
@@ -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 |
+47 -22
View File
@@ -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<String> 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<String>) -> &str { /* Some→value, None→"?" */ }
}
```
Key conventions:
- **`Option<String>` 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<String>` 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)
└── <data>.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<Instant>`
- **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 `<data>.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
@@ -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<String>` 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.
@@ -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(),
@@ -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(())
}