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    where
56        B::Error: Send + Sync + 'static,
57    {
58        let mut event_loop = EventLoop::new();
59
60        event_loop.run(Duration::from_millis(self.config.tick_interval_ms));
61        self.tui
62            .title
63            .set_tick_interval(self.config.tick_interval_ms);
64
65        while self.running {
66            self.tui.render(terminal, &self.info)?;
67
68            match event_loop.next().await? {
69                Event::Tick => {
70                    self.info = self.framework.get_info();
71                }
72                Event::Input(event) => {
73                    if let Some(app_event) = self.tui.handle_input(event)? {
74                        self.handle_event(app_event, &event_loop)?;
75                    }
76                }
77            }
78        }
79
80        Ok(())
81    }
82
83    fn handle_event(&mut self, event: AppEvent, event_loop: &EventLoop) -> color_eyre::Result<()> {
84        match event {
85            AppEvent::Quit => self.quit(),
86            AppEvent::SetMaxChargeLimit(value) => {
87                self.framework.set_max_charge_limit(value)?;
88                self.info.max_charge_limit = Some(value);
89            }
90            AppEvent::SetFingerprintBrightness(percentage) => {
91                match self.framework.set_fp_brightness(percentage) {
92                    Err(report) => match report.downcast::<EcErrorWrapper>() {
93                        Ok(EcErrorWrapper(EcError::Response(EcResponseStatus::InvalidVersion))) => {
94                            self.tui.set_error(
95                                "Couldn't set fingerprint brightness. Please, update your BIOS."
96                                    .to_string(),
97                            );
98                        }
99                        Ok(error) => {
100                            return Err(Report::from(error));
101                        }
102                        Err(report) => {
103                            return Err(report);
104                        }
105                    },
106                    Ok(_) => {
107                        self.info.fp_brightness_percentage = Some(percentage);
108                    }
109                }
110            }
111            AppEvent::SetKeyboardBrightness(percentage) => {
112                self.framework.set_kb_brightness(percentage);
113                self.info.kb_brightness_percentage = Some(percentage);
114            }
115            AppEvent::SetTickInterval(interval_ms) => {
116                self.config.set_tick_interval(interval_ms)?;
117                event_loop.set_tick_interval(Duration::from_millis(interval_ms));
118                self.tui.title.set_tick_interval(interval_ms);
119            }
120        }
121
122        Ok(())
123    }
124
125    fn quit(&mut self) {
126        self.running = false;
127    }
128}