Skip to content

Commit 0e48cac

Browse files
committed
Add app
1 parent d6cab27 commit 0e48cac

File tree

12 files changed

+2106
-0
lines changed

12 files changed

+2106
-0
lines changed

Cargo.lock

Lines changed: 888 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "dmitui"
3+
version = "0.1.0"
4+
edition = "2024"
5+
license = "GPL-3.0-or-later"
6+
description = "DmiTui"
7+
readme = "README.md"
8+
homepage = "https://github.com/pythops/dmitui"
9+
repository = "https://github.com/pythops/dmitui"
10+
11+
[dependencies]
12+
ratatui = "0.29"
13+
crossterm = "0.29"
14+
anyhow = "1"
15+
clap = { version = "4", features = ["derive", "cargo"] }
16+
libc = "0.2"
17+
uuid = "1"
18+
19+
[profile.release]
20+
lto = "fat"
21+
strip = true
22+
codegen-units = 1

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<div align="center">
2+
<h1> 🚧 Work In Progress 🚧 </h1>
3+
<br>
4+
<h2> DMI table decoder </h2>
5+
</div>
6+
7+
## 💡 Prerequisites
8+
9+
A Linux based OS.
10+
11+
## 🚀 Installation
12+
13+
### ⚒️ Build from source
14+
15+
To build `dmitui`:
16+
17+
```
18+
cargo build --release
19+
```
20+
21+
This will produce an executable file at `target/release/dmitui` that you can copy to a directory in your `$PATH`.
22+
23+
## 🪄 Usage
24+
25+
Run the following command to start `dmitui`:
26+
27+
```
28+
sudo dmitui
29+
```
30+
31+
## ⚖️ License
32+
33+
GPLv3

src/app.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use anyhow::Result;
2+
use ratatui::Frame;
3+
4+
use crate::dmi::DMI;
5+
6+
#[non_exhaustive]
7+
#[derive(Debug)]
8+
pub enum ActivePopup {
9+
Help,
10+
}
11+
12+
#[derive(Debug)]
13+
pub struct App {
14+
pub running: bool,
15+
pub dmi: DMI,
16+
}
17+
18+
impl App {
19+
pub fn new() -> Result<Self> {
20+
let dmi = DMI::new()?;
21+
Ok(Self { running: true, dmi })
22+
}
23+
24+
pub fn render(&mut self, frame: &mut Frame) {
25+
self.dmi.render(frame);
26+
}
27+
28+
pub fn quit(&mut self) {
29+
self.running = false;
30+
}
31+
}

