Skip to main content

ferrix_app/
messages.rs

1/* messages.rs
2 *
3 * Copyright 2025 Michail Krasnov <mskrasnov07@ya.ru>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21//! UI events handler & Data Updater
22
23use 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    // AddTotalCPUUsage,
87    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::AddTotalCPUUsage => {
181            //     fx.cpu_usage_chart.push_value(match &fx.curr_proc_stat {
182            //         DataLoadingState::Loaded(val) => val.cpu.unwrap().usage_percentage({
183            //             let prev = fx.prev_proc_stat.clone().unwrap();
184            //             prev.cpu
185            //         }) as f64,
186            //         _ => 0.,
187            //     });
188            //     Task::none()
189            // }
190            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                        // fx.show_chart_elements,
373                    );
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                            // kern.mods.modules.sort_by_key(|md| md.name.clone());
435                            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        // TODO: add error handling
622        let _ = crate::utils::xdg_open(url);
623        Task::none()
624    }
625
626    fn save_settings(&mut self) -> Task<Message> {
627        // TODO: add error handling
628        let _ = self
629            .settings
630            .write(get_home().join(".config").join(SETTINGS_PATH));
631        Task::none()
632    }
633}