1use ferrix_lib::{
24 battery::BatInfo,
25 cpu::{Processors, Stat},
26 cpu_freq::CpuFreq,
27 drm::Video,
28 init::{Connection, SystemdServices},
29 parts::Mounts,
30 ram::{RAM, Swaps},
31 soft::InstalledPackages,
32 sys::{Groups, KModules, Kernel, OsRelease, Users},
33 traits::ToJson,
34 vulnerabilities::Vulnerabilities,
35};
36use iced::{Task, color};
37
38use crate::{
39 DataLoadingState, Page, SETTINGS_PATH, System,
40 dmi::DMIData,
41 export::{ExportData, ExportFormat, ExportMode},
42 ferrix::{Ferrix, FerrixData},
43 settings::Style,
44 styles::CPU_CHARTS_COLORS,
45 utils::get_home,
46 widgets::line_charts::LineSeries,
47};
48
49#[derive(Debug, Clone)]
50pub enum Message {
51 DataReceiver(DataReceiverMessage),
52 ExportManager(ExportManagerMessage),
53 Settings(SettingsMessage),
54 Buttons(ButtonsMessage),
55
56 SelectPage(Page),
57 Dummy,
58}
59
60impl Message {
61 pub fn update<'a>(self, state: &'a mut Ferrix) -> Task<Message> {
62 match self {
63 Self::DataReceiver(data) => data.update(&mut state.data, state.current_page),
64 Self::ExportManager(export) => export.update(state),
65 Self::Settings(settings) => settings.update(state),
66 Self::Buttons(buttons) => buttons.update(state),
67
68 Self::SelectPage(page) => state.select_page(page),
69 Self::Dummy => Task::none(),
70 }
71 }
72}
73
74impl Ferrix {
75 fn select_page(&mut self, page: Page) -> Task<Message> {
76 self.current_page = page;
77 Task::none()
78 }
79}
80
81#[derive(Debug, Clone)]
82pub enum DataReceiverMessage {
83 GetCPUData,
84 CPUDataReceived(DataLoadingState<Processors>),
85
86 AddCPUCoreLineSeries,
88 ChangeShowCPUChartElements(usize),
89
90 GetProcStat,
91 ProcStatReceived(DataLoadingState<Stat>),
92
93 GetCPUFrequency,
94 CPUFrequencyReceived(DataLoadingState<CpuFreq>),
95
96 GetCPUVulnerabilities,
97 CPUVulnerabilitiesReveived(DataLoadingState<Vulnerabilities>),
98
99 GetRAMData,
100 RAMDataReceived(DataLoadingState<RAM>),
101
102 GetSwapData,
103 SwapDataReceived(DataLoadingState<Swaps>),
104
105 AddTotalRAMUsage,
106
107 GetStorageData,
108 StorageDataReceived(DataLoadingState<Mounts>),
109
110 GetDMIData,
111 DMIDataReceived(DataLoadingState<DMIData>),
112
113 GetBatInfo,
114 BatInfoReceived(DataLoadingState<BatInfo>),
115
116 GetDRMData,
117 DRMDataReceived(DataLoadingState<Video>),
118
119 GetOsReleaseData,
120 OsReleaseDataReceived(DataLoadingState<OsRelease>),
121
122 GetKernelData,
123 KernelDataReceived(DataLoadingState<Kernel>),
124
125 GetKModsData,
126 KModsDataReceived(DataLoadingState<KModules>),
127
128 GetUsersData,
129 UsersDataReceived(DataLoadingState<Users>),
130
131 GetGroupsData,
132 GroupsDataReceived(DataLoadingState<Groups>),
133
134 GetSystemdServices,
135 SystemdServicesReceived(DataLoadingState<SystemdServices>),
136
137 GetPackagesList,
138 PackagesListReceived(DataLoadingState<InstalledPackages>),
139
140 GetSystemData,
141 SystemDataReceived(DataLoadingState<System>),
142}
143
144impl DataReceiverMessage {
145 pub fn update<'a>(self, fx: &'a mut FerrixData, cur_page: Page) -> Task<Message> {
146 match self {
147 Self::CPUDataReceived(state) => {
148 fx.proc_data = state;
149 Task::none()
150 }
151 Self::GetCPUData => Task::perform(
152 async move {
153 let proc = Processors::new();
154 match proc {
155 Ok(proc) => DataLoadingState::Loaded(proc),
156 Err(why) => DataLoadingState::Error(why.to_string()),
157 }
158 },
159 |val| Message::DataReceiver(Self::CPUDataReceived(val)),
160 ),
161 Self::ProcStatReceived(state) => {
162 if fx.curr_proc_stat.is_some() {
163 fx.prev_proc_stat = fx.curr_proc_stat.clone();
164 } else if fx.curr_proc_stat.is_none() && fx.prev_proc_stat.is_none() {
165 fx.prev_proc_stat = state.clone();
166 }
167 fx.curr_proc_stat = state;
168 Task::none()
169 }
170 Self::GetProcStat => Task::perform(
171 async move {
172 let stat = Stat::new();
173 match stat {
174 Ok(stat) => DataLoadingState::Loaded(stat),
175 Err(why) => DataLoadingState::Error(why.to_string()),
176 }
177 },
178 |val| Message::DataReceiver(Self::ProcStatReceived(val)),
179 ),
180 Self::AddCPUCoreLineSeries => {
191 let curr_proc = &fx.curr_proc_stat;
192 let prev_proc = &fx.prev_proc_stat;
193
194 if curr_proc.is_none() || prev_proc.is_none() {
195 return Task::none();
196 }
197 let curr_proc = curr_proc.to_option().unwrap();
198 let prev_proc = prev_proc.to_option().unwrap();
199
200 if curr_proc.cpus.len() != prev_proc.cpus.len() {
201 return Task::none();
202 }
203 let len = curr_proc.cpus.len();
204
205 let colors = CPU_CHARTS_COLORS;
206 for id in 0..len {
207 let percent =
208 curr_proc.cpus[id].usage_percentage(Some(prev_proc.cpus[id])) as f64;
209
210 if fx.show_cpus_chart.get(&id).is_none() {
211 let color = {
212 if colors.len() - 1 < id {
213 color!(255, 255, 255)
214 } else {
215 colors[id]
216 }
217 };
218 let mut line =
219 LineSeries::new(format!("CPU #{id}"), color, fx.show_chart_elements);
220 line.push(percent);
221
222 fx.cpu_usage_chart.push_series(line);
223 fx.show_cpus_chart.insert(id);
224 } else {
225 fx.cpu_usage_chart.push_to(id, percent);
226 }
227 }
228
229 Task::none()
230 }
231 Self::ChangeShowCPUChartElements(elems) => {
232 fx.show_chart_elements = elems;
233
234 fx.cpu_usage_chart.set_max_values(elems);
235 fx.ram_usage_chart.set_max_values(elems);
236
237 Task::none()
238 }
239 Self::CPUFrequencyReceived(state) => {
240 fx.cpu_freq = state;
241 Task::none()
242 }
243 Self::GetCPUFrequency => Task::perform(
244 async move {
245 let cpu_freq = CpuFreq::new();
246 match cpu_freq {
247 Ok(cpu_freq) => DataLoadingState::Loaded(cpu_freq),
248 Err(why) => DataLoadingState::Error(why.to_string()),
249 }
250 },
251 |val| Message::DataReceiver(DataReceiverMessage::CPUFrequencyReceived(val)),
252 ),
253 Self::CPUVulnerabilitiesReveived(state) => {
254 fx.cpu_vulnerabilities = state;
255 Task::none()
256 }
257 Self::GetCPUVulnerabilities => Task::perform(
258 async move {
259 let vuln = Vulnerabilities::new();
260 match vuln {
261 Ok(vuln) => DataLoadingState::Loaded(vuln),
262 Err(why) => DataLoadingState::Error(why.to_string()),
263 }
264 },
265 |val| Message::DataReceiver(Self::CPUVulnerabilitiesReveived(val)),
266 ),
267 Self::StorageDataReceived(state) => {
268 fx.storages = state;
269 Task::none()
270 }
271 Self::GetStorageData => Task::perform(
272 async move {
273 let storage = Mounts::new();
274 match storage {
275 Ok(storage) => DataLoadingState::Loaded(storage),
276 Err(why) => DataLoadingState::Error(why.to_string()),
277 }
278 },
279 |val| Message::DataReceiver(DataReceiverMessage::StorageDataReceived(val)),
280 ),
281 Self::DMIDataReceived(state) => {
282 if state.some_value() && fx.is_polkit {
283 fx.dmi_data = state;
284 } else if !fx.is_polkit {
285 fx.dmi_data = state;
286 }
287 Task::none()
288 }
289 Self::GetDMIData => {
290 if !fx.is_polkit && fx.dmi_data.is_none() && cur_page == Page::DMI {
291 fx.is_polkit = true;
292 Task::perform(async move { crate::dmi::get_dmi_data().await }, |val| {
293 Message::DataReceiver(Self::DMIDataReceived(val))
294 })
295 } else {
296 Task::none()
297 }
298 }
299 Self::BatInfoReceived(state) => {
300 fx.bat_data = state;
301 Task::none()
302 }
303 Self::GetBatInfo => Task::perform(
304 async move {
305 let bat = BatInfo::new();
306 match bat {
307 Ok(bat) => DataLoadingState::Loaded(bat),
308 Err(why) => DataLoadingState::Error(why.to_string()),
309 }
310 },
311 |val| Message::DataReceiver(Self::BatInfoReceived(val)),
312 ),
313 Self::DRMDataReceived(state) => {
314 fx.drm_data = state;
315 Task::none()
316 }
317 Self::GetDRMData => Task::perform(
318 async move {
319 let drm = Video::new();
320 match drm {
321 Ok(drm) => DataLoadingState::Loaded(drm),
322 Err(why) => DataLoadingState::Error(why.to_string()),
323 }
324 },
325 |val| Message::DataReceiver(Self::DRMDataReceived(val)),
326 ),
327 Self::RAMDataReceived(state) => {
328 fx.ram_data = state;
329 Task::none()
330 }
331 Self::GetRAMData => Task::perform(
332 async move {
333 let ram = RAM::new();
334 match ram {
335 Ok(ram) => DataLoadingState::Loaded(ram),
336 Err(why) => DataLoadingState::Error(why.to_string()),
337 }
338 },
339 |val| Message::DataReceiver(Self::RAMDataReceived(val)),
340 ),
341 Self::SwapDataReceived(state) => {
342 fx.swap_data = state;
343 Task::none()
344 }
345 Self::GetSwapData => Task::perform(
346 async move {
347 let swap = Swaps::new();
348 match swap {
349 Ok(swap) => DataLoadingState::Loaded(swap),
350 Err(why) => DataLoadingState::Error(why.to_string()),
351 }
352 },
353 |val| Message::DataReceiver(Self::SwapDataReceived(val)),
354 ),
355 Self::AddTotalRAMUsage => {
356 let ram = &fx.ram_data;
357 let swap = &fx.swap_data;
358
359 if ram.is_none() {
360 return Task::none();
361 }
362 let ram = ram.to_option().unwrap();
363 let ram_color = color!(128, 64, 255);
364
365 let ram_usage = ram.usage_percentage().unwrap_or(0.);
366
367 if fx.ram_usage_chart.series_count() == 0 {
368 let mut ram_line = LineSeries::new(
369 format!("RAM"),
370 ram_color,
371 fx.show_chart_elements,
372 );
374 ram_line.push(ram_usage);
375 fx.ram_usage_chart.push_series(ram_line);
376 } else {
377 fx.ram_usage_chart.push_to(0, ram_usage);
378 }
379
380 if let Some(swap) = swap.to_option() {
381 let len = swap.swaps.len();
382 let colors = CPU_CHARTS_COLORS;
383
384 let current_series_cnt = fx.ram_usage_chart.series_count();
385
386 for id in 0..len {
387 let series_idx = id + 1;
388 let swap_usage = swap.swaps[id].usage_percentage().unwrap_or(0.);
389
390 if series_idx >= current_series_cnt {
391 let color = if colors.len() - 1 < id {
392 color!(255, 255, 128)
393 } else {
394 colors[id]
395 };
396 let mut line = LineSeries::new(
397 swap.swaps[id].filename.clone(),
398 color,
399 fx.show_chart_elements,
400 );
401 line.push(swap_usage);
402
403 fx.ram_usage_chart.push_series(line);
404 } else {
405 fx.ram_usage_chart.push_to(series_idx, swap_usage);
406 }
407 }
408 }
409 Task::none()
410 }
411 Self::OsReleaseDataReceived(state) => {
412 fx.osrel_data = state;
413 Task::none()
414 }
415 Self::GetOsReleaseData => Task::perform(
416 async move {
417 let osrel = OsRelease::new();
418 match osrel {
419 Ok(osrel) => DataLoadingState::Loaded(osrel),
420 Err(why) => DataLoadingState::Error(why.to_string()),
421 }
422 },
423 |val| Message::DataReceiver(Self::OsReleaseDataReceived(val)),
424 ),
425 Self::KernelDataReceived(state) => {
426 fx.kernel_data = state;
427 Task::none()
428 }
429 Self::GetKernelData => Task::perform(
430 async move {
431 let kern = Kernel::new();
432 match kern {
433 Ok(kern) => {
434 DataLoadingState::Loaded(kern)
436 }
437 Err(why) => DataLoadingState::Error(why.to_string()),
438 }
439 },
440 |val| Message::DataReceiver(Self::KernelDataReceived(val)),
441 ),
442 Self::KModsDataReceived(state) => {
443 fx.kmods_data = state;
444 Task::none()
445 }
446 Self::GetKModsData => Task::perform(
447 async move {
448 let kern = KModules::new();
449 match kern {
450 Ok(mut kern) => {
451 kern.modules.sort_by_key(|module| module.name.clone());
452 DataLoadingState::Loaded(kern)
453 }
454 Err(why) => DataLoadingState::Error(why.to_string()),
455 }
456 },
457 |val| Message::DataReceiver(Self::KModsDataReceived(val)),
458 ),
459 Self::UsersDataReceived(state) => {
460 fx.users_list = state;
461 Task::none()
462 }
463 Self::GetUsersData => Task::perform(
464 async move {
465 let users = Users::new();
466 match users {
467 Ok(mut users) => {
468 users.users.sort_by_key(|usr| usr.uid);
469 DataLoadingState::Loaded(users)
470 }
471 Err(why) => DataLoadingState::Error(why.to_string()),
472 }
473 },
474 |val| Message::DataReceiver(Self::UsersDataReceived(val)),
475 ),
476 Self::GroupsDataReceived(state) => {
477 fx.groups_list = state;
478 Task::none()
479 }
480 Self::GetGroupsData => Task::perform(
481 async move {
482 let groups = Groups::new();
483 match groups {
484 Ok(mut groups) => {
485 groups.groups.sort_by_key(|grp| grp.gid);
486 DataLoadingState::Loaded(groups)
487 }
488 Err(why) => DataLoadingState::Error(why.to_string()),
489 }
490 },
491 |val| Message::DataReceiver(Self::GroupsDataReceived(val)),
492 ),
493 Self::SystemdServicesReceived(state) => {
494 fx.sysd_services_list = state;
495 Task::none()
496 }
497 Self::GetSystemdServices => Task::perform(
498 async move {
499 let conn = Connection::session().await;
500 if let Err(why) = conn {
501 return DataLoadingState::Error(why.to_string());
502 }
503 let conn = conn.unwrap();
504
505 let srv_list = SystemdServices::new_from_connection(&conn).await;
506 match srv_list {
507 Ok(srv_list) => DataLoadingState::Loaded(srv_list),
508 Err(why) => DataLoadingState::Error(why.to_string()),
509 }
510 },
511 |val| Message::DataReceiver(Self::SystemdServicesReceived(val)),
512 ),
513 Self::SystemDataReceived(state) => {
514 fx.system = state;
515 Task::none()
516 }
517 Self::GetPackagesList => Task::perform(
518 async move {
519 let pkglist = InstalledPackages::get();
520 match pkglist {
521 Ok(pkglist) => DataLoadingState::Loaded(pkglist),
522 Err(why) => DataLoadingState::Error(why.to_string()),
523 }
524 },
525 |val| Message::DataReceiver(Self::PackagesListReceived(val)),
526 ),
527 Self::PackagesListReceived(state) => {
528 fx.installed_pkgs_list = state;
529 Task::none()
530 }
531 Self::GetSystemData => Task::perform(
532 async move {
533 let sys = System::new();
534 match sys {
535 Ok(sys) => DataLoadingState::Loaded(sys),
536 Err(why) => DataLoadingState::Error(why.to_string()),
537 }
538 },
539 |val| Message::DataReceiver(Self::SystemDataReceived(val)),
540 ),
541 }
542 }
543}
544
545pub type ExportToFilePath = String;
546
547#[derive(Debug, Clone)]
548pub enum ExportManagerMessage {
549 ExportData(ExportToFilePath),
550 ExportFormatSelected(ExportFormat),
551 ExportModeSelected(ExportMode),
552}
553
554impl ExportManagerMessage {
555 pub fn update<'a>(self, fx: &'a mut Ferrix) -> Task<Message> {
556 match self {
557 Self::ExportData(path) => fx.export_data(&path),
558 _ => Task::none(),
559 }
560 }
561}
562
563impl Ferrix {
564 fn export_data(&mut self, path: &str) -> Task<Message> {
565 let json = ExportData::from(self)
566 .to_json()
567 .unwrap_or("{error}".to_string());
568 let _ = std::fs::write(path, json);
569 Task::none()
570 }
571}
572
573#[derive(Debug, Clone)]
574pub enum SettingsMessage {
575 ChangeStyle(Style),
576 ChangeUpdatePeriod(u8),
577}
578
579impl SettingsMessage {
580 pub fn update<'a>(self, fx: &'a mut Ferrix) -> Task<Message> {
581 match self {
582 Self::ChangeStyle(style) => fx.change_style(style),
583 Self::ChangeUpdatePeriod(secs) => fx.change_update_period(secs),
584 }
585 }
586}
587
588impl Ferrix {
589 fn change_style(&mut self, style: Style) -> Task<Message> {
590 self.settings.style = style;
591 self.data.cpu_usage_chart.set_style(&style.to_theme());
592 self.data.ram_usage_chart.set_style(&style.to_theme());
593 Task::none()
594 }
595
596 fn change_update_period(&mut self, per: u8) -> Task<Message> {
597 self.settings.update_period = per;
598 Task::none()
599 }
600}
601
602#[derive(Debug, Clone)]
603pub enum ButtonsMessage {
604 LinkButtonPressed(String),
605 SaveSettingsButtonPressed,
606 CopyButtonPressed(String),
607}
608
609impl ButtonsMessage {
610 pub fn update<'a>(self, fx: &'a mut Ferrix) -> Task<Message> {
611 match self {
612 Self::LinkButtonPressed(url) => fx.go_to_url(&url),
613 Self::SaveSettingsButtonPressed => fx.save_settings(),
614 Self::CopyButtonPressed(s) => iced::clipboard::write(s),
615 }
616 }
617}
618
619impl Ferrix {
620 fn go_to_url(&self, url: &str) -> Task<Message> {
621 let _ = crate::utils::xdg_open(url);
623 Task::none()
624 }
625
626 fn save_settings(&mut self) -> Task<Message> {
627 let _ = self
629 .settings
630 .write(get_home().join(".config").join(SETTINGS_PATH));
631 Task::none()
632 }
633}