Skip to main content

framework_tool_tui/
app.rs

1use color_eyre::eyre::Report;
2use framework_lib::chromium_ec::{CrosEc, EcError, EcResponseStatus};
3use ratatui::{prelude::Backend, Terminal};
4use std::{sync::Arc, time::Duration};
5
6use crate::{
7    config::Config,
8    event::{Event, EventLoop},
9    framework::{fingerprint::Fingerprint, info::FrameworkInfo, EcErrorWrapper, Framework},
10    tui::Tui,
11};
12
13pub const APP_TITLE: &str = " Framework System ";
14pub const FOOTER_HELP: &str = "[Tab] Switch panels [Up/Down] Scroll [Enter] Edit/Apply [Left/Right] Adjust value [Esc] Cancel [q] Quit";
15pub const VERSION: &str = env!("CARGO_PKG_VERSION");
16
17pub struct App {
18    framework: Framework,
19    info: FrameworkInfo,
20    running: bool,
21    tui: Tui,
22    config: Config,
23}
24
25pub enum AppEvent {
26    Quit,
27    SetMaxChargeLimit(u8),
28    SetFingerprintBrightness(u8),
29    SetKeyboardBrightness(u8),
30    SetTickInterval(u64),
31}
32
33impl App {
34    pub fn new() -> color_eyre::Result<Self> {
35        let ec = CrosEc::new();
36        let fingerprint = Arc::new(Fingerprint::new(&ec)?);
37        // Pre-fetch framework info
38        let mut framework = Framework::new(ec, fingerprint.clone());
39        let info = framework.get_info();
40
41        // Load config (or create default on first startup)
42        let config = Config::load_or_create()?;
43        let tui = Tui::new(fingerprint, &info, config.clone())?;
44
45        Ok(Self {
46            framework,
47            info,
48            running: true,
49            tui,
50            config,
51        })
52    }
53
54    pub async fn run<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> color_eyre::Result<()> {
55        let mut event_loop = EventLoop::new();
56
57        event_loop.run(Duration::from_millis(self.config.tick_interval_ms));
58        self.tui
59            .title
60            .set_tick_interval(self.config.tick_interval_ms);
61
62        while self.running {
63            self.tui.render(terminal, &self.info)?;
64
65            match event_loop.next().await? {
66                Event::Tick => {
67                    self.info = self.framework.get_info();
68                }
69                Event::Input(event) => {
70                    if let Some(app_event) = self.tui.handle_input(event)? {
71                        self.handle_event(app_event, &event_loop)?;
72                    }
73                }
74            }
75        }
76
77        Ok(())
78    }
79
80    fn handle_event(&mut self, event: AppEvent, event_loop: &EventLoop) -> color_eyre::Result<()> {
81        match event {
82            AppEvent::Quit => self.quit(),
83            AppEvent::SetMaxChargeLimit(value) => {
84                self.framework.set_max_charge_limit(value)?;
85                self.info.max_charge_limit = Some(value);
86            }
87            AppEvent::SetFingerprintBrightness(percentage) => {
88                match self.framework.set_fp_brightness(percentage) {
89                    Err(report) => match report.downcast::<EcErrorWrapper>() {
90                        Ok(EcErrorWrapper(EcError::Response(EcResponseStatus::InvalidVersion))) => {
91                            self.tui.set_error(
92                                "Couldn't set fingerprint brightness. Please, update your BIOS."
93                                    .to_string(),
94                            );
95                        }
96                        Ok(error) => {
97                            return Err(Report::from(error));
98                        }
99                        Err(report) => {
100                            return Err(report);
101                        }
102                    },
103                    Ok(_) => {
104                        self.info.fp_brightness_percentage = Some(percentage);
105                    }
106                }
107            }
108            AppEvent::SetKeyboardBrightness(percentage) => {
109                self.framework.set_kb_brightness(percentage);
110                self.info.kb_brightness_percentage = Some(percentage);
111            }
112            AppEvent::SetTickInterval(interval_ms) => {
113                self.config.set_tick_interval(interval_ms)?;
114                event_loop.set_tick_interval(Duration::from_millis(interval_ms));
115                self.tui.title.set_tick_interval(interval_ms);
116            }
117        }
118
119        Ok(())
120    }
121
122    fn quit(&mut self) {
123        self.running = false;
124    }
125}