src/dmi.rs

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
mod firmware;
2+
mod system;
3+
4+
use std::{
5+
fmt::Display,
6+
fs::File,
7+
io::{BufRead, BufReader, Read},
8+
};
9+
10+
use anyhow::{Result, bail};
11+
12+
use crate::dmi::firmware::Firmware;
13+
use crate::dmi::system::System;
14+
15+
use crossterm::event::{KeyCode, KeyEvent};
16+
use ratatui::{
17+
Frame,
18+
layout::{Alignment, Constraint, Direction, Layout},
19+
style::{Color, Style, Stylize},
20+
text::{Line, Span},
21+
widgets::{Block, BorderType, Borders, Padding},
22+
};
23+
24+
#[derive(Debug)]
25+
pub struct DMI {
26+
firmware: Firmware,
27+
system: System,
28+
pub focused_section: FocusedSection,
29+
}
30+
31+
#[non_exhaustive]
32+
#[derive(Debug, PartialEq)]
33+
pub enum FocusedSection {
34+
Firmware,
35+
System,
36+
}
37+
38+
#[derive(Debug)]
39+
pub struct Header {
40+
pub structure_type: StructureType,
41+
pub length: u8,
42+
pub handle: u16,
43+
}
44+
45+
impl From<[u8; 4]> for Header {
46+
fn from(value: [u8; 4]) -> Self {
47+
let structure_type = match value[0] {
48+
0 => StructureType::Firmware,
49+
1 => StructureType::System,
50+
127 => StructureType::End,
51+
_ => StructureType::Other,
52+
};
53+
54+
Self {
55+
structure_type,
56+
length: value[1],
57+
handle: u16::from_be_bytes([value[2], value[3]]),
58+
}
59+
}
60+
}
61+
62+
#[derive(Debug, PartialEq)]
63+
#[repr(u8)]
64+
pub enum StructureType {
65+
Firmware = 0,
66+
System = 1,
67+
End = 127,
68+
Other = 255,
69+
}
70+
71+
// https://www.dmtf.org/dsp/DSP0134
72+
// https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.8.0.pdf
73+
impl DMI {
74+
pub fn new() -> Result<Self> {
75+
let mut firmware: Option<Firmware> = None;
76+
let mut system: Option<System> = None;
77+
78+
let mem_file = File::open("/sys/firmware/dmi/tables/DMI")?;
79+
let mut file = BufReader::new(mem_file);
80+
81+
loop {
82+
// Read header
83+
let mut header_buffer: [u8; 4] = [0; 4];
84+
file.read_exact(&mut header_buffer)?;
85+
let header = Header::from(header_buffer);
86+
if header.structure_type == StructureType::End {
87+
break;
88+
}
89+
90+
if header.length < 4 {
91+
bail!("Header size < 4");
92+
}
93+
94+
// Read structure
95+
let mut data = vec![0; header.length.saturating_sub(4) as usize];
96+
file.read_exact(&mut data)?;
97+
98+
// Read Strings
99+
let mut text: Vec<String> = Vec::new();
100+
101+
let mut previous_read_zero: bool = false;
102+
let mut previous_read_string: bool = false;
103+
104+
loop {
105+
let mut string_buf = Vec::new();
106+
if let Ok(number_of_bytes_read) = file.read_until(0, &mut string_buf) {
107+
if number_of_bytes_read == 1 {
108+
if previous_read_zero {
109+
break;
110+
} else {
111+
if previous_read_string {
112+
break;
113+
}
114+
previous_read_zero = true;
115+
}
116+
} else {
117+
string_buf.pop();
118+
text.push(String::from_utf8_lossy(&string_buf).to_string());
119+
previous_read_string = true;
120+
}
121+
}
122+
}
123+
124+
match header.structure_type {
125+
StructureType::Firmware => {
126+
firmware = Some(Firmware::from((data, text, header.length)));
127+
}
128+
StructureType::System => {
129+
system = Some(System::from((data, text)));
130+
}
131+
_ => {}
132+
}
133+
}
134+
135+
Ok(Self {
136+
firmware: firmware.unwrap(),
137+
system: system.unwrap(),
138+
focused_section: FocusedSection::Firmware,
139+
})
140+
}
141+
142+
pub fn handle_key_events(&mut self, key_event: KeyEvent) {
143+
match key_event.code {
144+
KeyCode::Up | KeyCode::Char('k') => {}
145+
KeyCode::Down | KeyCode::Char('j') => {}
146+
KeyCode::Tab => match self.focused_section {
147+
FocusedSection::Firmware => self.focused_section = FocusedSection::System,
148+
FocusedSection::System => self.focused_section = FocusedSection::Firmware,
149+
},
150+
KeyCode::BackTab => match self.focused_section {
151+
FocusedSection::Firmware => self.focused_section = FocusedSection::System,
152+
FocusedSection::System => self.focused_section = FocusedSection::Firmware,
153+
},
154+
_ => {}
155+
}
156+
}
157+
158+
fn title_span(&self, header_section: FocusedSection) -> Span<'_> {
159+
let is_focused = self.focused_section == header_section;
160+
match header_section {
161+
FocusedSection::Firmware => {
162+
if is_focused {
163+
Span::styled(
164+
" Firmware ",
165+
Style::default().bg(Color::Yellow).fg(Color::White).bold(),
166+
)
167+
} else {
168+
Span::from(" Firmware ").fg(Color::DarkGray)
169+
}
170+
}
171+
FocusedSection::System => {
172+
if is_focused {
173+
Span::styled(
174+
" System ",
175+
Style::default().bg(Color::Yellow).fg(Color::White).bold(),
176+
)
177+
} else {
178+
Span::from(" System ").fg(Color::DarkGray)
179+
}
180+
}
181+
}
182+
}
183+
184+
pub fn render(&mut self, frame: &mut Frame) {
185+
let (section_block, help_block) = {
186+
let chunks = Layout::default()
187+
.direction(Direction::Vertical)
188+
.constraints([Constraint::Fill(1), Constraint::Length(3)])
189+
.flex(ratatui::layout::Flex::SpaceBetween)
190+
.split(frame.area());
191+
192+
(chunks[0], chunks[1])
193+
};
194+
195+
frame.render_widget(
196+
Block::default()
197+
.title(Line::from(vec![
198+
self.title_span(FocusedSection::Firmware),
199+
self.title_span(FocusedSection::System),
200+
]))
201+
.title_alignment(Alignment::Left)
202+
.padding(Padding::top(1))
203+
.borders(Borders::ALL)
204+
.border_type(BorderType::Rounded)
205+
.style(Style::default())
206+
.border_style(Style::default().fg(Color::Yellow)),
207+
section_block,
208+
);
209+
210+
// Help banner
211+
let message = Line::from("↑,k : Up | ↓,j : Down | ⇆ : Navigation")
212+
.centered()
213+
.cyan();
214+
215+
frame.render_widget(message, help_block);
216+
217+
match self.focused_section {
218+
FocusedSection::Firmware => {
219+
self.firmware.render(frame, section_block);
220+
}
221+
FocusedSection::System => {
222+
self.system.render(frame, section_block);
223+
}
224+
}
225+
}
226+
}
227+
228+
#[non_exhaustive]
229+
#[derive(Debug, PartialEq)]
230+
pub enum SmbiosType {
231+
Firmware,
232+
System,
233+
Baseboard,
234+
Chassis,
235+
Processor,
236+
Memory,
237+
Cache,
238+
Connector,
239+
Slot,
240+
}
241+
242+
#[derive(Debug)]
243+
pub struct Release {
244+
pub major: u8,
245+
pub minor: u8,
246+
}
247+
248+
impl Release {
249+
fn new(major: u8, minor: u8) -> Self {
250+
Self { major, minor }
251+
}
252+
}
253+
254+
impl Display for Release {
255+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256+
write!(f, "{}.{}", self.major, self.minor)
257+
}
258+
}

0 commit comments

Comments
 (0)