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    drm::Video,
27    init::{Connection, SystemdServices},
28    ram::RAM,
29    sys::{Groups, OsRelease, Users},
30    traits::ToJson,
31};
32use iced::Task;
33
34use crate::{
35    DataLoadingState, Ferrix, KernelData, Page, SETTINGS_PATH, Style, System,
36    dmi::DMIResult,
37    export::{ExportData, ExportFormat, ExportMode},
38    utils::get_home,
39};
40
41#[derive(Debug, Clone)]
42pub enum Message {
43    DataReceiver(DataReceiverMessage),
44    ExportManager(ExportManagerMessage),
45    Settings(SettingsMessage),
46    Buttons(ButtonsMessage),
47
48    SelectPage(Page),
49    Dummy,
50}
51
52impl Message {
53    pub fn update<'a>(self, state: &'a mut Ferrix) -> Task<Message> {
54        match self {
55            Self::DataReceiver(data) => data.update(state),
56            Self::ExportManager(export) => export.update(state),
57            Self::Settings(settings) => settings.update(state),
58            Self::Buttons(buttons) => buttons.update(state),
59
60            Self::SelectPage(page) => state.select_page(page),
61            Self::Dummy => Task::none(),
62        }
63    }
64}
65
66impl Ferrix {
67    fn select_page(&mut self, page: Page) -> Task<Message> {
68        self.current_page = page;
69        Task::none()
70    }
71}
72
73#[derive(Debug, Clone)]
74pub enum DataReceiverMessage {
75    GetCPUData,
76    CPUDataReceived(DataLoadingState<Processors>),
77
78    GetProcStat,
79    ProcStatReceived(DataLoadingState<Stat>),
80
81    GetRAMData,
82    RAMDataReceived(DataLoadingState<RAM>),
83
84    GetDMIData,
85    DMIDataReceived(DataLoadingState<DMIResult>),
86
87    GetBatInfo,
88    BatInfoReceived(DataLoadingState<BatInfo>),
89
90    GetDRMData,
91    DRMDataReceived(DataLoadingState<Video>),
92
93    GetOsReleaseData,
94    OsReleaseDataReceived(DataLoadingState<OsRelease>),
95
96    GetKernelData,
97    KernelDataReceived(DataLoadingState<KernelData>),
98
99    GetUsersData,
100    UsersDataReceived(DataLoadingState<Users>),
101
102    GetGroupsData,
103    GroupsDataReceived(DataLoadingState<Groups>),
104
105    GetSystemdServices,
106    SystemdServicesReceived(DataLoadingState<SystemdServices>),
107
108    GetSystemData,
109    SystemDataReceived(DataLoadingState<System>),
110}
111
112impl DataReceiverMessage {
113    pub fn update<'a>(self, fx: &'a mut Ferrix) -> Task<Message> {
114        match self {
115            Self::CPUDataReceived(state) => {
116                fx.proc_data = state;
117                Task::none()
118            }
119            Self::GetCPUData => Task::perform(
120                async move {
121                    let proc = Processors::new();
122                    match proc {
123                        Ok(proc) => DataLoadingState::Loaded(proc),
124                        Err(why) => DataLoadingState::Error(why.to_string()),
125                    }
126                },
127                |val| Message::DataReceiver(Self::CPUDataReceived(val)),
128            ),
129            Self::ProcStatReceived(state) => {
130                if fx.curr_proc_stat.is_some() {
131                    fx.prev_proc_stat = fx.curr_proc_stat.clone();
132                } else if fx.curr_proc_stat.is_none() && fx.prev_proc_stat.is_none() {
133                    fx.prev_proc_stat = state.clone();
134                }
135                fx.curr_proc_stat = state;
136                Task::none()
137            }
138            Self::GetProcStat => Task::perform(
139                async move {
140                    let stat = Stat::new();
141                    match stat {
142                        Ok(stat) => DataLoadingState::Loaded(stat),
143                        Err(why) => DataLoadingState::Error(why.to_string()),
144                    }
145                },
146                |val| Message::DataReceiver(Self::ProcStatReceived(val)),
147            ),
148            Self::DMIDataReceived(state) => {
149                fx.is_polkit = true;
150                if state.is_some() && fx.is_polkit {
151                    fx.dmi_data = state;
152                } else if !fx.is_polkit {
153                    fx.dmi_data = state;
154                }
155                Task::none()
156            }
157            Self::GetDMIData => {
158                if fx.dmi_data.is_none() && fx.current_page == Page::DMI && !fx.is_polkit {
159                    Task::perform(async move { crate::dmi::get_dmi_data().await }, |val| {
160                        Message::DataReceiver(Self::DMIDataReceived(val))
161                    })
162                } else {
163                    Task::none()
164                }
165            }
166            Self::BatInfoReceived(state) => {
167                fx.bat_data = state;
168                Task::none()
169            }
170            Self::GetBatInfo => Task::perform(
171                async move {
172                    let bat = BatInfo::new();
173                    match bat {
174                        Ok(bat) => DataLoadingState::Loaded(bat),
175                        Err(why) => DataLoadingState::Error(why.to_string()),
176                    }
177                },
178                |val| Message::DataReceiver(Self::BatInfoReceived(val)),
179            ),
180            Self::DRMDataReceived(state) => {
181                fx.drm_data = state;
182                Task::none()
183            }
184            Self::GetDRMData => Task::perform(
185                async move {
186                    let drm = Video::new();
187                    match drm {
188                        Ok(drm) => DataLoadingState::Loaded(drm),
189                        Err(why) => DataLoadingState::Error(why.to_string()),
190                    }
191                },
192                |val| Message::DataReceiver(Self::DRMDataReceived(val)),
193            ),
194            Self::RAMDataReceived(state) => {
195                fx.ram_data = state;
196                Task::none()
197            }
198            Self::GetRAMData => Task::perform(
199                async move {
200                    let ram = RAM::new();
201                    match ram {
202                        Ok(ram) => DataLoadingState::Loaded(ram),
203                        Err(why) => DataLoadingState::Error(why.to_string()),
204                    }
205                },
206                |val| Message::DataReceiver(Self::RAMDataReceived(val)),
207            ),
208            Self::OsReleaseDataReceived(state) => {
209                fx.osrel_data = state;
210                Task::none()
211            }
212            Self::GetOsReleaseData => Task::perform(
213                async move {
214                    let osrel = OsRelease::new();
215                    match osrel {
216                        Ok(osrel) => DataLoadingState::Loaded(osrel),
217                        Err(why) => DataLoadingState::Error(why.to_string()),
218                    }
219                },
220                |val| Message::DataReceiver(Self::OsReleaseDataReceived(val)),
221            ),
222            Self::KernelDataReceived(state) => {
223                fx.info_kernel = state;
224                Task::none()
225            }
226            Self::GetKernelData => Task::perform(
227                async move {
228                    let kern = KernelData::new();
229                    match kern {
230                        Ok(mut kern) => {
231                            kern.mods.modules.sort_by_key(|md| md.name.clone());
232                            DataLoadingState::Loaded(kern)
233                        }
234                        Err(why) => DataLoadingState::Error(why.to_string()),
235                    }
236                },
237                |val| Message::DataReceiver(Self::KernelDataReceived(val)),
238            ),
239            Self::UsersDataReceived(state) => {
240                fx.users_list = state;
241                Task::none()
242            }
243            Self::GetUsersData => Task::perform(
244                async move {
245                    let users = Users::new();
246                    match users {
247                        Ok(mut users) => {
248                            users.users.sort_by_key(|usr| usr.uid);
249                            DataLoadingState::Loaded(users)
250                        }
251                        Err(why) => DataLoadingState::Error(why.to_string()),
252                    }
253                },
254                |val| Message::DataReceiver(Self::UsersDataReceived(val)),
255            ),
256            Self::GroupsDataReceived(state) => {
257                fx.groups_list = state;
258                Task::none()
259            }
260            Self::GetGroupsData => Task::perform(
261                async move {
262                    let groups = Groups::new();
263                    match groups {
264                        Ok(mut groups) => {
265                            groups.groups.sort_by_key(|grp| grp.gid);
266                            DataLoadingState::Loaded(groups)
267                        }
268                        Err(why) => DataLoadingState::Error(why.to_string()),
269                    }
270                },
271                |val| Message::DataReceiver(Self::GroupsDataReceived(val)),
272            ),
273            Self::SystemdServicesReceived(state) => {
274                fx.sysd_services_list = state;
275                Task::none()
276            }
277            Self::GetSystemdServices => Task::perform(
278                async move {
279                    let conn = Connection::session().await;
280                    if let Err(why) = conn {
281                        return DataLoadingState::Error(why.to_string());
282                    }
283                    let conn = conn.unwrap();
284
285                    let srv_list = SystemdServices::new_from_connection(&conn).await;
286                    match srv_list {
287                        Ok(srv_list) => DataLoadingState::Loaded(srv_list),
288                        Err(why) => DataLoadingState::Error(why.to_string()),
289                    }
290                },
291                |val| Message::DataReceiver(Self::SystemdServicesReceived(val)),
292            ),
293            Self::SystemDataReceived(state) => {
294                fx.system = state;
295                Task::none()
296            }
297            Self::GetSystemData => Task::perform(
298                async move {
299                    let sys = System::new();
300                    match sys {
301                        Ok(sys) => DataLoadingState::Loaded(sys),
302                        Err(why) => DataLoadingState::Error(why.to_string()),
303                    }
304                },
305                |val| Message::DataReceiver(Self::SystemDataReceived(val)),
306            ),
307        }
308    }
309}
310
311pub type ExportToFilePath = String;
312
313#[derive(Debug, Clone)]
314pub enum ExportManagerMessage {
315    ExportData(ExportToFilePath),
316    ExportFormatSelected(ExportFormat),
317    ExportModeSelected(ExportMode),
318}
319
320impl ExportManagerMessage {
321    pub fn update<'a>(self, fx: &'a mut Ferrix) -> Task<Message> {
322        match self {
323            Self::ExportData(path) => fx.export_data(&path),
324            _ => Task::none(),
325        }
326    }
327}
328
329impl Ferrix {
330    fn export_data(&mut self, path: &str) -> Task<Message> {
331        let json = ExportData::from(self)
332            .to_json()
333            .unwrap_or("{error}".to_string());
334        let _ = std::fs::write(path, json);
335        Task::none()
336    }
337}
338
339#[derive(Debug, Clone)]
340pub enum SettingsMessage {
341    ChangeStyle(Style),
342    ChangeUpdatePeriod(u8),
343}
344
345impl SettingsMessage {
346    pub fn update<'a>(self, fx: &'a mut Ferrix) -> Task<Message> {
347        match self {
348            Self::ChangeStyle(style) => fx.change_style(style),
349            Self::ChangeUpdatePeriod(secs) => fx.change_update_period(secs),
350        }
351    }
352}
353
354impl Ferrix {
355    fn change_style(&mut self, style: Style) -> Task<Message> {
356        self.settings.style = style;
357        Task::none()
358    }
359
360    fn change_update_period(&mut self, per: u8) -> Task<Message> {
361        self.settings.update_period = per;
362        Task::none()
363    }
364}
365
366#[derive(Debug, Clone)]
367pub enum ButtonsMessage {
368    LinkButtonPressed(String),
369    SaveSettingsButtonPressed,
370}
371
372impl ButtonsMessage {
373    pub fn update<'a>(self, fx: &'a mut Ferrix) -> Task<Message> {
374        match self {
375            Self::LinkButtonPressed(url) => fx.go_to_url(&url),
376            Self::SaveSettingsButtonPressed => fx.save_settings(),
377        }
378    }
379}
380
381impl Ferrix {
382    fn go_to_url(&self, url: &str) -> Task<Message> {
383        // TODO: add error handling
384        let _ = crate::utils::xdg_open(url);
385        Task::none()
386    }
387
388    fn save_settings(&mut self) -> Task<Message> {
389        // TODO: add error handling
390        let _ = self
391            .settings
392            .write(get_home().join(".config").join(SETTINGS_PATH));
393        Task::none()
394    }
395}