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