1pub mod export;
22pub mod i18n;
23pub mod icons;
24pub mod load_state;
25pub mod modals;
26pub mod pages;
27pub mod styles;
28pub mod utils;
29pub mod widgets;
30
31pub mod dmi;
32pub mod kernel;
33
34pub mod messages;
36use messages::*;
38
39pub use load_state::DataLoadingState;
40pub use pages::*;
41
42use dmi::DMIResult;
43use serde::{Deserialize, Serialize};
44use widgets::{icon_button, sidebar_button};
45
46use anyhow::Result;
47use ferrix_lib::{
48 battery::BatInfo,
49 cpu::{Processors, Stat},
50 drm::Video,
51 init::SystemdServices,
52 ram::RAM,
53 sys::{
54 Groups, LoadAVG, OsRelease, Uptime, Users, get_current_desktop, get_env_vars, get_hostname,
55 get_lang,
56 },
57};
58use iced::{
59 time, widget::{column, container, row, scrollable, text}, Alignment::Center, Element, Length, Padding, Subscription, Task, Theme
60};
61use std::{fmt::Display, fs, path::Path, time::Duration};
62
63use crate::utils::get_home;
64
65const SETTINGS_PATH: &str = "./ferrix.conf";
66
67#[derive(Debug)]
68pub struct Ferrix {
69 pub current_page: Page,
70 pub proc_data: DataLoadingState<Processors>,
71 pub prev_proc_stat: DataLoadingState<Stat>,
72 pub curr_proc_stat: DataLoadingState<Stat>,
73 pub ram_data: DataLoadingState<RAM>,
74 pub dmi_data: DataLoadingState<DMIResult>,
75 pub bat_data: DataLoadingState<BatInfo>,
76 pub drm_data: DataLoadingState<Video>,
77 pub osrel_data: DataLoadingState<OsRelease>,
78 pub info_kernel: DataLoadingState<KernelData>,
79 pub users_list: DataLoadingState<Users>,
80 pub groups_list: DataLoadingState<Groups>,
81 pub sysd_services_list: DataLoadingState<SystemdServices>,
82 pub system: DataLoadingState<System>,
83 pub settings: FXSettings,
84 pub is_polkit: bool,
85}
86
87impl Default for Ferrix {
88 fn default() -> Self {
89 Self {
90 current_page: Page::default(),
91 proc_data: DataLoadingState::Loading,
92 prev_proc_stat: DataLoadingState::Loading,
93 curr_proc_stat: DataLoadingState::Loading,
94 ram_data: DataLoadingState::Loading,
95 dmi_data: DataLoadingState::Loading,
96 bat_data: DataLoadingState::Loading,
97 drm_data: DataLoadingState::Loading,
98 osrel_data: DataLoadingState::Loading,
99 info_kernel: DataLoadingState::Loading,
100 users_list: DataLoadingState::Loading,
101 groups_list: DataLoadingState::Loading,
102 sysd_services_list: DataLoadingState::Loading,
103 system: DataLoadingState::Loading,
104 settings: FXSettings::read(get_home().join(".config").join(SETTINGS_PATH))
105 .unwrap_or_default(),
106 is_polkit: false,
107 }
108 }
109}
110
111#[derive(Debug, Clone, Serialize)]
112pub struct System {
113 pub hostname: Option<String>,
114 pub loadavg: Option<LoadAVG>,
115 pub uptime: Option<Uptime>,
116 pub desktop: Option<String>,
117 pub language: Option<String>,
118 pub env_vars: Vec<(String, String)>,
119}
120
121impl System {
122 pub fn new() -> Result<Self> {
123 Ok(Self {
124 hostname: get_hostname(),
125 loadavg: Some(LoadAVG::new()?),
126 uptime: Some(Uptime::new()?),
127 desktop: get_current_desktop(),
128 language: get_lang(),
129 env_vars: get_env_vars(),
130 })
131 }
132}
133
134#[derive(Debug, Clone, Deserialize, Serialize)]
135pub struct FXSettings {
136 pub update_period: u8,
137 pub style: Style,
138}
139
140impl FXSettings {
141 pub fn read<P: AsRef<Path>>(pth: P) -> Result<Self> {
142 let contents = fs::read_to_string(pth)?;
143 let data = toml::from_str(&contents)?;
144 Ok(data)
145 }
146
147 pub fn write<'a, P: AsRef<Path>>(&'a self, pth: P) -> Result<()> {
148 let contents = toml::to_string(&self)?;
149 fs::write(pth, contents)?;
150 Ok(())
151 }
152}
153
154impl Default for FXSettings {
155 fn default() -> Self {
156 Self {
157 update_period: 1,
158 style: Style::default(),
159 }
160 }
161}
162
163#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq)]
164pub enum Style {
165 Light,
166 #[default]
167 Dark,
168}
169
170impl Style {
171 pub const ALL: &[Self] = &[Self::Light, Self::Dark];
172
173 pub fn to_theme(&self) -> Theme {
174 match self {
175 Self::Light => Theme::GruvboxLight,
176 Self::Dark => Theme::GruvboxDark,
177 }
178 }
179}
180
181impl Display for Style {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 write!(
184 f,
185 "{}",
186 match self {
187 Self::Light => fl!("style-light"),
188 Self::Dark => fl!("style-dark"),
189 }
190 )
191 }
192}
193
194impl Ferrix {
195 pub fn theme(&self) -> Theme {
196 self.settings.style.to_theme()
197 }
198
199 pub fn update(&mut self, message: Message) -> Task<Message> {
200 message.update(self)
201 }
202
203 pub fn subscription(&self) -> Subscription<Message> {
204 let mut scripts = vec![
205 time::every(Duration::from_secs(self.settings.update_period as u64))
206 .map(|_| Message::DataReceiver(DataReceiverMessage::GetCPUData)),
207 time::every(Duration::from_secs(self.settings.update_period as u64))
208 .map(|_| Message::DataReceiver(DataReceiverMessage::GetProcStat)),
209 ];
210
211 if self.current_page == Page::Dashboard {
212 scripts.push(
213 time::every(Duration::from_secs(self.settings.update_period as u64))
214 .map(|_| Message::DataReceiver(DataReceiverMessage::GetRAMData)),
215 );
216 }
217
218 if self.osrel_data.is_none()
219 && (self.current_page == Page::Distro || self.current_page == Page::Dashboard)
220 {
221 scripts.push(
222 time::every(Duration::from_millis(10))
223 .map(|_| Message::DataReceiver(DataReceiverMessage::GetOsReleaseData)),
224 );
225 }
226
227 if self.drm_data.is_none() && self.current_page == Page::Screen {
228 scripts.push(
229 time::every(Duration::from_millis(10))
230 .map(|_| Message::DataReceiver(DataReceiverMessage::GetDRMData)),
231 );
232 } else if self.drm_data.is_some() && self.current_page == Page::Screen {
233 scripts.push(
234 time::every(Duration::from_secs(self.settings.update_period as u64))
235 .map(|_| Message::DataReceiver(DataReceiverMessage::GetDRMData)),
236 );
237 }
238
239 if self.bat_data.is_none() && self.current_page == Page::Battery {
240 scripts.push(
241 time::every(Duration::from_millis(10))
242 .map(|_| Message::DataReceiver(DataReceiverMessage::GetBatInfo)),
243 );
244 } else if self.bat_data.is_some() && self.current_page == Page::Battery {
245 scripts.push(
246 time::every(Duration::from_secs(self.settings.update_period as u64))
247 .map(|_| Message::DataReceiver(DataReceiverMessage::GetBatInfo)),
248 );
249 }
250
251 if self.info_kernel.is_none() && self.current_page == Page::Kernel {
252 scripts.push(
253 time::every(Duration::from_millis(10))
254 .map(|_| Message::DataReceiver(DataReceiverMessage::GetKernelData)),
255 );
256 }
257
258 if self.users_list.is_none() && self.current_page == Page::Users {
259 scripts.push(
260 time::every(Duration::from_millis(10))
261 .map(|_| Message::DataReceiver(DataReceiverMessage::GetUsersData)),
262 );
263 }
264
265 if self.system.is_none() {
266 scripts.push(
267 time::every(Duration::from_millis(10))
268 .map(|_| Message::DataReceiver(DataReceiverMessage::GetSystemData)),
269 );
270 }
271
272 if self.groups_list.is_none() && self.current_page == Page::Groups {
273 scripts.push(
274 time::every(Duration::from_millis(10))
275 .map(|_| Message::DataReceiver(DataReceiverMessage::GetGroupsData)),
276 );
277 }
278
279 if self.sysd_services_list.is_none() && self.current_page == Page::SystemManager {
280 scripts.push(
281 time::every(Duration::from_millis(10))
282 .map(|_| Message::DataReceiver(DataReceiverMessage::GetSystemdServices)),
283 );
284 } else if self.sysd_services_list.is_some() && self.current_page == Page::SystemManager {
285 scripts.push(
286 time::every(Duration::from_secs(
287 self.settings.update_period as u64 * 10u64,
288 ))
289 .map(|_| Message::DataReceiver(DataReceiverMessage::GetSystemdServices)),
290 );
291 }
292
293 if self.system.is_none() && self.current_page == Page::SystemMisc {
294 scripts.push(
295 time::every(Duration::from_millis(10))
296 .map(|_| Message::DataReceiver(DataReceiverMessage::GetSystemData)),
297 );
298 } else if self.system.is_some() && self.current_page == Page::SystemMisc {
299 scripts.push(
300 time::every(Duration::from_secs(self.settings.update_period as u64))
301 .map(|_| Message::DataReceiver(DataReceiverMessage::GetSystemData)),
302 );
303 }
304
305 if self.current_page == Page::DMI && !self.is_polkit && self.dmi_data.is_none() {
306 scripts.push(
307 time::every(Duration::from_secs(1))
308 .map(|_| Message::DataReceiver(DataReceiverMessage::GetDMIData)),
309 );
310 }
311
312 Subscription::batch(scripts)
313 }
314
315 pub fn view<'a>(&'a self) -> Element<'a, Message> {
316 row![sidebar(self.current_page), self.current_page.page(&self)]
317 .spacing(5)
318 .padding(5)
319 .into()
320 }
321}
322
323fn sidebar<'a>(cur_page: Page) -> container::Container<'a, Message> {
324 let buttons_bar = row![
325 icon_button("export", fl!("sidebar-export")).on_press(Message::SelectPage(Page::Export)),
326 icon_button("settings", fl!("sidebar-settings"))
327 .on_press(Message::SelectPage(Page::Settings)),
328 icon_button("about", fl!("sidebar-about")).on_press(Message::SelectPage(Page::About)),
329 ]
330 .spacing(2)
331 .align_y(Center);
332
333 let pages_bar = column![
334 text(fl!("sidebar-hardware")).style(text::secondary),
335 sidebar_button(Page::Dashboard, cur_page),
336 sidebar_button(Page::Processors, cur_page),
337 sidebar_button(Page::Memory, cur_page),
338 sidebar_button(Page::Storage, cur_page),
339 sidebar_button(Page::DMI, cur_page),
340 sidebar_button(Page::Battery, cur_page),
341 sidebar_button(Page::Screen, cur_page),
342 text(fl!("sidebar-admin")).style(text::secondary),
343 sidebar_button(Page::Distro, cur_page),
344 sidebar_button(Page::Users, cur_page),
345 sidebar_button(Page::Groups, cur_page),
346 sidebar_button(Page::SystemManager, cur_page),
347 sidebar_button(Page::Software, cur_page),
348 sidebar_button(Page::Environment, cur_page),
349 sidebar_button(Page::Sensors, cur_page),
350 text(fl!("sidebar-system")).style(text::secondary),
351 sidebar_button(Page::Kernel, cur_page),
352 sidebar_button(Page::KModules, cur_page),
353 sidebar_button(Page::Development, cur_page),
354 sidebar_button(Page::SystemMisc, cur_page),
355 text(fl!("sidebar-manage")).style(text::secondary),
356 sidebar_button(Page::Settings, cur_page),
357 sidebar_button(Page::About, cur_page),
358 ]
359 .padding(Padding::new(0.).right(5.))
360 .spacing(5);
361
362 container(column![buttons_bar, scrollable(pages_bar)].spacing(5))
363 .padding(5)
364 .style(container::bordered_box)
365 .height(Length::Fill)
366}