// // Copyright (C) <2026> // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . /* This file is the main application file. Main logic and stuff is located here. Including config loading. stages loading, switching between stages, and stuff. */ use iced::widget; use log; use std::process::ExitCode; use iced::{Element, Task}; use crate::stage::{KiraConfig, StageAction}; use crate::stages::license; use crate::stages::network; use crate::stages::welcome; use crate::stages::welcome::WelcomeStage; rust_i18n::i18n!("src/locales", fallback = "en"); mod kira_color_bar; mod kira_disk_layout; mod kira_logger; mod kira_scroll_list; mod kira_size; mod kira_sysinfo; mod kira_theming; mod stage; mod stages; enum Views { Start, Welcome(welcome::WelcomeStage), License(license::LicenseStage), Network(stages::network::NetworkStage), TimeZone(stages::timezone::TimeZoneStage), Locale(stages::locale::LocaleStage), Keyboard(stages::keyboard::KeyboardStage), Partition(stages::partition::PartitionStage), Security(stages::security::SecurityStage), } enum Message { Start, Welcome(welcome::Message), License(license::Message), Network(stages::network::Message), TimeZone(stages::timezone::Message), Locale(stages::locale::Message), Keyboard(stages::keyboard::Message), Partition(stages::partition::Message), Security(stages::security::Message), } struct KiraState { current_view: Views, toml_config: toml::Table, config: KiraConfig, stages_save: Vec, } const CONFIG_PATH_STR: &str = "kira_config.toml"; fn load_kira_config(config_path: &str) -> Result> { use std::fs; use toml::Table; // 1. Read the file content into a String let content = fs::read_to_string(config_path)?; // 2. Parse the string into a Table let table: Table = content.parse()?; log::info!("{:?}", table); Ok(table) } impl KiraState { fn boot() -> (Self, Task) { ( Self { current_view: Views::Start, toml_config: toml::Table::new(), config: KiraConfig { config_trail: Vec::new(), }, stages_save: Vec::new(), }, Task::done(Message::Start), ) } } fn view(k_state: &KiraState) -> Element<'_, Message> { match &k_state.current_view { Views::Start => Element::new(widget::space()), Views::Welcome(wellcome_stage) => wellcome_stage.view().map(Message::Welcome), Views::License(license_stage) => license_stage.view().map(Message::License), Views::Network(network_stage) => network_stage.view().map(Message::Network), Views::TimeZone(timezone_stage) => timezone_stage.view().map(Message::TimeZone), Views::Locale(locale_stage) => locale_stage.view().map(Message::Locale), Views::Keyboard(keyboard_stage) => keyboard_stage.view().map(Message::Keyboard), Views::Partition(partition_stage) => partition_stage.view().map(Message::Partition), Views::Security(security_stage) => security_stage.view().map(Message::Security) } } fn update(k_state: &mut KiraState, message: Message) -> Task { match message { Message::Start => match load_kira_config(CONFIG_PATH_STR) { Ok(conf) => { log::info!("Config loaded!"); k_state.toml_config = conf; k_state.current_view = Views::Welcome(WelcomeStage::new()); Task::none() } Err(ex) => { log::error!("Error reading config {}: {}", CONFIG_PATH_STR, ex); iced::exit() } }, Message::Welcome(wlc_msg) => { if let Views::Welcome(wlc_view) = &mut k_state.current_view { match wlc_view.update(wlc_msg) { StageAction::Next(welcome_res) => { k_state.config.config_trail.push(welcome_res); let s_state = std::mem::replace(&mut k_state.current_view, Views::License(license::LicenseStage {})); k_state.stages_save.push(s_state); Task::none() } StageAction::Abort => iced::exit(), _ => Task::none(), } } else { Task::none() } } Message::License(license_message) => { if let Views::License(license_view) = &mut k_state.current_view { let action = license_view.update(license_message); match action { StageAction::Next(license_res) => { k_state.config.config_trail.push(license_res); let s_state = std::mem::replace(&mut k_state.current_view, Views::Network(network::NetworkStage::new(&k_state.toml_config))); k_state.stages_save.push(s_state); Task::done(Message::Network(network::Message::CheckNetwork)) } StageAction::Abort => iced::exit(), StageAction::Back => { k_state.current_view = k_state.stages_save.pop().unwrap(); k_state.config.config_trail.pop(); Task::none() } StageAction::None => Task::none(), } } else { Task::none() } } Message::Network(network_message) => { if let Views::Network(network_view) = &mut k_state.current_view { let update_result = network_view.update(network_message); match update_result { network::UpdateResult::Task(t) => t.map(Message::Network), network::UpdateResult::StageAction(action) => match action { StageAction::Next(network_res) => { k_state.config.config_trail.push(network_res); let s_state = std::mem::replace(&mut k_state.current_view,Views::TimeZone(stages::timezone::TimeZoneStage::new())); k_state.stages_save.push(s_state); Task::none() } StageAction::Back => { k_state.current_view = k_state.stages_save.pop().unwrap(); k_state.config.config_trail.pop(); Task::none() } _ => Task::none(), }, } } else { Task::none() } } Message::TimeZone(timezone_message) => { if let Views::TimeZone(timezone_view) = &mut k_state.current_view { let action = timezone_view.update(timezone_message); match action { StageAction::Next(tz_res) => { k_state.config.config_trail.push(tz_res); let s_state = std::mem::replace(&mut k_state.current_view,Views::Locale(stages::locale::LocaleStage::new(&k_state.config))); k_state.stages_save.push(s_state); Task::none() } StageAction::Abort => iced::exit(), StageAction::Back => { k_state.config.config_trail.pop(); k_state.current_view = k_state.stages_save.pop().unwrap(); Task::none() } StageAction::None => Task::none(), } } else { Task::none() } } Message::Locale(locale_msg) => { if let Views::Locale(locale_view) = &mut k_state.current_view { match locale_view.update(locale_msg) { StageAction::Next(locale_res) => { k_state.config.config_trail.push(locale_res); let s_state = std::mem::replace(&mut k_state.current_view,Views::Keyboard(stages::keyboard::KeyboardStage::new())); k_state.stages_save.push(s_state); Task::none() } StageAction::Back => { k_state.config.config_trail.pop(); k_state.current_view = k_state.stages_save.pop().unwrap(); Task::none() } _ => Task::none(), } } else { Task::none() } } Message::Keyboard(keyboard_msg) => { if let Views::Keyboard(keyboard_view) = &mut k_state.current_view { match keyboard_view.update(keyboard_msg) { StageAction::Next(keyboard_res) => { match stages::partition::PartitionStage::new(&k_state.toml_config) { Ok(part_stage) => { k_state.config.config_trail.push(keyboard_res); let s_state = std::mem::replace(&mut k_state.current_view,Views::Partition(part_stage)); k_state.stages_save.push(s_state); } Err(ex) => { log::error!("Error, cannot init partition stage: {}", ex); } } Task::none() } StageAction::Back => { k_state.config.config_trail.pop(); k_state.current_view = k_state.stages_save.pop().unwrap(); Task::none() } _ => Task::none(), } } else { Task::none() } }, Message::Partition(partition_msg) => { if let Views::Partition(partition_view) = &mut k_state.current_view { match partition_view.update(partition_msg) { StageAction::Next(partition_res) => { k_state.config.config_trail.push(partition_res); let s_state = std::mem::replace(&mut k_state.current_view,Views::Security(stages::security::SecurityStage::new())); k_state.stages_save.push(s_state); Task::none() } StageAction::Back => { k_state.config.config_trail.pop(); k_state.current_view = k_state.stages_save.pop().unwrap(); Task::none() } _ => Task::none(), } } else { Task::none() } }, Message::Security(security_msg) => { if let Views::Security(security_view) = &mut k_state.current_view { match security_view.update(security_msg) { StageAction::Next(security_res) => { k_state.config.config_trail.push(security_res); //let s_state = std::mem::replace(&mut k_state.current_view, Task::none() } StageAction::Back => { k_state.config.config_trail.pop(); k_state.current_view = k_state.stages_save.pop().unwrap(); Task::none() } _ => Task::none(), } } else { Task::none() } }, } } pub fn main() -> ExitCode { // Init logger kira_logger::init().unwrap(); let iced_result = iced::application(KiraState::boot, update, view) .window(iced::window::Settings { icon: Some( iced::window::icon::from_file_data(include_bytes!("media/icon.png"), None) .expect("icon should be a valid PNG"), ), ..Default::default() }) .centered() .theme(kira_theming::main_theme()) .default_font(iced::Font::MONOSPACE) .run(); match iced_result { Ok(()) => ExitCode::SUCCESS, Err(ex) => { log::error!("ICED Error: {}", ex); ExitCode::from(42) } // Custom error code } }