2026-04-23 00:44:01 +02:00
|
|
|
// <Kira Installer - universal Linux installer.>
|
|
|
|
|
// Copyright (C) <2026> <Kira Foundation>
|
2026-04-18 19:02:46 +02:00
|
|
|
|
2026-04-23 00:44:01 +02:00
|
|
|
// 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.
|
2026-04-18 19:02:46 +02:00
|
|
|
|
2026-04-23 00:44:01 +02:00
|
|
|
// 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.
|
2026-04-18 19:02:46 +02:00
|
|
|
|
2026-04-23 00:44:01 +02:00
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2026-04-18 19:02:46 +02:00
|
|
|
|
|
|
|
|
/*
|
2026-04-23 00:44:01 +02:00
|
|
|
This file is the main application file.
|
|
|
|
|
Main logic and stuff is located here. Including config loading.
|
|
|
|
|
stages loading, switching between stages, and stuff.
|
|
|
|
|
*/
|
2026-04-18 19:02:46 +02:00
|
|
|
|
2026-04-16 00:29:43 +02:00
|
|
|
use iced::widget;
|
2026-04-22 14:21:24 +02:00
|
|
|
use std::process::ExitCode;
|
2026-04-16 00:29:43 +02:00
|
|
|
|
2026-04-18 17:54:13 +02:00
|
|
|
use iced::{Element, Task};
|
2026-04-16 00:29:43 +02:00
|
|
|
|
2026-04-23 00:44:01 +02:00
|
|
|
use crate::stage::{KiraConfig, StageAction};
|
2026-04-16 00:29:43 +02:00
|
|
|
use crate::stages::license;
|
2026-04-22 14:21:24 +02:00
|
|
|
use crate::stages::network;
|
2026-04-16 00:29:43 +02:00
|
|
|
use crate::stages::welcome;
|
|
|
|
|
use crate::stages::welcome::WelcomeStage;
|
|
|
|
|
|
|
|
|
|
rust_i18n::i18n!("src/locales", fallback = "en");
|
2026-05-01 23:32:25 +02:00
|
|
|
mod kira_theming;
|
2026-05-04 21:21:41 +02:00
|
|
|
mod kira_scroll_list;
|
2026-05-13 00:24:05 +02:00
|
|
|
mod kira_color_bar;
|
2026-05-19 23:52:20 +02:00
|
|
|
mod kira_disk_layout;
|
2026-05-25 13:30:16 +02:00
|
|
|
mod kira_size;
|
|
|
|
|
mod kira_sysinfo;
|
2026-04-16 00:29:43 +02:00
|
|
|
mod stage;
|
|
|
|
|
mod stages;
|
|
|
|
|
|
|
|
|
|
enum Views {
|
|
|
|
|
Start,
|
|
|
|
|
Welcome(welcome::WelcomeStage),
|
|
|
|
|
License(license::LicenseStage),
|
2026-04-22 14:21:24 +02:00
|
|
|
Network(stages::network::NetworkStage),
|
2026-04-28 23:53:50 +02:00
|
|
|
TimeZone(stages::timezone::TimeZoneStage),
|
2026-05-03 00:56:20 +02:00
|
|
|
Locale(stages::locale::LocaleStage),
|
2026-05-08 00:24:22 +02:00
|
|
|
Keyboard(stages::keyboard::KeyboardStage),
|
2026-05-09 21:50:46 +02:00
|
|
|
Partition(stages::partition::PartitionStage),
|
2026-04-16 00:29:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum Message {
|
2026-04-22 14:21:24 +02:00
|
|
|
Start,
|
2026-04-23 00:44:01 +02:00
|
|
|
Welcome(welcome::Message),
|
|
|
|
|
License(license::Message),
|
|
|
|
|
Network(stages::network::Message),
|
2026-04-28 23:53:50 +02:00
|
|
|
TimeZone(stages::timezone::Message),
|
2026-05-03 00:56:20 +02:00
|
|
|
Locale(stages::locale::Message),
|
2026-05-08 00:24:22 +02:00
|
|
|
Keyboard(stages::keyboard::Message),
|
2026-05-09 21:50:46 +02:00
|
|
|
Partition(stages::partition::Message),
|
2026-04-16 00:29:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct KiraState {
|
|
|
|
|
current_view: Views,
|
|
|
|
|
toml_config: toml::Table,
|
|
|
|
|
config: KiraConfig,
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 14:21:24 +02:00
|
|
|
const CONFIG_PATH_STR: &str = "kira_config.toml";
|
2026-04-16 00:29:43 +02:00
|
|
|
|
2026-04-22 14:21:24 +02:00
|
|
|
fn load_kira_config(config_path: &str) -> Result<toml::Table, Box<dyn std::error::Error>> {
|
|
|
|
|
use std::fs;
|
|
|
|
|
use toml::Table;
|
|
|
|
|
// 1. Read the file content into a String
|
|
|
|
|
let content = fs::read_to_string(config_path)?;
|
2026-04-16 00:29:43 +02:00
|
|
|
|
2026-04-22 14:21:24 +02:00
|
|
|
// 2. Parse the string into a Table
|
|
|
|
|
let table: Table = content.parse()?;
|
2026-04-16 00:29:43 +02:00
|
|
|
|
2026-04-22 14:21:24 +02:00
|
|
|
println!("{:?}", table);
|
|
|
|
|
Ok(table)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl KiraState {
|
|
|
|
|
fn boot() -> (Self, Task<Message>) {
|
2026-04-23 00:44:01 +02:00
|
|
|
(
|
|
|
|
|
Self {
|
|
|
|
|
current_view: Views::Start,
|
|
|
|
|
toml_config: toml::Table::new(),
|
|
|
|
|
config: KiraConfig {
|
|
|
|
|
config_trail: Vec::new(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Task::done(Message::Start),
|
|
|
|
|
)
|
2026-04-16 00:29:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 14:21:24 +02:00
|
|
|
|
2026-04-16 00:29:43 +02:00
|
|
|
fn view(k_state: &KiraState) -> Element<'_, Message> {
|
|
|
|
|
match &k_state.current_view {
|
2026-04-23 00:44:01 +02:00
|
|
|
Views::Start => Element::new(widget::space()),
|
2026-04-16 00:29:43 +02:00
|
|
|
Views::Welcome(wellcome_stage) => wellcome_stage.view().map(Message::Welcome),
|
|
|
|
|
Views::License(license_stage) => license_stage.view().map(Message::License),
|
2026-04-22 14:21:24 +02:00
|
|
|
Views::Network(network_stage) => network_stage.view().map(Message::Network),
|
2026-04-28 23:53:50 +02:00
|
|
|
Views::TimeZone(timezone_stage) => timezone_stage.view().map(Message::TimeZone),
|
2026-05-03 00:56:20 +02:00
|
|
|
Views::Locale(locale_stage) => locale_stage.view().map(Message::Locale),
|
2026-05-08 00:24:22 +02:00
|
|
|
Views::Keyboard(keyboard_stage) => keyboard_stage.view().map(Message::Keyboard),
|
2026-05-09 21:50:46 +02:00
|
|
|
Views::Partition(partition_stage) => partition_stage.view().map(Message::Partition),
|
2026-04-16 00:29:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update(k_state: &mut KiraState, message: Message) -> Task<Message> {
|
|
|
|
|
match message {
|
2026-04-23 00:44:01 +02:00
|
|
|
Message::Start => match load_kira_config(CONFIG_PATH_STR) {
|
|
|
|
|
Ok(conf) => {
|
|
|
|
|
println!("Config loaded!");
|
|
|
|
|
k_state.toml_config = conf;
|
|
|
|
|
k_state.current_view = Views::Welcome(WelcomeStage::new());
|
|
|
|
|
Task::none()
|
|
|
|
|
}
|
|
|
|
|
Err(ex) => {
|
|
|
|
|
println!("Error reading config {}: {}", CONFIG_PATH_STR, ex);
|
|
|
|
|
iced::exit()
|
2026-04-22 14:21:24 +02:00
|
|
|
}
|
|
|
|
|
},
|
2026-04-16 00:29:43 +02:00
|
|
|
Message::Welcome(wlc_msg) => {
|
|
|
|
|
if let Views::Welcome(wlc_view) = &mut k_state.current_view {
|
2026-05-01 23:32:25 +02:00
|
|
|
match wlc_view.update(wlc_msg) {
|
|
|
|
|
StageAction::Next(welcome_res) => {
|
|
|
|
|
k_state.config.config_trail.push(welcome_res);
|
|
|
|
|
k_state.current_view = Views::License(license::LicenseStage {});
|
|
|
|
|
Task::none()
|
|
|
|
|
}
|
|
|
|
|
StageAction::Abort => iced::exit(),
|
|
|
|
|
_ => Task::none(),
|
2026-04-16 00:29:43 +02:00
|
|
|
}
|
2026-05-01 23:32:25 +02:00
|
|
|
} else {
|
|
|
|
|
Task::none()
|
2026-04-16 00:29:43 +02:00
|
|
|
}
|
2026-05-03 00:56:20 +02:00
|
|
|
},
|
2026-04-16 00:29:43 +02:00
|
|
|
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);
|
2026-04-23 00:44:01 +02:00
|
|
|
k_state.current_view =
|
|
|
|
|
Views::Network(network::NetworkStage::new(&k_state.toml_config));
|
|
|
|
|
Task::done(Message::Network(network::Message::CheckNetwork))
|
|
|
|
|
}
|
2026-05-01 23:32:25 +02:00
|
|
|
StageAction::Abort => iced::exit(),
|
|
|
|
|
StageAction::Back => {
|
|
|
|
|
k_state.current_view = Views::Welcome(WelcomeStage::new());
|
|
|
|
|
k_state.config.config_trail.pop();
|
|
|
|
|
Task::none()
|
|
|
|
|
}
|
2026-04-23 00:44:01 +02:00
|
|
|
StageAction::None => Task::none(),
|
2026-04-16 00:29:43 +02:00
|
|
|
}
|
2026-04-23 00:44:01 +02:00
|
|
|
} else {
|
2026-04-16 00:29:43 +02:00
|
|
|
Task::none()
|
|
|
|
|
}
|
2026-05-03 00:56:20 +02:00
|
|
|
},
|
2026-04-22 14:21:24 +02:00
|
|
|
Message::Network(network_message) => {
|
|
|
|
|
if let Views::Network(network_view) = &mut k_state.current_view {
|
2026-04-23 00:44:01 +02:00
|
|
|
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);
|
2026-04-28 23:53:50 +02:00
|
|
|
k_state.current_view =
|
|
|
|
|
Views::TimeZone(stages::timezone::TimeZoneStage::new());
|
|
|
|
|
Task::none()
|
2026-04-23 00:44:01 +02:00
|
|
|
}
|
|
|
|
|
StageAction::Back => {
|
|
|
|
|
k_state.current_view = Views::License(license::LicenseStage {});
|
2026-05-01 23:32:25 +02:00
|
|
|
k_state.config.config_trail.pop();
|
2026-04-23 00:44:01 +02:00
|
|
|
Task::none()
|
|
|
|
|
}
|
|
|
|
|
_ => Task::none(),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2026-04-22 14:21:24 +02:00
|
|
|
Task::none()
|
|
|
|
|
}
|
2026-05-03 00:56:20 +02:00
|
|
|
},
|
2026-04-28 23:53:50 +02:00
|
|
|
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);
|
2026-05-03 00:56:20 +02:00
|
|
|
k_state.current_view = Views::Locale(stages::locale::LocaleStage::new(&k_state.config));
|
|
|
|
|
Task::none()
|
2026-04-28 23:53:50 +02:00
|
|
|
}
|
2026-05-01 23:32:25 +02:00
|
|
|
StageAction::Abort => iced::exit(),
|
2026-05-01 00:29:15 +02:00
|
|
|
StageAction::Back => {
|
|
|
|
|
k_state.config.config_trail.pop();
|
|
|
|
|
k_state.current_view =
|
|
|
|
|
Views::Network(network::NetworkStage::new(&k_state.toml_config));
|
|
|
|
|
Task::done(Message::Network(network::Message::CheckNetwork))
|
|
|
|
|
}
|
2026-04-28 23:53:50 +02:00
|
|
|
StageAction::None => Task::none(),
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Task::none()
|
|
|
|
|
}
|
2026-05-03 00:56:20 +02:00
|
|
|
},
|
|
|
|
|
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);
|
2026-05-08 00:24:22 +02:00
|
|
|
k_state.current_view = Views::Keyboard(stages::keyboard::KeyboardStage::new());
|
|
|
|
|
Task::none()
|
2026-05-03 00:56:20 +02:00
|
|
|
}
|
|
|
|
|
StageAction::Back => {
|
|
|
|
|
k_state.config.config_trail.pop();
|
|
|
|
|
k_state.current_view =
|
|
|
|
|
Views::TimeZone(stages::timezone::TimeZoneStage::new());
|
|
|
|
|
Task::none()
|
|
|
|
|
}
|
2026-05-08 00:24:22 +02:00
|
|
|
_ => 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) => {
|
2026-05-11 23:01:54 +02:00
|
|
|
match stages::partition::PartitionStage::new(&k_state.toml_config) {
|
|
|
|
|
Ok(part_stage) => {
|
|
|
|
|
k_state.config.config_trail.push(keyboard_res);
|
|
|
|
|
k_state.current_view = Views::Partition(part_stage);
|
|
|
|
|
},
|
|
|
|
|
Err(ex) => {
|
|
|
|
|
println!("Error, cannot init partition stage: {}", ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2026-05-09 21:50:46 +02:00
|
|
|
Task::none()
|
2026-05-08 00:24:22 +02:00
|
|
|
}
|
|
|
|
|
StageAction::Back => {
|
|
|
|
|
k_state.config.config_trail.pop();
|
|
|
|
|
k_state.current_view = Views::Locale(stages::locale::LocaleStage::new(&k_state.config));
|
|
|
|
|
Task::none()
|
|
|
|
|
}
|
2026-05-09 21:50:46 +02:00
|
|
|
_ => 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);
|
|
|
|
|
iced::exit()
|
|
|
|
|
}
|
|
|
|
|
StageAction::Back => {
|
|
|
|
|
k_state.config.config_trail.pop();
|
|
|
|
|
k_state.current_view = Views::Keyboard(stages::keyboard::KeyboardStage::new());
|
|
|
|
|
Task::none()
|
|
|
|
|
}
|
2026-05-03 00:56:20 +02:00
|
|
|
_ => Task::none(),
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Task::none()
|
|
|
|
|
}
|
2026-04-28 23:53:50 +02:00
|
|
|
}
|
2026-04-16 00:29:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-04 21:21:41 +02:00
|
|
|
|
2026-04-22 14:21:24 +02:00
|
|
|
pub fn main() -> ExitCode {
|
|
|
|
|
|
|
|
|
|
let iced_result = iced::application(KiraState::boot, update, view)
|
2026-04-23 00:44:01 +02:00
|
|
|
.window(iced::window::Settings {
|
|
|
|
|
icon: Some(
|
2026-04-28 23:53:50 +02:00
|
|
|
iced::window::icon::from_file_data(include_bytes!("media/icon.png"), None)
|
2026-04-23 00:44:01 +02:00
|
|
|
.expect("icon should be a valid PNG"),
|
|
|
|
|
),
|
|
|
|
|
..Default::default()
|
|
|
|
|
})
|
2026-04-16 00:29:43 +02:00
|
|
|
.centered()
|
2026-05-01 23:32:25 +02:00
|
|
|
.theme(kira_theming::main_theme())
|
2026-05-03 18:07:06 +02:00
|
|
|
.default_font(iced::Font::MONOSPACE)
|
2026-04-22 14:21:24 +02:00
|
|
|
.run();
|
|
|
|
|
|
|
|
|
|
match iced_result {
|
|
|
|
|
Ok(()) => ExitCode::SUCCESS,
|
2026-05-03 23:13:25 +02:00
|
|
|
Err(ex) => {
|
|
|
|
|
println!("ICED Error: {}", ex);
|
|
|
|
|
ExitCode::from(42)
|
|
|
|
|
}, // Custom error code
|
2026-04-22 14:21:24 +02:00
|
|
|
}
|
2026-04-16 00:29:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fn main() {
|
|
|
|
|
// println!("Hello, world!");
|
|
|
|
|
// println!("{}", t!("wellcome.text"));
|
|
|
|
|
// // Initialize the state
|
|
|
|
|
// let _res = main_interface();
|
|
|
|
|
|
|
|
|
|
// }
|