Compare commits
34 Commits
| Author | SHA256 | Date | |
|---|---|---|---|
| c56da21eab | |||
| ed6ffcdee1 | |||
| 4d8c9540d0 | |||
| 56e87a62cf | |||
| 9769ae409e | |||
| 3abbbe9abd | |||
| b2f510e679 | |||
| a503b2a36f | |||
| f0a8e704c0 | |||
| 187129d314 | |||
| b0ea2e0ee3 | |||
| 4fb49797cb | |||
| bb3f5ed1f0 | |||
| 078589332c | |||
| 6b4550dc8e | |||
| e0c6112422 | |||
| 66f7851574 | |||
| b1f3644092 | |||
| 302423d98f | |||
| 9743b010d1 | |||
| c6e7fa6069 | |||
| c0ae014eef | |||
| edfd57d5d5 | |||
| cf916eb508 | |||
| 4000e0cfac | |||
| f851c6fef0 | |||
| 2684c12e73 | |||
| 75ef50bed1 | |||
| 9081e301d4 | |||
| 8b3f22909e | |||
| 7365f95d20 | |||
| d4c7e59cd6 | |||
| cf7b9ace08 | |||
| a1263e7056 |
Generated
+176
-910
File diff suppressed because it is too large
Load Diff
+20
-3
@@ -3,15 +3,32 @@ name = "kira-installer"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
# Reference the build script
|
||||||
|
# build = "build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
blocking = "1.6"
|
blocking = "1.6"
|
||||||
iced = { version = "0.14", features = ["smol", "image"] }
|
iced = { version = "0.14", default-features = false, features = ["crisp", "linux-theme-detection", "tiny-skia", "x11", "smol", "image"] }
|
||||||
iced_moving_picture = "0"
|
iced_moving_picture = "0"
|
||||||
|
log = "0.4"
|
||||||
rust-i18n = "3"
|
rust-i18n = "3"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
time-format = "1.2"
|
||||||
toml = "1"
|
toml = "1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rand = "0.10"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 2
|
||||||
|
codegen-units = 16
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
#lto = true
|
lto = true
|
||||||
codegen-units = 8
|
debug = false
|
||||||
|
incremental = false
|
||||||
|
codegen-units = 1
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let profile = env::var("PROFILE").unwrap();
|
||||||
|
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||||
|
let target_dir = PathBuf::from(&manifest_dir).join("target").join(&profile);
|
||||||
|
|
||||||
|
// Ensure target directory exists
|
||||||
|
if !target_dir.exists() {
|
||||||
|
fs::create_dir_all(&target_dir).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the file (e.g., config.json) to the target directory
|
||||||
|
let source = PathBuf::from(&manifest_dir).join("src").join("kira_config.toml");
|
||||||
|
let dest = target_dir.join("kira_config.toml");
|
||||||
|
|
||||||
|
fs::copy(&source, &dest).expect("Failed to copy kira_config.toml");
|
||||||
|
|
||||||
|
// Optional: Tell Cargo to re-run this script if the source file changes
|
||||||
|
println!("cargo:rerun-if-changed=src/kira_config.toml");
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
use iced::widget::{self, Row, container, text};
|
||||||
|
use iced::{Alignment, Color, Element, Length};
|
||||||
|
|
||||||
|
// Assuming your Item struct or type implements Display or has a text representation
|
||||||
|
pub fn color_bar<Message: 'static>(
|
||||||
|
maybe_items: Option<Vec<(String, u16, Color)>>,
|
||||||
|
bar_height: u32,
|
||||||
|
) -> Element<'static, Message> {
|
||||||
|
|
||||||
|
if let Some(items) = maybe_items {
|
||||||
|
let mut c_b = Row::new().height(Length::Fill);
|
||||||
|
for (bar_text, bar_fill, bar_color) in items {
|
||||||
|
c_b = c_b.push(
|
||||||
|
container(text(bar_text).color(Color::BLACK).size(iced::Pixels(14.0)))
|
||||||
|
.height(Length::Fill)
|
||||||
|
.width(Length::FillPortion(bar_fill))
|
||||||
|
.style(move |_| container::background(bar_color))
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.align_y(Alignment::Center)
|
||||||
|
.padding(2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
container(c_b.spacing(2))
|
||||||
|
.style(container::rounded_box)
|
||||||
|
.padding(3)
|
||||||
|
.height(bar_height)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
widget::container(widget::space())
|
||||||
|
.height(bar_height)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
|
||||||
[network]
|
[network]
|
||||||
reqire_network = true
|
reqire_network = true
|
||||||
|
|
||||||
|
[partition]
|
||||||
|
min_disk_size_mb = 8192
|
||||||
|
|||||||
@@ -0,0 +1,680 @@
|
|||||||
|
// <Kira Installer - universal Linux installer.>
|
||||||
|
// Copyright (C) <2026> <Kira Foundation>
|
||||||
|
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Functions for working with disk layout
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::kira_size::KiraSize;
|
||||||
|
use log;
|
||||||
|
use std::{collections::HashMap, io::Read, process::ExitStatus};
|
||||||
|
///
|
||||||
|
/// size - device size in bytes
|
||||||
|
/// sector size - default 4096
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct BlkDev {
|
||||||
|
pub name: String,
|
||||||
|
pub size: KiraSize,
|
||||||
|
pub sector_size: KiraSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for BlkDev {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} ({})", &self.name, self.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlkDev {
|
||||||
|
/// Create new BlkDev struct with specifyed name and size
|
||||||
|
pub fn new(name: String, size: KiraSize) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name,
|
||||||
|
size: size,
|
||||||
|
sector_size: KiraSize::new_b(4096),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new BlkDev struct with name and size parsed from hashmap
|
||||||
|
/// that contains String key val pairs with "NAME" and "SIZE" keys
|
||||||
|
pub fn from_hash_map(data: &HashMap<String, String>) -> Option<Self> {
|
||||||
|
Some(Self {
|
||||||
|
name: data.get("NAME")?.clone(),
|
||||||
|
size: KiraSize::from_str_bytes(data.get("SIZE")?)?,
|
||||||
|
sector_size: KiraSize::new_b(4096),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// returns dev name in a "/dev/name" form
|
||||||
|
pub fn full_name(&self) -> String {
|
||||||
|
format!("/dev/{}", self.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// this is blocking function
|
||||||
|
/// Lists block devices in system thru "lsbk" command
|
||||||
|
pub fn list_sys_blk_dev() -> Result<Vec<Self>, String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// lsblk -d -b -P -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT
|
||||||
|
match Command::new("lsblk")
|
||||||
|
.args(["-d", "-b", "-P", "-o", "NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT"])
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(cmd_output) => {
|
||||||
|
if cmd_output.status.success() {
|
||||||
|
match String::from_utf8(cmd_output.stdout) {
|
||||||
|
Ok(raw_str_list) => Ok(raw_str_list
|
||||||
|
.lines()
|
||||||
|
.map(|l| {
|
||||||
|
l.split_whitespace()
|
||||||
|
.filter_map(|pair| {
|
||||||
|
pair.split_once("=").and_then(|kv| {
|
||||||
|
Some((
|
||||||
|
kv.0.trim_matches('"').to_string(),
|
||||||
|
kv.1.trim_matches('"').to_string(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<HashMap<String, String>>()
|
||||||
|
})
|
||||||
|
.filter(|mp| mp.get("MOUNTPOINT").is_some_and(|v| v.is_empty()))
|
||||||
|
.filter_map(|mp| Self::from_hash_map(&mp))
|
||||||
|
.collect()),
|
||||||
|
Err(ex) => Err(format!(
|
||||||
|
"Exception while converting disks info to UTF8 {}",
|
||||||
|
ex
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(format!(
|
||||||
|
"Error getting disk devices list: {}",
|
||||||
|
String::from_utf8(cmd_output.stderr).unwrap_or("UNKNOWN".into())
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ex) => Err(ex.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum FSType {
|
||||||
|
VFAT,
|
||||||
|
XFS,
|
||||||
|
LUKS,
|
||||||
|
SWAP,
|
||||||
|
EXT4,
|
||||||
|
BTRFS,
|
||||||
|
BCACHEFS,
|
||||||
|
UNKNOWN,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for FSType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::VFAT => write!(f, "vfat"),
|
||||||
|
Self::XFS => write!(f, "xfs"),
|
||||||
|
Self::LUKS => write!(f, "luks"),
|
||||||
|
Self::SWAP => write!(f, "swap"),
|
||||||
|
Self::EXT4 => write!(f, "ext4"),
|
||||||
|
Self::BTRFS => write!(f, "btrfs"),
|
||||||
|
Self::BCACHEFS => write!(f, "bcachefs"),
|
||||||
|
Self::UNKNOWN => write!(f, "unknown"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FSType {
|
||||||
|
pub fn from_str(s: &str) -> Self {
|
||||||
|
let s_low = s.to_lowercase();
|
||||||
|
match s_low.as_str() {
|
||||||
|
"vfat" => Self::VFAT,
|
||||||
|
"xfs" => Self::XFS,
|
||||||
|
"crypto_luks" => Self::LUKS,
|
||||||
|
"swap" => Self::SWAP,
|
||||||
|
"ext4" => Self::EXT4,
|
||||||
|
"btrfs" => Self::BTRFS,
|
||||||
|
"bcachefs" => Self::BCACHEFS,
|
||||||
|
_ => Self::UNKNOWN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return fs type that can be used with parted utility
|
||||||
|
pub fn to_parted_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::VFAT => "fat32",
|
||||||
|
Self::XFS => "xfs",
|
||||||
|
Self::LUKS => "luks",
|
||||||
|
Self::SWAP => "linux-swap",
|
||||||
|
Self::EXT4 => "ext4",
|
||||||
|
Self::BTRFS => "btrfs",
|
||||||
|
// workaroud as parted do not support bcachefs partition type
|
||||||
|
Self::BCACHEFS => "bcachefs",
|
||||||
|
// lets do something generic here instead of panic
|
||||||
|
Self::UNKNOWN => "linux-swap",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn part_type_to_color(t: &str) -> Color {
|
||||||
|
// match t {
|
||||||
|
// "vfat" => Color::from_rgb8(204, 121, 167),
|
||||||
|
// "xfs" => Color::from_rgb8(0, 158, 115),
|
||||||
|
// "crypto_LUKS" => Color::from_rgb8(240, 228, 66),
|
||||||
|
// "swap" => Color::from_rgb8(213, 94, 0),
|
||||||
|
// "ext4" => Color::from_rgb8(0, 114, 178),
|
||||||
|
// "btrfs" => Color::from_rgb8(86, 180, 233),
|
||||||
|
// _ => Color::from_rgb8(230, 159, 0),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum PartRole {
|
||||||
|
EFI,
|
||||||
|
ROOT,
|
||||||
|
HOME,
|
||||||
|
SWAP,
|
||||||
|
LuksRoot,
|
||||||
|
LuksHome,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct PartInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub size: KiraSize,
|
||||||
|
pub start: KiraSize,
|
||||||
|
pub fs_type: FSType,
|
||||||
|
pub gpt_label: Option<String>,
|
||||||
|
pub fs_label: Option<String>,
|
||||||
|
pub mount_point: Option<String>,
|
||||||
|
pub role: Option<PartRole>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for PartInfo {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} {} {}", &self.name, &self.fs_type, self.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartInfo {
|
||||||
|
pub fn from_hash_map(data: &HashMap<String, String>) -> Option<Self> {
|
||||||
|
Some(Self {
|
||||||
|
name: data.get("NAME")?.clone(),
|
||||||
|
size: KiraSize::from_str_bytes(data.get("SIZE")?)?,
|
||||||
|
start: KiraSize::new_b(0),
|
||||||
|
fs_type: FSType::from_str(data.get("FSTYPE")?),
|
||||||
|
gpt_label: data
|
||||||
|
.get("PARTLABEL")
|
||||||
|
.and_then(|v| if v.is_empty() { None } else { Some(v.clone()) }),
|
||||||
|
fs_label: data
|
||||||
|
.get("LABEL")
|
||||||
|
.and_then(|v| if v.is_empty() { None } else { Some(v.clone()) }),
|
||||||
|
mount_point: data
|
||||||
|
.get("MOUNTPOINT")
|
||||||
|
.and_then(|v| if v.is_empty() { None } else { Some(v.clone()) }),
|
||||||
|
role: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// getting list of device partitions
|
||||||
|
fn get_dev_part_blocking(dev_name: &str) -> Result<Vec<PartInfo>, String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
//"lsblk -o NAME,SIZE,FSTYPE -l -n /dev/nvme0n1"
|
||||||
|
// lsblk -o NAME,SIZE,FSTYPE,TYPE,PARTLABEL,LABEL,PARTFLAGS -b -P
|
||||||
|
|
||||||
|
match Command::new("lsblk")
|
||||||
|
.args([
|
||||||
|
"-o",
|
||||||
|
"NAME,SIZE,FSTYPE,TYPE,PARTLABEL,LABEL,MOUNTPOINT,PARTFLAGS",
|
||||||
|
"-b",
|
||||||
|
"-P",
|
||||||
|
format!("/dev/{}", dev_name).as_str(),
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(cmd_output) => {
|
||||||
|
if cmd_output.status.success() {
|
||||||
|
match String::from_utf8(cmd_output.stdout) {
|
||||||
|
Ok(raw_str_list) => Ok(raw_str_list
|
||||||
|
.lines()
|
||||||
|
.map(|l| {
|
||||||
|
l.split_whitespace()
|
||||||
|
.filter_map(|pair| {
|
||||||
|
pair.split_once("=").and_then(|kv| {
|
||||||
|
Some((
|
||||||
|
kv.0.trim_matches('"').to_string(),
|
||||||
|
kv.1.trim_matches('"').to_string(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<HashMap<String, String>>()
|
||||||
|
})
|
||||||
|
.filter(|mp| mp.get("TYPE").is_some_and(|v| v == "part"))
|
||||||
|
.filter_map(|mp| PartInfo::from_hash_map(&mp))
|
||||||
|
.collect()),
|
||||||
|
Err(ex) => Err(format!(
|
||||||
|
"Exception while converting partition info to UTF8 {}",
|
||||||
|
ex
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(format!(
|
||||||
|
"Error getting device partitions list: {}",
|
||||||
|
String::from_utf8(cmd_output.stderr).unwrap_or("UNKNOWN".into())
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ex) => Err(ex.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// size - size in bytes
|
||||||
|
/// start - start byte of partition, inclusive index.
|
||||||
|
/// returns tuple of aligned size, start index, and end index
|
||||||
|
/// return none if aligment is not possibe (size of partition is less than align)
|
||||||
|
pub fn align_part(size: u64, start: u64, align: u64) -> Option<(u64, u64, u64)> {
|
||||||
|
if size < align {
|
||||||
|
log::debug!("if size < align !!!!!!!");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_mod = start % align;
|
||||||
|
// increasing start if not align
|
||||||
|
let align_start = if start_mod > 0 {
|
||||||
|
start + (align - start_mod)
|
||||||
|
} else {
|
||||||
|
start
|
||||||
|
};
|
||||||
|
|
||||||
|
// decreasing end if not align
|
||||||
|
let part_end = align_start + size;
|
||||||
|
let end_mod = part_end % align;
|
||||||
|
log::debug!("part_end: {} end_mod: {}", part_end, end_mod);
|
||||||
|
let align_end = part_end - end_mod - 1;
|
||||||
|
|
||||||
|
let align_size = align_end - align_start + 1;
|
||||||
|
|
||||||
|
if align_size < align {
|
||||||
|
log::warn!(
|
||||||
|
"!!! align_size < align !!! align_size: {} align: {}",
|
||||||
|
align_size,
|
||||||
|
align
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((align_size, align_start, align_end))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PartLayout {
|
||||||
|
pub dev: BlkDev,
|
||||||
|
pub empty_space: KiraSize,
|
||||||
|
pub part_list: Vec<PartInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartLayout {
|
||||||
|
pub fn new(dev: BlkDev) -> Self {
|
||||||
|
Self {
|
||||||
|
empty_space: dev.size,
|
||||||
|
dev: dev,
|
||||||
|
part_list: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_from_disk(dev: &BlkDev) -> Result<Self, String> {
|
||||||
|
let part_list = get_dev_part_blocking(&dev.name)?;
|
||||||
|
let parts_size = part_list
|
||||||
|
.iter()
|
||||||
|
.fold(KiraSize::new_b(0), |acc, part| acc + part.size);
|
||||||
|
Ok(Self {
|
||||||
|
dev: dev.clone(),
|
||||||
|
empty_space: dev.size - parts_size,
|
||||||
|
part_list: part_list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_table(mut self) -> Self {
|
||||||
|
self.empty_space = self.dev.size;
|
||||||
|
self.part_list = Vec::new();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_part(
|
||||||
|
mut self,
|
||||||
|
part_size: KiraSize,
|
||||||
|
fs_type: FSType,
|
||||||
|
gpt_label: Option<String>,
|
||||||
|
fs_label: Option<String>,
|
||||||
|
mount_point: Option<String>,
|
||||||
|
role: Option<PartRole>,
|
||||||
|
) -> Self {
|
||||||
|
let part_start = if self.part_list.is_empty() {
|
||||||
|
KiraSize::new_mb(2)
|
||||||
|
} else {
|
||||||
|
self.dev.size - self.empty_space
|
||||||
|
};
|
||||||
|
|
||||||
|
let (size, start, _) =
|
||||||
|
align_part(part_size.b(), part_start.b(), self.dev.sector_size.b()).unwrap();
|
||||||
|
|
||||||
|
let part_name = format!("{}p{}", self.dev.name, self.part_list.len() + 1);
|
||||||
|
self.part_list.push(PartInfo {
|
||||||
|
name: part_name,
|
||||||
|
size: KiraSize::new_b(size),
|
||||||
|
start: KiraSize::new_b(start),
|
||||||
|
fs_type: fs_type,
|
||||||
|
gpt_label: gpt_label,
|
||||||
|
fs_label: fs_label,
|
||||||
|
mount_point: mount_point,
|
||||||
|
role: role,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.empty_space = self.empty_space - KiraSize::new_b(size);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn add_part_reserve(
|
||||||
|
self,
|
||||||
|
reserve_space: KiraSize,
|
||||||
|
fs_type: FSType,
|
||||||
|
gpt_label: Option<String>,
|
||||||
|
fs_label: Option<String>,
|
||||||
|
mount_point: Option<String>,
|
||||||
|
role: Option<PartRole>,
|
||||||
|
) -> Self {
|
||||||
|
let p_size = self.empty_space - reserve_space;
|
||||||
|
self.add_part(p_size, fs_type, gpt_label, fs_label, mount_point, role)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_classic_layout(dev: BlkDev, efi_size: KiraSize, swap_size: KiraSize) -> Self {
|
||||||
|
let mut res: PartLayout = PartLayout::new(dev);
|
||||||
|
|
||||||
|
// UEFI partition
|
||||||
|
res = res
|
||||||
|
.add_part(
|
||||||
|
efi_size,
|
||||||
|
FSType::VFAT,
|
||||||
|
Some("efi".into()),
|
||||||
|
Some("EFI".into()),
|
||||||
|
Some("/efi".into()),
|
||||||
|
Some(PartRole::EFI),
|
||||||
|
)
|
||||||
|
.add_part_reserve(
|
||||||
|
swap_size,
|
||||||
|
FSType::BTRFS,
|
||||||
|
Some("root".into()),
|
||||||
|
None,
|
||||||
|
Some("/".into()),
|
||||||
|
Some(PartRole::ROOT),
|
||||||
|
);
|
||||||
|
|
||||||
|
// swap
|
||||||
|
if swap_size.b() > 0 {
|
||||||
|
res = res.add_part(
|
||||||
|
swap_size,
|
||||||
|
FSType::SWAP,
|
||||||
|
Some("swap".into()),
|
||||||
|
None,
|
||||||
|
Some("SWAP".into()),
|
||||||
|
Some(PartRole::SWAP),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes parted command with given arguments
|
||||||
|
fn exec_parted(args: &Vec<&str>) -> Result<String, String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
match Command::new("parted").args(args).output() {
|
||||||
|
Err(ex) => Err(ex.to_string()),
|
||||||
|
Ok(out) => {
|
||||||
|
if out.status.success() {
|
||||||
|
String::from_utf8(out.stdout).map_err(|ex| ex.to_string())
|
||||||
|
} else {
|
||||||
|
Err(out.status.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parted_get_last_part_num(dev: &BlkDev) -> Result<u32, String> {
|
||||||
|
// parted -m /dev/loop0 print
|
||||||
|
match exec_parted(&vec!["-m", dev.full_name().as_str(), "print"]) {
|
||||||
|
Ok(res) => {
|
||||||
|
match res.lines().last().and_then(|last_line| {
|
||||||
|
last_line
|
||||||
|
.split_once(':')
|
||||||
|
.and_then(|(first_part, a)| u32::from_str_radix(first_part, 10).ok())
|
||||||
|
}) {
|
||||||
|
Some(n) => Ok(n),
|
||||||
|
None => Err("Error parsing parted output".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ex) => Err(ex),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// init gpt partition table
|
||||||
|
/// parted --script $install_device mklabel gpt
|
||||||
|
/// Its blocking function
|
||||||
|
pub fn parted_init_gpt_part_table(dev: &BlkDev) -> Result<(), String> {
|
||||||
|
exec_parted(&vec![
|
||||||
|
"--script",
|
||||||
|
dev.full_name().as_str(),
|
||||||
|
"mklabel",
|
||||||
|
"gpt",
|
||||||
|
])
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
const LINUX_ROOT_X86_64_TYPE: &str = "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709";
|
||||||
|
|
||||||
|
/// creating partition from PartInfo specification
|
||||||
|
/// It assumes that we add partitions sequentially to empty space (for part attributes settings)
|
||||||
|
/// Otherwise can produce bonkers layout
|
||||||
|
pub fn parted_mkpart(dev: &BlkDev, part: &PartInfo) -> Result<(), String> {
|
||||||
|
let part_end = part.start + part.size;
|
||||||
|
|
||||||
|
// gpt label should be in double qutes
|
||||||
|
let gpt_label = if let Some(s) = &part.gpt_label {
|
||||||
|
format!("\"{}\"", s)
|
||||||
|
} else {
|
||||||
|
"\"\"".to_string()
|
||||||
|
};
|
||||||
|
// creating partition
|
||||||
|
exec_parted(&vec![
|
||||||
|
"--script",
|
||||||
|
dev.full_name().as_str(),
|
||||||
|
"mkpart",
|
||||||
|
gpt_label.as_str(),
|
||||||
|
part.fs_type.to_parted_str(),
|
||||||
|
part.start.to_parted_bytes_str().as_str(),
|
||||||
|
part_end.to_parted_bytes_str().as_str(),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
// set partition options depending on its role
|
||||||
|
if let Some(role) = &part.role {
|
||||||
|
// getting part number of last created partition (hopefully)
|
||||||
|
let part_id = parted_get_last_part_num(dev)?;
|
||||||
|
match role {
|
||||||
|
PartRole::EFI => {
|
||||||
|
exec_parted(&vec![
|
||||||
|
"--script",
|
||||||
|
dev.full_name().as_str(),
|
||||||
|
"set",
|
||||||
|
&part_id.to_string(),
|
||||||
|
"esp",
|
||||||
|
"on",
|
||||||
|
])?;
|
||||||
|
}
|
||||||
|
// parted --script $install_device type 3 4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709
|
||||||
|
PartRole::ROOT => {
|
||||||
|
exec_parted(&vec![
|
||||||
|
"--script",
|
||||||
|
dev.full_name().as_str(),
|
||||||
|
"type",
|
||||||
|
&part_id.to_string(),
|
||||||
|
LINUX_ROOT_X86_64_TYPE,
|
||||||
|
])?;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// creating single drive bcachefs volume
|
||||||
|
pub fn bcachefs_format(
|
||||||
|
dev: &BlkDev,
|
||||||
|
compression: Option<String>,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
// sudo bcachefs format --compression=lz4 --background_compression=lz4 --encrypted /dev/loop0
|
||||||
|
// --data_checksum=none - do not make sense on single dev volumes
|
||||||
|
// sudo bcachefs format --force --compression=lz4 --background_compression=lz4 --encrypted --passphrase_file=./test.pass /dev/loop0
|
||||||
|
use std::fs;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let mut arg = vec!["format", "--force", "--data_checksum=none"];
|
||||||
|
// if compression algorythm specified
|
||||||
|
if let Some(compresss_alg) = &compression {
|
||||||
|
arg.push(compresss_alg.as_str());
|
||||||
|
}
|
||||||
|
// if we want encrypted volume
|
||||||
|
if let Some(passw) = password {
|
||||||
|
// creating tmp file with password
|
||||||
|
fs::write("/tmp/bzzpsspass.txt", &passw).map_err(|ex| ex.to_string())?;
|
||||||
|
arg.push("--encrypted");
|
||||||
|
arg.push("--passphrase_file=/tmp/bzzpsspass.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
// add dev name at the end
|
||||||
|
let dev_name = dev.full_name();
|
||||||
|
arg.push(dev_name.as_str());
|
||||||
|
|
||||||
|
let out = Command::new("bcachefs")
|
||||||
|
.args(arg)
|
||||||
|
.output()
|
||||||
|
.map_err(|ex| ex.to_string())?;
|
||||||
|
|
||||||
|
// ignore any errors while deleting pass tmp file
|
||||||
|
let _ = fs::remove_file("/tmp/bzzpsspass.txt");
|
||||||
|
|
||||||
|
if out.status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(String::from_utf8(out.stderr).map_err(|ex| ex.to_string())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// unlok encrypted bcache volume
|
||||||
|
pub fn bcachefs_unlock(dev: &BlkDev, pass: String) -> Result<(), String> {
|
||||||
|
use std::fs;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// bcachefs unlock --file=/tmp/bzzpsspass.txt /dev/loop0
|
||||||
|
let dev_name = dev.full_name();
|
||||||
|
let arg = vec!["unlock", "--file=/tmp/bzzpsspass.txt ", dev_name.as_str()];
|
||||||
|
// creating tmp file with password
|
||||||
|
fs::write("/tmp/bzzpsspass.txt", &pass).map_err(|ex| ex.to_string())?;
|
||||||
|
|
||||||
|
let out = Command::new("bcachefs")
|
||||||
|
.args(arg)
|
||||||
|
.output()
|
||||||
|
.map_err(|ex| ex.to_string())?;
|
||||||
|
// ignore any errors while deleting pass tmp file
|
||||||
|
let _ = fs::remove_file("/tmp/bzzpsspass.txt");
|
||||||
|
|
||||||
|
if out.status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(String::from_utf8(out.stderr).map_err(|ex| ex.to_string())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// create subvolume
|
||||||
|
pub fn bcachefs_create_subvolume(vol_path: &str) -> Result<(), String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// sudo bcachefs subvolume create /mnt/test/nyan
|
||||||
|
let out = Command::new("bcachefs")
|
||||||
|
.args(["subvolume", "create", vol_path])
|
||||||
|
.output()
|
||||||
|
.map_err(|ex| ex.to_string())?;
|
||||||
|
if out.status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(String::from_utf8(out.stderr).map_err(|ex| ex.to_string())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use rand::RngExt;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_align_part_fn_joe() {
|
||||||
|
//START (S),SIZE (Z),ALIGNMENT (A),ALIGNED_START (Salign),ALIGNED_END (Ealign),Effektive Größe (Zalign)
|
||||||
|
let joes_test_data: Vec<(u64, u64, u64, u64, u64, u64)> = vec![
|
||||||
|
(0, 10000, 4096, 0, 8191, 8192),
|
||||||
|
(5000, 20000, 4096, 8192, 24575, 16384),
|
||||||
|
(4096, 4096, 4096, 4096, 8191, 4096),
|
||||||
|
(1000000, 5000000, 1048576, 1048576, 5242879, 4194304),
|
||||||
|
(2097152, 1050000, 1048576, 2097152, 3145727, 1048576),
|
||||||
|
];
|
||||||
|
for test_data in joes_test_data {
|
||||||
|
let res = align_part(test_data.1, test_data.0, test_data.2).unwrap();
|
||||||
|
let test_res = (test_data.5, test_data.3, test_data.4);
|
||||||
|
assert_eq!(res, test_res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_align_part_fn_rand() {
|
||||||
|
for ((size, start, align), test_res) in rand::rng()
|
||||||
|
.random_iter::<u32>()
|
||||||
|
.filter(|n| *n > 0)
|
||||||
|
.take(100)
|
||||||
|
.flat_map(|align| {
|
||||||
|
(0..u64::MAX)
|
||||||
|
.step_by(align as usize)
|
||||||
|
.take(1000)
|
||||||
|
.map(move |start_align| {
|
||||||
|
let mut rng = rand::rng();
|
||||||
|
let size_align = align as u64 * rng.random_range(1..u16::MAX) as u64;
|
||||||
|
let end_align = start_align + size_align - 1;
|
||||||
|
let start = start_align.saturating_sub(rng.random_range(0..align) as u64);
|
||||||
|
let size = size_align.saturating_add(rng.random_range(0..align) as u64);
|
||||||
|
(
|
||||||
|
(size, start, align as u64),
|
||||||
|
(size_align, start_align.clone(), end_align),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
{
|
||||||
|
let res = align_part(size, start, align).unwrap();
|
||||||
|
assert_eq!(res, test_res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
use log::{Level, Metadata, Record, SetLoggerError, LevelFilter};
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
use time_format;
|
||||||
|
|
||||||
|
struct KiraLogger {
|
||||||
|
file: Option<Mutex<File>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KiraLogger {
|
||||||
|
pub fn open(file_name: Option<&str>) -> std::io::Result<Self> {
|
||||||
|
match file_name {
|
||||||
|
Some(f_n) => Ok(Self {
|
||||||
|
file: Some(Mutex::new(
|
||||||
|
OpenOptions::new().append(true).create(true).open(f_n)?,
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
None => Ok(Self {
|
||||||
|
file: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl log::Log for KiraLogger {
|
||||||
|
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||||
|
(metadata.target().starts_with("kira_installer") && metadata.level() <= Level::Debug) || (metadata.level() <= Level::Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&self, record: &Record) {
|
||||||
|
if self.enabled(record.metadata()) {
|
||||||
|
let ts = time_format::now().unwrap();
|
||||||
|
let local_time = time_format::strftime_local("%Y-%m-%dT%H:%M:%S", ts).unwrap();
|
||||||
|
if let Some(m_file) = &self.file {
|
||||||
|
|
||||||
|
let mut write_lock = m_file.lock().unwrap();
|
||||||
|
let _ = writeln!(&mut *write_lock, "{} {}: {}", local_time, record.level(), record.args());
|
||||||
|
}
|
||||||
|
println!("{} {} {}: {}", local_time,record.metadata().target(), record.level(), record.args());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {
|
||||||
|
if let Some(m_file) = &self.file {
|
||||||
|
let mut write_lock = m_file.lock().unwrap();
|
||||||
|
let _ = (&mut *write_lock).flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static LOGGER: LazyLock<KiraLogger> = LazyLock::new(|| {
|
||||||
|
let ts = time_format::now().unwrap();
|
||||||
|
// Local time with timezone name
|
||||||
|
let local_time = time_format::strftime_local("%Y-%m-%d_%H-%M-%S", ts).unwrap();
|
||||||
|
KiraLogger::open(Some(format!("installation_{}.log", local_time).as_str())).unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn init() -> Result<(), SetLoggerError> {
|
||||||
|
log::set_logger(&*LOGGER)
|
||||||
|
.map(|()| log::set_max_level(LevelFilter::Debug))
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
// <Kira Installer - universal Linux installer.>
|
||||||
|
// Copyright (C) <2026> <Kira Foundation>
|
||||||
|
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Scroll List iced widget implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use iced::widget::{Column, button, container, scrollable, text};
|
||||||
|
use iced::{Alignment, Color, Element, Length, Theme};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ListViewMessage {
|
||||||
|
Select(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unselected_button_style(theme: &Theme, status: button::Status) -> button::Style {
|
||||||
|
use iced::{Border, border::Radius};
|
||||||
|
|
||||||
|
let palette = theme.palette();
|
||||||
|
|
||||||
|
let mut res_stile = button::Style {
|
||||||
|
background: Some(iced::Background::Color(Color::TRANSPARENT)),
|
||||||
|
text_color: palette.text,
|
||||||
|
..button::primary(theme, button::Status::Active)
|
||||||
|
};
|
||||||
|
|
||||||
|
match status {
|
||||||
|
button::Status::Hovered => {
|
||||||
|
res_stile.border = Border {
|
||||||
|
color: Color::from_rgb(0.8, 0.3, 0.3),
|
||||||
|
width: 1.0,
|
||||||
|
radius: Radius::new(5.0),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
return res_stile;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_button_style(theme: &Theme, status: button::Status) -> button::Style {
|
||||||
|
use iced::{Border, border::Radius};
|
||||||
|
|
||||||
|
//let palette = theme.palette();
|
||||||
|
|
||||||
|
let mut res_stile = button::primary(theme, button::Status::Active);
|
||||||
|
|
||||||
|
|
||||||
|
match status {
|
||||||
|
button::Status::Hovered => {
|
||||||
|
res_stile.border = Border {
|
||||||
|
color: Color::from_rgb(0.8, 0.3, 0.3),
|
||||||
|
width: 1.0,
|
||||||
|
radius: Radius::new(5.0),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
return res_stile;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Assuming your Item struct or type implements Display or has a text representation
|
||||||
|
pub fn list_view<T: Display>(
|
||||||
|
items: &Vec<T>,
|
||||||
|
selected_id: Option<usize>,
|
||||||
|
container_height: Length,
|
||||||
|
) -> Element<'_, ListViewMessage> {
|
||||||
|
let mut column = Column::new()
|
||||||
|
.spacing(2)
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.width(Length::Fill);
|
||||||
|
|
||||||
|
for (index, value) in items.iter().enumerate() {
|
||||||
|
// Create a row for each item, possibly with buttons or other controls
|
||||||
|
let list_item = if let Some(id) = selected_id
|
||||||
|
&& id == index
|
||||||
|
{
|
||||||
|
button(text(value.to_string())).style(selected_button_style)
|
||||||
|
} else {
|
||||||
|
button(text(value.to_string())).style(unselected_button_style)
|
||||||
|
};
|
||||||
|
let list_item = list_item
|
||||||
|
.width(Length::Fill)
|
||||||
|
.on_press(ListViewMessage::Select(index));
|
||||||
|
|
||||||
|
column = column.push(list_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the column in a scrollable widget
|
||||||
|
container(scrollable(column).width(Length::Fill).spacing(5))
|
||||||
|
.style(container::bordered_box)
|
||||||
|
.height(container_height)
|
||||||
|
.padding(5)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
// <Kira Installer - universal Linux installer.>
|
||||||
|
// Copyright (C) <2026> <Kira Foundation>
|
||||||
|
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/*
|
||||||
|
KiraSize struct implementation for work with base2 sizes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::ops::{Add, Sub};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct KiraSize {
|
||||||
|
size_bytes: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for KiraSize {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if self.size_bytes < 1000 {
|
||||||
|
write!(f, "{}B", self.size_bytes)
|
||||||
|
} else {
|
||||||
|
let mut s = self.size_bytes as f64;
|
||||||
|
let mut idx = 0;
|
||||||
|
for i in 0..5 {
|
||||||
|
s = s / 1024.0;
|
||||||
|
idx = i;
|
||||||
|
if s < 1000.0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "{:.2}{}", s, Self::SUFFIXS[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KiraSize {
|
||||||
|
pub const SUFFIXS: [&str; 5] = ["KB", "MB", "GB", "TB", "PB"];
|
||||||
|
|
||||||
|
/// returns size in bytes suffixed with "B" character
|
||||||
|
pub fn to_parted_bytes_str(&self)-> String {
|
||||||
|
format!("{}B", self.size_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn new_b(size:u64) -> Self {
|
||||||
|
Self { size_bytes: size }
|
||||||
|
}
|
||||||
|
pub fn new_kb(size:u64) -> Self {
|
||||||
|
Self { size_bytes: size * 1024 }
|
||||||
|
}
|
||||||
|
pub fn new_mb(size:u64) -> Self {
|
||||||
|
Self { size_bytes: size * 1024u64.pow(2) }
|
||||||
|
}
|
||||||
|
pub fn new_gb(size:u64) -> Self {
|
||||||
|
Self { size_bytes: size * 1024u64.pow(3) }
|
||||||
|
}
|
||||||
|
pub fn new_tb(size:u64) -> Self {
|
||||||
|
Self { size_bytes: size * 1024u64.pow(4) }
|
||||||
|
}
|
||||||
|
pub fn new_pb(size:u64) -> Self {
|
||||||
|
Self { size_bytes: size * 1024u64.pow(5) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn b(&self) -> u64 {
|
||||||
|
self.size_bytes
|
||||||
|
}
|
||||||
|
pub fn kb(&self) -> u64 {
|
||||||
|
self.size_bytes / 1024u64
|
||||||
|
}
|
||||||
|
pub fn mb(&self) -> u64 {
|
||||||
|
self.size_bytes / 1024u64.pow(2)
|
||||||
|
}
|
||||||
|
pub fn gb(&self) -> u64 {
|
||||||
|
self.size_bytes / 1024u64.pow(3)
|
||||||
|
}
|
||||||
|
pub fn tb(&self) -> u64 {
|
||||||
|
self.size_bytes / 1024u64.pow(4)
|
||||||
|
}
|
||||||
|
pub fn pb(&self) -> u64 {
|
||||||
|
self.size_bytes / 1024u64.pow(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_str_bytes(s: &str) -> Option<Self> {
|
||||||
|
Some(Self::new_b(u64::from_str_radix(s, 10).ok()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn div_c(self, other: u64) -> Self {
|
||||||
|
Self { size_bytes: self.size_bytes / other }
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Panics if overflow accurs.
|
||||||
|
///
|
||||||
|
pub fn mul_c(self, other: u64) -> Self {
|
||||||
|
Self { size_bytes: self.size_bytes.strict_mul(other) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for KiraSize {
|
||||||
|
type Output = KiraSize;
|
||||||
|
|
||||||
|
fn add(self, other: KiraSize) -> Self::Output {
|
||||||
|
KiraSize {
|
||||||
|
size_bytes: self.size_bytes + other.size_bytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for KiraSize {
|
||||||
|
type Output = KiraSize;
|
||||||
|
|
||||||
|
/// Panics if overflow accors.
|
||||||
|
fn sub(self, other: KiraSize) -> Self::Output {
|
||||||
|
KiraSize {
|
||||||
|
size_bytes: self.size_bytes.strict_sub(other.size_bytes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
// <Kira Installer - universal Linux installer.>
|
||||||
|
// Copyright (C) <2026> <Kira Foundation> <Kira>
|
||||||
|
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/*
|
||||||
|
This file contains functions to query system information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// vis_vram_total - size of vram in bytes
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct GpuInfo {
|
||||||
|
pub id: u8,
|
||||||
|
pub name: String,
|
||||||
|
pub vis_vram_total: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See /proc/meminfo output format and units (kB)
|
||||||
|
pub fn get_meminfo() -> HashMap<String, u64> {
|
||||||
|
let mem_info_str = fs::read_to_string("/proc/meminfo").unwrap();
|
||||||
|
|
||||||
|
mem_info_str
|
||||||
|
.lines()
|
||||||
|
.filter_map(|l| {
|
||||||
|
l.split_once(':').and_then(|(k, v)| {
|
||||||
|
u64::from_str_radix(v.trim_end_matches("kB").trim(), 10)
|
||||||
|
.ok()
|
||||||
|
.and_then(|v_num| Some((k.to_string(), v_num)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// cat /sys/class/drm/card1/device/mem_info_vis_vram_total
|
||||||
|
pub fn get_gpus_list() -> Vec<GpuInfo> {
|
||||||
|
let drm_devs_dirs: Vec<(String, u8)> = fs::read_dir("/sys/class/drm/")
|
||||||
|
.unwrap()
|
||||||
|
.filter_map(|maybe_entry| {
|
||||||
|
maybe_entry.ok().and_then(|entry| {
|
||||||
|
entry.file_type().ok().and_then(|ft| {
|
||||||
|
if ft.is_symlink() || ft.is_dir() {
|
||||||
|
entry.file_name().into_string().ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.filter(|f_name| f_name.len() == 5 && f_name.starts_with("card"))
|
||||||
|
.filter_map(|dir_name| {
|
||||||
|
dir_name.split_once("card").and_then(|name_touple| {
|
||||||
|
u8::from_str_radix(name_touple.1, 10)
|
||||||
|
.ok()
|
||||||
|
.and_then(|gpu_id| Some((dir_name.clone(), gpu_id)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let res: Vec<GpuInfo> = drm_devs_dirs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(d, id)| {
|
||||||
|
fs::read_to_string(format!(
|
||||||
|
"/sys/class/drm/{}/device/mem_info_vis_vram_total",
|
||||||
|
d
|
||||||
|
))
|
||||||
|
.ok()
|
||||||
|
.and_then(|mem_str| {
|
||||||
|
u64::from_str_radix(mem_str.trim(), 10)
|
||||||
|
.ok()
|
||||||
|
.and_then(|mem_size| {
|
||||||
|
Some(GpuInfo {
|
||||||
|
id: *id,
|
||||||
|
name: d.clone(),
|
||||||
|
vis_vram_total: mem_size,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct SecureBootStatus {
|
||||||
|
pub installed: bool,
|
||||||
|
pub guid: String,
|
||||||
|
pub setup_mode: bool,
|
||||||
|
pub secure_boot: bool,
|
||||||
|
pub vendors: Vec<String>,
|
||||||
|
pub firmware_quirks: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_secure_boot_status() -> Result<SecureBootStatus, String> {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
//sbctl --json status
|
||||||
|
match Command::new("sbctl").args(["--json", "status"]).output() {
|
||||||
|
Ok(cmd_output) => {
|
||||||
|
if cmd_output.status.success() {
|
||||||
|
match serde_json::from_slice(&cmd_output.stdout) {
|
||||||
|
Ok(res) => Ok(res),
|
||||||
|
Err(ex) => Err(ex.to_string()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(format!(
|
||||||
|
"Error getting disk devices list: {}",
|
||||||
|
String::from_utf8(cmd_output.stderr).unwrap_or("UNKNOWN".into())
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ex) => Err(ex.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
-4
@@ -4,11 +4,14 @@
|
|||||||
"button.next": "Next",
|
"button.next": "Next",
|
||||||
"button.back": "Back",
|
"button.back": "Back",
|
||||||
"button.cancel": "Cancel",
|
"button.cancel": "Cancel",
|
||||||
|
"button.finish": "Finish",
|
||||||
"button.exit": "Exit",
|
"button.exit": "Exit",
|
||||||
|
"button.yes": "Yes",
|
||||||
|
"button.no": "No",
|
||||||
"wellcome.text": "Welcome to Kira Installer!",
|
"wellcome.text": "Welcome to Kira Installer!",
|
||||||
"wellcome.choose_language": "Please select language to use during istalation!",
|
"wellcome.choose_language": "Please select language to use during istalation!",
|
||||||
"license.license": "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.\n\nThis 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.\n\nYou should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.",
|
"license.license": "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.\n\nThis 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.\n\nYou should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.",
|
||||||
"license.accept_text": "By pressing 'Accept' button you agreening to terms described above:",
|
"license.accept_text": "By pressing 'Accept' button you agreening to terms described below:",
|
||||||
"license.button.accept": "Accept",
|
"license.button.accept": "Accept",
|
||||||
"license.button.decline": "Decline",
|
"license.button.decline": "Decline",
|
||||||
"network.reuse_checkbox_caption": "Reuse settings?",
|
"network.reuse_checkbox_caption": "Reuse settings?",
|
||||||
@@ -21,7 +24,33 @@
|
|||||||
"timezone.selected_timezone": "Selected timezone:",
|
"timezone.selected_timezone": "Selected timezone:",
|
||||||
"timezone.select_timezone": "Please select preffered time zone",
|
"timezone.select_timezone": "Please select preffered time zone",
|
||||||
"timezone.init_error": "Error getting time zones data from system!",
|
"timezone.init_error": "Error getting time zones data from system!",
|
||||||
"locale.select_language_locale": "Language locale set to",
|
"locale.select_language_locale": "The system language will be set to",
|
||||||
"locale.select_formats_locale": "Formats locale set to"
|
"locale.select_formats_locale": "The numbers and dates locale will be set to",
|
||||||
|
"keyboard.select_model": "Select keyboard model",
|
||||||
|
"keyboard.select_layout": "Select keyboard layout",
|
||||||
|
"keyboard.select_variant": "Select keyboard variant",
|
||||||
|
"keyboard.init_error": "Error getting keyboard layouts information from the system!",
|
||||||
|
"partition.no_swap": "No swap partition",
|
||||||
|
"partition.swap_no_hibernate": "Swap without hibernate",
|
||||||
|
"partition.swap_hibernate": "Swap with hibernation",
|
||||||
|
"partition.swap_custom": "Custom swap size",
|
||||||
|
"partition.select_device_placeholder": "Select Device",
|
||||||
|
"partition.select_device": "Select device for system installation",
|
||||||
|
"partition.select_swapmode_placeholder": "Select Swap Mode",
|
||||||
|
"partition.select_swapmode": "Select swap partition mode",
|
||||||
|
"partition.use_zram": "Use zram technology",
|
||||||
|
"partition.use_secure_boot": "Setup secure boot",
|
||||||
|
"partition.encrypt_drive": "Encrypt drive",
|
||||||
|
"partition.current_dev_layout": "Current device layout",
|
||||||
|
"partition.current_after_install": "After installation device will look like this",
|
||||||
|
"security.user_name": "User login name",
|
||||||
|
"security.user_full_name": "User full name/description",
|
||||||
|
"security.user_password": "User password",
|
||||||
|
"security.reuse_user_password": "Reuse user password for root user",
|
||||||
|
"security.root_password": "Root password",
|
||||||
|
"security.pass_low": "low security",
|
||||||
|
"security.pass_middle": "normal security",
|
||||||
|
"security.pass_hight": "hight security",
|
||||||
|
"install.caption": "Now we will beee install Bzz Linux to your PC",
|
||||||
|
"install.warning": "We a ready to start installation, this action will destroy all data on select drive!\nDo you want to proceed?"
|
||||||
}
|
}
|
||||||
+115
-50
@@ -21,6 +21,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use iced::widget;
|
use iced::widget;
|
||||||
|
use log;
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
|
||||||
use iced::{Element, Task};
|
use iced::{Element, Task};
|
||||||
@@ -32,6 +33,12 @@ use crate::stages::welcome;
|
|||||||
use crate::stages::welcome::WelcomeStage;
|
use crate::stages::welcome::WelcomeStage;
|
||||||
|
|
||||||
rust_i18n::i18n!("src/locales", fallback = "en");
|
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 kira_theming;
|
||||||
mod stage;
|
mod stage;
|
||||||
mod stages;
|
mod stages;
|
||||||
@@ -43,6 +50,9 @@ enum Views {
|
|||||||
Network(stages::network::NetworkStage),
|
Network(stages::network::NetworkStage),
|
||||||
TimeZone(stages::timezone::TimeZoneStage),
|
TimeZone(stages::timezone::TimeZoneStage),
|
||||||
Locale(stages::locale::LocaleStage),
|
Locale(stages::locale::LocaleStage),
|
||||||
|
Keyboard(stages::keyboard::KeyboardStage),
|
||||||
|
Partition(stages::partition::PartitionStage),
|
||||||
|
Security(stages::security::SecurityStage),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Message {
|
enum Message {
|
||||||
@@ -52,12 +62,16 @@ enum Message {
|
|||||||
Network(stages::network::Message),
|
Network(stages::network::Message),
|
||||||
TimeZone(stages::timezone::Message),
|
TimeZone(stages::timezone::Message),
|
||||||
Locale(stages::locale::Message),
|
Locale(stages::locale::Message),
|
||||||
|
Keyboard(stages::keyboard::Message),
|
||||||
|
Partition(stages::partition::Message),
|
||||||
|
Security(stages::security::Message),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct KiraState {
|
struct KiraState {
|
||||||
current_view: Views,
|
current_view: Views,
|
||||||
toml_config: toml::Table,
|
toml_config: toml::Table,
|
||||||
config: KiraConfig,
|
config: KiraConfig,
|
||||||
|
stages_save: Vec<Views>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONFIG_PATH_STR: &str = "kira_config.toml";
|
const CONFIG_PATH_STR: &str = "kira_config.toml";
|
||||||
@@ -71,7 +85,7 @@ fn load_kira_config(config_path: &str) -> Result<toml::Table, Box<dyn std::error
|
|||||||
// 2. Parse the string into a Table
|
// 2. Parse the string into a Table
|
||||||
let table: Table = content.parse()?;
|
let table: Table = content.parse()?;
|
||||||
|
|
||||||
println!("{:?}", table);
|
log::info!("{:?}", table);
|
||||||
Ok(table)
|
Ok(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,24 +98,13 @@ impl KiraState {
|
|||||||
config: KiraConfig {
|
config: KiraConfig {
|
||||||
config_trail: Vec::new(),
|
config_trail: Vec::new(),
|
||||||
},
|
},
|
||||||
|
stages_save: Vec::new(),
|
||||||
},
|
},
|
||||||
Task::done(Message::Start),
|
Task::done(Message::Start),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl KiraState {
|
|
||||||
// fn new(toml_config: toml::Table) -> Self {
|
|
||||||
|
|
||||||
// Self {
|
|
||||||
// current_view: Views::Start,
|
|
||||||
// toml_config: toml_config,
|
|
||||||
// config: KiraConfig { config_trail: Vec::new() }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
fn view(k_state: &KiraState) -> Element<'_, Message> {
|
fn view(k_state: &KiraState) -> Element<'_, Message> {
|
||||||
match &k_state.current_view {
|
match &k_state.current_view {
|
||||||
Views::Start => Element::new(widget::space()),
|
Views::Start => Element::new(widget::space()),
|
||||||
@@ -110,6 +113,9 @@ fn view(k_state: &KiraState) -> Element<'_, Message> {
|
|||||||
Views::Network(network_stage) => network_stage.view().map(Message::Network),
|
Views::Network(network_stage) => network_stage.view().map(Message::Network),
|
||||||
Views::TimeZone(timezone_stage) => timezone_stage.view().map(Message::TimeZone),
|
Views::TimeZone(timezone_stage) => timezone_stage.view().map(Message::TimeZone),
|
||||||
Views::Locale(locale_stage) => locale_stage.view().map(Message::Locale),
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,13 +123,13 @@ fn update(k_state: &mut KiraState, message: Message) -> Task<Message> {
|
|||||||
match message {
|
match message {
|
||||||
Message::Start => match load_kira_config(CONFIG_PATH_STR) {
|
Message::Start => match load_kira_config(CONFIG_PATH_STR) {
|
||||||
Ok(conf) => {
|
Ok(conf) => {
|
||||||
println!("Config loaded!");
|
log::info!("Config loaded!");
|
||||||
k_state.toml_config = conf;
|
k_state.toml_config = conf;
|
||||||
k_state.current_view = Views::Welcome(WelcomeStage::new());
|
k_state.current_view = Views::Welcome(WelcomeStage::new());
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Err(ex) => {
|
Err(ex) => {
|
||||||
println!("Error reading config {}: {}", CONFIG_PATH_STR, ex);
|
log::error!("Error reading config {}: {}", CONFIG_PATH_STR, ex);
|
||||||
iced::exit()
|
iced::exit()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -132,7 +138,8 @@ fn update(k_state: &mut KiraState, message: Message) -> Task<Message> {
|
|||||||
match wlc_view.update(wlc_msg) {
|
match wlc_view.update(wlc_msg) {
|
||||||
StageAction::Next(welcome_res) => {
|
StageAction::Next(welcome_res) => {
|
||||||
k_state.config.config_trail.push(welcome_res);
|
k_state.config.config_trail.push(welcome_res);
|
||||||
k_state.current_view = Views::License(license::LicenseStage {});
|
let s_state = std::mem::replace(&mut k_state.current_view, Views::License(license::LicenseStage {}));
|
||||||
|
k_state.stages_save.push(s_state);
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
StageAction::Abort => iced::exit(),
|
StageAction::Abort => iced::exit(),
|
||||||
@@ -141,20 +148,20 @@ fn update(k_state: &mut KiraState, message: Message) -> Task<Message> {
|
|||||||
} else {
|
} else {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Message::License(license_message) => {
|
Message::License(license_message) => {
|
||||||
if let Views::License(license_view) = &mut k_state.current_view {
|
if let Views::License(license_view) = &mut k_state.current_view {
|
||||||
let action = license_view.update(license_message);
|
let action = license_view.update(license_message);
|
||||||
match action {
|
match action {
|
||||||
StageAction::Next(license_res) => {
|
StageAction::Next(license_res) => {
|
||||||
k_state.config.config_trail.push(license_res);
|
k_state.config.config_trail.push(license_res);
|
||||||
k_state.current_view =
|
let s_state = std::mem::replace(&mut k_state.current_view, Views::Network(network::NetworkStage::new(&k_state.toml_config)));
|
||||||
Views::Network(network::NetworkStage::new(&k_state.toml_config));
|
k_state.stages_save.push(s_state);
|
||||||
Task::done(Message::Network(network::Message::CheckNetwork))
|
Task::done(Message::Network(network::Message::CheckNetwork))
|
||||||
}
|
}
|
||||||
StageAction::Abort => iced::exit(),
|
StageAction::Abort => iced::exit(),
|
||||||
StageAction::Back => {
|
StageAction::Back => {
|
||||||
k_state.current_view = Views::Welcome(WelcomeStage::new());
|
k_state.current_view = k_state.stages_save.pop().unwrap();
|
||||||
k_state.config.config_trail.pop();
|
k_state.config.config_trail.pop();
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
@@ -163,7 +170,7 @@ fn update(k_state: &mut KiraState, message: Message) -> Task<Message> {
|
|||||||
} else {
|
} else {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Message::Network(network_message) => {
|
Message::Network(network_message) => {
|
||||||
if let Views::Network(network_view) = &mut k_state.current_view {
|
if let Views::Network(network_view) = &mut k_state.current_view {
|
||||||
let update_result = network_view.update(network_message);
|
let update_result = network_view.update(network_message);
|
||||||
@@ -172,12 +179,12 @@ fn update(k_state: &mut KiraState, message: Message) -> Task<Message> {
|
|||||||
network::UpdateResult::StageAction(action) => match action {
|
network::UpdateResult::StageAction(action) => match action {
|
||||||
StageAction::Next(network_res) => {
|
StageAction::Next(network_res) => {
|
||||||
k_state.config.config_trail.push(network_res);
|
k_state.config.config_trail.push(network_res);
|
||||||
k_state.current_view =
|
let s_state = std::mem::replace(&mut k_state.current_view,Views::TimeZone(stages::timezone::TimeZoneStage::new()));
|
||||||
Views::TimeZone(stages::timezone::TimeZoneStage::new());
|
k_state.stages_save.push(s_state);
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
StageAction::Back => {
|
StageAction::Back => {
|
||||||
k_state.current_view = Views::License(license::LicenseStage {});
|
k_state.current_view = k_state.stages_save.pop().unwrap();
|
||||||
k_state.config.config_trail.pop();
|
k_state.config.config_trail.pop();
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
@@ -187,40 +194,41 @@ fn update(k_state: &mut KiraState, message: Message) -> Task<Message> {
|
|||||||
} else {
|
} else {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Message::TimeZone(timezone_message) => {
|
Message::TimeZone(timezone_message) => {
|
||||||
if let Views::TimeZone(timezone_view) = &mut k_state.current_view {
|
if let Views::TimeZone(timezone_view) = &mut k_state.current_view {
|
||||||
let action = timezone_view.update(timezone_message);
|
let action = timezone_view.update(timezone_message);
|
||||||
match action {
|
match action {
|
||||||
StageAction::Next(tz_res) => {
|
StageAction::Next(tz_res) => {
|
||||||
k_state.config.config_trail.push(tz_res);
|
k_state.config.config_trail.push(tz_res);
|
||||||
k_state.current_view = Views::Locale(stages::locale::LocaleStage::new(&k_state.config));
|
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()
|
Task::none()
|
||||||
}
|
}
|
||||||
StageAction::Abort => iced::exit(),
|
StageAction::Abort => iced::exit(),
|
||||||
StageAction::Back => {
|
StageAction::Back => {
|
||||||
k_state.config.config_trail.pop();
|
k_state.config.config_trail.pop();
|
||||||
k_state.current_view =
|
k_state.current_view = k_state.stages_save.pop().unwrap();
|
||||||
Views::Network(network::NetworkStage::new(&k_state.toml_config));
|
Task::none()
|
||||||
Task::done(Message::Network(network::Message::CheckNetwork))
|
|
||||||
}
|
}
|
||||||
StageAction::None => Task::none(),
|
StageAction::None => Task::none(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Message::Locale(locale_msg) => {
|
Message::Locale(locale_msg) => {
|
||||||
if let Views::Locale(locale_view) = &mut k_state.current_view {
|
if let Views::Locale(locale_view) = &mut k_state.current_view {
|
||||||
match locale_view.update(locale_msg) {
|
match locale_view.update(locale_msg) {
|
||||||
StageAction::Next(locale_res) => {
|
StageAction::Next(locale_res) => {
|
||||||
k_state.config.config_trail.push(locale_res);
|
k_state.config.config_trail.push(locale_res);
|
||||||
iced::exit()
|
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 => {
|
StageAction::Back => {
|
||||||
k_state.config.config_trail.pop();
|
k_state.config.config_trail.pop();
|
||||||
k_state.current_view =
|
k_state.current_view = k_state.stages_save.pop().unwrap();
|
||||||
Views::TimeZone(stages::timezone::TimeZoneStage::new());
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
_ => Task::none(),
|
_ => Task::none(),
|
||||||
@@ -229,18 +237,79 @@ fn update(k_state: &mut KiraState, message: Message) -> Task<Message> {
|
|||||||
Task::none()
|
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_interface() -> iced::Result {
|
|
||||||
// iced::run(WellcomeStage::update, WellcomeStage::view)
|
|
||||||
// }
|
|
||||||
pub fn main() -> ExitCode {
|
pub fn main() -> ExitCode {
|
||||||
// let app_init = || {
|
// Init logger
|
||||||
// let mut k_state = KiraState::default()
|
kira_logger::init().unwrap();
|
||||||
// k_state.current_view = Views::Welcome(WelcomeStage::new());
|
|
||||||
// k_stateapp_init
|
|
||||||
// };
|
|
||||||
|
|
||||||
let iced_result = iced::application(KiraState::boot, update, view)
|
let iced_result = iced::application(KiraState::boot, update, view)
|
||||||
.window(iced::window::Settings {
|
.window(iced::window::Settings {
|
||||||
@@ -252,18 +321,14 @@ pub fn main() -> ExitCode {
|
|||||||
})
|
})
|
||||||
.centered()
|
.centered()
|
||||||
.theme(kira_theming::main_theme())
|
.theme(kira_theming::main_theme())
|
||||||
|
.default_font(iced::Font::MONOSPACE)
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
match iced_result {
|
match iced_result {
|
||||||
Ok(()) => ExitCode::SUCCESS,
|
Ok(()) => ExitCode::SUCCESS,
|
||||||
Err(_) => ExitCode::from(42), // Custom error code
|
Err(ex) => {
|
||||||
|
log::error!("ICED Error: {}", ex);
|
||||||
|
ExitCode::from(42)
|
||||||
|
} // Custom error code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn main() {
|
|
||||||
// println!("Hello, world!");
|
|
||||||
// println!("{}", t!("wellcome.text"));
|
|
||||||
// // Initialize the state
|
|
||||||
// let _res = main_interface();
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|||||||
+90
-18
@@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This file contains basic enums and structs to be used with stages.
|
This file contains basic enums and structs to be used with stages.
|
||||||
*/
|
*/
|
||||||
@@ -29,15 +28,102 @@ pub enum ConfigValue {
|
|||||||
F64(f64),
|
F64(f64),
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
Vector(Vec<ConfigValue>),
|
Vector(Vec<ConfigValue>),
|
||||||
Dictionary(HashMap<String, ConfigValue>)
|
Dictionary(HashMap<String, ConfigValue>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StageResult {
|
pub struct StageResult {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub config: Option<HashMap<String, ConfigValue>>,
|
pub config: Option<HashMap<String, ConfigValue>>,
|
||||||
pub resuts: Option<HashMap<String, ConfigValue>>,
|
pub error: Option<String>,
|
||||||
pub error: Option<String>
|
}
|
||||||
|
|
||||||
|
impl StageResult {
|
||||||
|
pub fn new(name: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
config: None,
|
||||||
|
error: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_error(mut self, ex: String) -> Self {
|
||||||
|
self.error = Some(ex);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_val(mut self, val_name: &str, conf_val: ConfigValue) -> Self {
|
||||||
|
match &mut self.config {
|
||||||
|
Some(c) => {
|
||||||
|
c.insert(val_name.into(), conf_val);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.config = Some(HashMap::from([(val_name.into(), conf_val)]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_val_string(self, val_name: &str, v: String) -> Self {
|
||||||
|
self.add_val(val_name, ConfigValue::String(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_val_bool(self, val_name: &str, v: bool) -> Self {
|
||||||
|
self.add_val(val_name, ConfigValue::Bool(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_val_u64(self, val_name: &str, v: u64) -> Self {
|
||||||
|
self.add_val(val_name, ConfigValue::U64(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_val(&self, val_name: &str) -> Option<ConfigValue> {
|
||||||
|
let v = self
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|hm| hm.get(val_name).cloned());
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_val_str(&self, val_name: &str) -> Option<String> {
|
||||||
|
match self.get_val(val_name) {
|
||||||
|
Some(ConfigValue::String(v)) => Some(v),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_val_bool(&self, val_name: &str) -> Option<bool> {
|
||||||
|
match self.get_val(val_name) {
|
||||||
|
Some(ConfigValue::Bool(v)) => Some(v),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct KiraConfig {
|
||||||
|
pub config_trail: Vec<StageResult>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for KiraConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
config_trail: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KiraConfig {
|
||||||
|
pub fn get_stage(&self, name: &str) -> Option<StageResult> {
|
||||||
|
self.config_trail
|
||||||
|
.iter()
|
||||||
|
.find(|v| v.name == name)
|
||||||
|
.and_then(|v| Some(v.clone()))
|
||||||
|
}
|
||||||
|
pub fn pop_last(&mut self) -> Option<StageResult> {
|
||||||
|
self.config_trail.pop()
|
||||||
|
}
|
||||||
|
pub fn push(&mut self, stage_result: StageResult) {
|
||||||
|
self.config_trail.push(stage_result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -47,17 +133,3 @@ pub enum StageAction {
|
|||||||
Next(StageResult),
|
Next(StageResult),
|
||||||
Abort,
|
Abort,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct KiraConfig {
|
|
||||||
pub config_trail: Vec<StageResult>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KiraConfig {
|
|
||||||
pub fn get_stage(&self, name: &str) -> Option<StageResult> {
|
|
||||||
self.config_trail.iter().find(|v| v.name == name)
|
|
||||||
.and_then(|v| Some(v.clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
// <Kira Installer - universal Linux installer.>
|
||||||
|
// Copyright (C) <2026> <Kira Foundation>
|
||||||
|
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Actual installation script.
|
||||||
|
*/
|
||||||
|
|
||||||
|
///
|
||||||
|
/// So. I am goind to make dir structure lile this
|
||||||
|
/// /bzz/root
|
||||||
|
/// /bzz/home
|
||||||
|
/// Mount root from it and binmount home
|
||||||
|
///
|
||||||
|
|
||||||
|
use log;
|
||||||
|
use crate::{
|
||||||
|
kira_theming,
|
||||||
|
stage::{KiraConfig, StageAction, StageResult},
|
||||||
|
};
|
||||||
|
use iced::{Alignment, Task, futures, widget};
|
||||||
|
use rust_i18n::t;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum InstallationState {
|
||||||
|
UserConfirmDialog,
|
||||||
|
Partitionning,
|
||||||
|
CopyFiles,
|
||||||
|
Configuring,
|
||||||
|
Finish,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct InstallStage {
|
||||||
|
state: InstallationState,
|
||||||
|
progress: f32,
|
||||||
|
progress_message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
UserConfirms,
|
||||||
|
UserDenies,
|
||||||
|
StartPartitioning,
|
||||||
|
UpdateProgress(Result<(f32, String), String>),
|
||||||
|
PartitioningFinish(Result<(), String>),
|
||||||
|
Back,
|
||||||
|
Cancel,
|
||||||
|
Finish,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Update {
|
||||||
|
StgAct(StageAction),
|
||||||
|
Task(Task<Message>),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl InstallStage {
|
||||||
|
pub fn new(config: &KiraConfig) -> Self {
|
||||||
|
Self {
|
||||||
|
state: InstallationState::UserConfirmDialog,
|
||||||
|
progress: 0.0,
|
||||||
|
progress_message: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_result(&self) -> StageResult {
|
||||||
|
StageResult::new("install")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, message: Message) -> Update {
|
||||||
|
match message {
|
||||||
|
Message::UserDenies => Update::StgAct(StageAction::Back),
|
||||||
|
Message::UserConfirms => {
|
||||||
|
// starting installation process here
|
||||||
|
self.state = InstallationState::Partitionning;
|
||||||
|
Update::Task(Task::run(disk_part::check_connected(), Message::UpdateProgress))
|
||||||
|
//Update::Task(Task::perform(, Message::PartitioningFinish))
|
||||||
|
}
|
||||||
|
Message::StartPartitioning => Update::StgAct(StageAction::None),
|
||||||
|
Message::PartitioningFinish(res) => Update::StgAct(StageAction::None),
|
||||||
|
Message::UpdateProgress(maybe_msg) => {
|
||||||
|
match maybe_msg {
|
||||||
|
Ok((progress, msg))=> {
|
||||||
|
self.progress = progress;
|
||||||
|
self.progress_message = msg;
|
||||||
|
Update::StgAct(StageAction::None)
|
||||||
|
}
|
||||||
|
Err(ex) => {
|
||||||
|
log::error!("{}", &ex);
|
||||||
|
self.state = InstallationState::Error;
|
||||||
|
Update::StgAct(StageAction::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Update::StgAct(StageAction::None)
|
||||||
|
|
||||||
|
},
|
||||||
|
Message::Cancel => Update::StgAct(StageAction::Abort),
|
||||||
|
Message::Back => Update::StgAct(StageAction::Back),
|
||||||
|
Message::Finish => Update::StgAct(StageAction::None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&self) -> iced::Element<'_, Message> {
|
||||||
|
let action_button = if self.state == InstallationState::Finish {
|
||||||
|
widget::button(widget::text(t!("button.finish"))).on_press(Message::Finish)
|
||||||
|
} else {
|
||||||
|
widget::button(widget::text(t!("button.cancel"))).on_press(Message::Cancel)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Embed the image bytes into the executable
|
||||||
|
let welcom_logo_handle = widget::image::Handle::from_bytes(kira_theming::get_logo_bytes());
|
||||||
|
|
||||||
|
let main_content = widget::column![
|
||||||
|
widget::container(
|
||||||
|
widget::column![
|
||||||
|
widget::image(welcom_logo_handle)
|
||||||
|
.width(iced::Pixels(128.0))
|
||||||
|
.height(iced::Pixels(128.0)),
|
||||||
|
widget::text(t!("install.caption")),
|
||||||
|
widget::progress_bar(0.0..=100.0, self.progress),
|
||||||
|
widget::text(self.progress_message.clone()),
|
||||||
|
]
|
||||||
|
.padding(10)
|
||||||
|
.spacing(10)
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
)
|
||||||
|
.height(iced::Length::Fill)
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.align_y(Alignment::Center),
|
||||||
|
widget::container(action_button)
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.align_y(Alignment::End)
|
||||||
|
.padding(10),
|
||||||
|
]
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.spacing(10);
|
||||||
|
|
||||||
|
let page_stack = widget::Stack::with_capacity(2).push(main_content);
|
||||||
|
|
||||||
|
if self.state == InstallationState::UserConfirmDialog {
|
||||||
|
page_stack
|
||||||
|
.push(
|
||||||
|
widget::column![
|
||||||
|
widget::text(t!("install.warning")),
|
||||||
|
widget::row![
|
||||||
|
widget::button(widget::text(t!("button.no")))
|
||||||
|
.on_press(Message::UserDenies),
|
||||||
|
widget::space::horizontal(), // Pushes the right button to the far right
|
||||||
|
widget::button(widget::text(t!("button.yes")))
|
||||||
|
.on_press(Message::UserConfirms)
|
||||||
|
]
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.align_y(Alignment::End)
|
||||||
|
.padding(10)
|
||||||
|
]
|
||||||
|
.align_x(Alignment::Center),
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
page_stack.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,398 @@
|
|||||||
|
// <Kira Installer - universal Linux installer.>
|
||||||
|
// Copyright (C) <2026> <Kira Foundation>
|
||||||
|
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is Keyboar stage, used to select Keyboard Layouts
|
||||||
|
*/
|
||||||
|
|
||||||
|
use log;
|
||||||
|
use crate::kira_scroll_list::{self, ListViewMessage};
|
||||||
|
use crate::stage::{StageAction, StageResult};
|
||||||
|
use iced::{Alignment, Length, widget};
|
||||||
|
use rust_i18n::t;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Layout {
|
||||||
|
layout: String,
|
||||||
|
description: String,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for Layout {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} ({})", &self.layout, &self.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Layout {
|
||||||
|
pub fn from_str_touple(v: &(&str, &str)) -> Self {
|
||||||
|
Self {
|
||||||
|
layout: v.0.trim().to_string(),
|
||||||
|
description: v.1.trim().to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Variant {
|
||||||
|
variant: String,
|
||||||
|
description: String,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for Variant {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} ({})", &self.variant, &self.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Variant {
|
||||||
|
pub fn from_str_touple(v: &(&str, &str)) -> Self {
|
||||||
|
Self {
|
||||||
|
variant: v.0.trim().to_string(),
|
||||||
|
description: v.1.trim().to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Variant {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
variant: "default".into(),
|
||||||
|
description: "Default variant".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct KeyboarLayout {
|
||||||
|
layout: Layout,
|
||||||
|
variant: Variant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyboarLayout {
|
||||||
|
pub fn to_result(&self) -> String {
|
||||||
|
format!("{}||{}", self.layout.layout, self.variant.variant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct KeyboarModel {
|
||||||
|
model: String,
|
||||||
|
description: String,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for KeyboarModel {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} ({})", &self.model, &self.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyboarModel {
|
||||||
|
pub fn from_str_touple(v: &(&str, &str)) -> Self {
|
||||||
|
Self {
|
||||||
|
model: v.0.trim().to_string(),
|
||||||
|
description: v.1.trim().to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for KeyboarLayout {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}/{}", &self.layout.layout, &self.variant.variant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct KeyboardStage {
|
||||||
|
models: Vec<KeyboarModel>,
|
||||||
|
model: Option<KeyboarModel>,
|
||||||
|
layouts: Vec<Layout>,
|
||||||
|
layout: Option<usize>,
|
||||||
|
variants_map: HashMap<String, Vec<Variant>>,
|
||||||
|
variants: Vec<Variant>,
|
||||||
|
variant: Option<usize>,
|
||||||
|
|
||||||
|
keyboard_layout: Option<KeyboarLayout>,
|
||||||
|
// selected_kbd_layout_id: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
SelectKeyboardModel(KeyboarModel),
|
||||||
|
SelectLayout(kira_scroll_list::ListViewMessage),
|
||||||
|
SelectVariant(kira_scroll_list::ListViewMessage),
|
||||||
|
|
||||||
|
// AddLayout(usize),
|
||||||
|
// RemoveLayout(usize),
|
||||||
|
// SelectAddedLayout(kira_scroll_list::ListViewMessage),
|
||||||
|
Next,
|
||||||
|
Back,
|
||||||
|
}
|
||||||
|
|
||||||
|
const XKB_RULES_FILE_NAME: &str = "/usr/share/X11/xkb/rules/evdev.lst";
|
||||||
|
|
||||||
|
/// Getting list of awailable keyboard layouts from from xkb/rules/evdev.lst file.
|
||||||
|
fn get_layouts_blocking() -> std::io::Result<(
|
||||||
|
Vec<KeyboarModel>,
|
||||||
|
Vec<Layout>,
|
||||||
|
HashMap<String, Vec<Variant>>,
|
||||||
|
)> {
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{BufReader, Read};
|
||||||
|
|
||||||
|
let mut file_content = String::new();
|
||||||
|
{
|
||||||
|
let locgen_file = File::open(XKB_RULES_FILE_NAME)?;
|
||||||
|
let mut reader = BufReader::new(locgen_file);
|
||||||
|
reader.read_to_string(&mut file_content)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw_sections: Vec<String> = file_content.split("! ").map(str::to_string).collect();
|
||||||
|
|
||||||
|
// Parsing models data
|
||||||
|
let models: Vec<KeyboarModel> = raw_sections
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.starts_with("model"))
|
||||||
|
.and_then(|s| {
|
||||||
|
Some(
|
||||||
|
s.lines()
|
||||||
|
.skip(1)
|
||||||
|
.filter_map(|s| s.trim().split_once(char::is_whitespace))
|
||||||
|
.map(|s_spl| KeyboarModel::from_str_touple(&s_spl))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(Vec::new);
|
||||||
|
|
||||||
|
// Parsing layots data
|
||||||
|
let layouts: Vec<Layout> = raw_sections
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.starts_with("layout"))
|
||||||
|
.and_then(|s| {
|
||||||
|
Some(
|
||||||
|
s.lines()
|
||||||
|
.skip(1)
|
||||||
|
.filter_map(|s| s.trim().split_once(char::is_whitespace))
|
||||||
|
.map(|s_spl| Layout::from_str_touple(&s_spl))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(Vec::new);
|
||||||
|
|
||||||
|
// parsing variants data to HashMap
|
||||||
|
let mut variants: HashMap<String, Vec<Variant>> = layouts
|
||||||
|
.iter()
|
||||||
|
.map(|l| (l.layout.clone(), vec![Variant::default()]))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
raw_sections
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.starts_with("variant"))
|
||||||
|
.and_then(|s| {
|
||||||
|
Some(
|
||||||
|
s.lines()
|
||||||
|
.skip(1)
|
||||||
|
.filter_map(|s| s.trim().split_once(char::is_whitespace))
|
||||||
|
.filter_map(|(vrnt, l_plus_descr)| {
|
||||||
|
l_plus_descr
|
||||||
|
.trim()
|
||||||
|
.split_once(": ")
|
||||||
|
.and_then(|(loc, descr)| {
|
||||||
|
Some((loc.trim(), Variant::from_str_touple(&(vrnt, descr))))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.for_each(|v| {
|
||||||
|
let maybe_key = variants
|
||||||
|
.get_mut(v.0)
|
||||||
|
.and_then(|vrn_vec| Some(vrn_vec.push(v.1.clone())));
|
||||||
|
if maybe_key.is_none() {
|
||||||
|
variants.insert(v.0.to_string(), vec![v.1.clone()]);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok((models, layouts, variants))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyboardStage {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
match get_layouts_blocking() {
|
||||||
|
Ok((models, layouts, variants_map)) => {
|
||||||
|
let def_layout = layouts
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find_map(|(idx, l)| (l.layout == "us").then_some(idx));
|
||||||
|
|
||||||
|
let variants = variants_map["us"].clone();
|
||||||
|
|
||||||
|
let def_model = models[0].clone();
|
||||||
|
|
||||||
|
let keyboard_layout = def_layout.and_then(|li_d| {
|
||||||
|
Some(KeyboarLayout {
|
||||||
|
layout: layouts[li_d].clone(),
|
||||||
|
variant: variants[0].clone(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
layouts: layouts,
|
||||||
|
variants_map: variants_map,
|
||||||
|
variants: variants,
|
||||||
|
model: Some(def_model),
|
||||||
|
models: models,
|
||||||
|
layout: def_layout,
|
||||||
|
variant: Some(0),
|
||||||
|
keyboard_layout: keyboard_layout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ex) => {
|
||||||
|
log::error!(
|
||||||
|
"Exception while trying to get kayboard layouts data from system {}",
|
||||||
|
ex
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
layouts: Vec::new(),
|
||||||
|
variants_map: HashMap::new(),
|
||||||
|
variants: Vec::new(),
|
||||||
|
model: None,
|
||||||
|
models: Vec::new(),
|
||||||
|
layout: None,
|
||||||
|
variant: None,
|
||||||
|
keyboard_layout: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_result(&self) -> StageResult {
|
||||||
|
if let Some(model) = &self.model
|
||||||
|
&& let Some(k_l) = &self.keyboard_layout
|
||||||
|
{
|
||||||
|
StageResult::new("keyboard")
|
||||||
|
.add_val_string("model", model.model.clone())
|
||||||
|
.add_val_string("layout", k_l.to_result())
|
||||||
|
} else {
|
||||||
|
StageResult::new("keyboard")
|
||||||
|
.add_error("Something go terrible wrong, there is no keyboard layouts.".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, message: Message) -> StageAction {
|
||||||
|
match message {
|
||||||
|
Message::SelectLayout(ListViewMessage::Select(l_id)) => {
|
||||||
|
if let Some(current_l_id) = self.layout
|
||||||
|
&& current_l_id != l_id
|
||||||
|
{
|
||||||
|
self.layout = Some(l_id);
|
||||||
|
self.variants = self.variants_map[self.layouts[l_id].layout.as_str()].clone();
|
||||||
|
self.variant = Some(0);
|
||||||
|
|
||||||
|
self.keyboard_layout = Some(KeyboarLayout {
|
||||||
|
layout: self.layouts[l_id].clone(),
|
||||||
|
variant: self.variants[0].clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::SelectVariant(ListViewMessage::Select(v_id)) => {
|
||||||
|
self.variant = Some(v_id);
|
||||||
|
self.keyboard_layout = Some(KeyboarLayout {
|
||||||
|
layout: self.layouts[self.layout.unwrap()].clone(),
|
||||||
|
variant: self.variants[v_id].clone(),
|
||||||
|
});
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::SelectKeyboardModel(k_m) => {
|
||||||
|
self.model = Some(k_m);
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::Next => StageAction::Next(self.gen_result()),
|
||||||
|
Message::Back => StageAction::Back,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&self) -> iced::Element<'_, Message> {
|
||||||
|
let next_button = if self.model.is_some() && self.keyboard_layout.is_some() {
|
||||||
|
widget::button(widget::text(t!("button.next"))).on_press(Message::Next)
|
||||||
|
} else {
|
||||||
|
widget::button(widget::text(t!("button.next")))
|
||||||
|
};
|
||||||
|
|
||||||
|
let main_content = if self.layouts.len() > 0 {
|
||||||
|
widget::container(
|
||||||
|
widget::column![
|
||||||
|
widget::column![
|
||||||
|
widget::text(t!("keyboard.select_model")),
|
||||||
|
widget::pick_list(
|
||||||
|
self.models.clone(),
|
||||||
|
self.model.clone(),
|
||||||
|
Message::SelectKeyboardModel,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
widget::row![
|
||||||
|
widget::column![
|
||||||
|
widget::text(t!("keyboard.select_layout")),
|
||||||
|
crate::kira_scroll_list::list_view(
|
||||||
|
&self.layouts,
|
||||||
|
self.layout,
|
||||||
|
Length::Shrink
|
||||||
|
)
|
||||||
|
.map(Message::SelectLayout)
|
||||||
|
],
|
||||||
|
widget::column![
|
||||||
|
widget::text(t!("keyboard.select_variant")),
|
||||||
|
crate::kira_scroll_list::list_view(
|
||||||
|
&self.variants,
|
||||||
|
self.variant,
|
||||||
|
Length::FillPortion(1)
|
||||||
|
)
|
||||||
|
.map(Message::SelectVariant)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.height(Length::Shrink),
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.align_x(Alignment::Center),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
widget::container(widget::text(t!("keyboard.init_error")))
|
||||||
|
};
|
||||||
|
|
||||||
|
let back_button = widget::button(widget::text(t!("button.back"))).on_press(Message::Back);
|
||||||
|
|
||||||
|
widget::column![
|
||||||
|
main_content
|
||||||
|
.padding(iced::Padding {
|
||||||
|
top: 10.0,
|
||||||
|
right: 10.0,
|
||||||
|
bottom: 0.0,
|
||||||
|
left: 10.0,
|
||||||
|
})
|
||||||
|
.height(iced::Length::Fill)
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.align_y(Alignment::Center),
|
||||||
|
widget::row![
|
||||||
|
back_button,
|
||||||
|
widget::space::horizontal(), // Pushes the right button to the far right
|
||||||
|
next_button
|
||||||
|
]
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.align_y(Alignment::End)
|
||||||
|
.padding(10),
|
||||||
|
]
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.spacing(10)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-16
@@ -14,17 +14,13 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This is License stage. Used to inform user about program legal status.
|
This is License stage. Used to inform user about program legal status.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use iced::{Alignment, widget};
|
use iced::{Alignment, widget};
|
||||||
|
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::stage;
|
use crate::stage;
|
||||||
use crate::stage::StageResult;
|
use crate::stage::StageResult;
|
||||||
@@ -47,15 +43,8 @@ pub enum Message {
|
|||||||
|
|
||||||
impl LicenseStage {
|
impl LicenseStage {
|
||||||
fn accepted() -> StageResult {
|
fn accepted() -> StageResult {
|
||||||
StageResult {
|
StageResult::new("license")
|
||||||
name: "license".into(),
|
.add_val_bool("accepted", true)
|
||||||
config: Some(HashMap::from([(
|
|
||||||
"accepted".to_string(),
|
|
||||||
stage::ConfigValue::Bool(true),
|
|
||||||
)])),
|
|
||||||
resuts: None,
|
|
||||||
error: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, message: Message) -> stage::StageAction {
|
pub fn update(&mut self, message: Message) -> stage::StageAction {
|
||||||
@@ -74,12 +63,20 @@ impl LicenseStage {
|
|||||||
widget::button(widget::text(t!("license.button.decline"))).on_press(Message::Decline);
|
widget::button(widget::text(t!("license.button.decline"))).on_press(Message::Decline);
|
||||||
|
|
||||||
widget::column![
|
widget::column![
|
||||||
widget::container(widget::column![
|
widget::container(
|
||||||
widget::text(t!("license.accept_text")),
|
widget::column![
|
||||||
|
widget::text(t!("license.accept_text")).font(iced::Font {
|
||||||
|
weight: iced::font::Weight::Bold,
|
||||||
|
..iced::Font::DEFAULT
|
||||||
|
}),
|
||||||
widget::container(widget::text(t!("license.license")))
|
widget::container(widget::text(t!("license.license")))
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.style(widget::container::bordered_box)
|
.style(widget::container::bordered_box)
|
||||||
].align_x(Alignment::Center).padding(20).spacing(10))
|
]
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.padding(20)
|
||||||
|
.spacing(15)
|
||||||
|
)
|
||||||
.height(iced::Length::Fill)
|
.height(iced::Length::Fill)
|
||||||
.width(iced::Length::Fill)
|
.width(iced::Length::Fill)
|
||||||
.align_x(Alignment::Center)
|
.align_x(Alignment::Center)
|
||||||
|
|||||||
+65
-80
@@ -29,9 +29,9 @@ use std::collections::HashMap;
|
|||||||
const LOCALES_GEN_FILE_NAME: &str = "/etc/locale.gen";
|
const LOCALES_GEN_FILE_NAME: &str = "/etc/locale.gen";
|
||||||
const LOCALES_GEN_START_LINE: usize = 17;
|
const LOCALES_GEN_START_LINE: usize = 17;
|
||||||
|
|
||||||
fn default_locale() -> (String, String) {
|
// fn default_locale() -> (String, String) {
|
||||||
("C.UTF-8".to_string(), "UTF-8".to_string())
|
// ("C.UTF-8".to_string(), "UTF-8".to_string())
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct LocaleData {
|
pub struct LocaleData {
|
||||||
@@ -41,10 +41,21 @@ pub struct LocaleData {
|
|||||||
|
|
||||||
impl std::fmt::Display for LocaleData {
|
impl std::fmt::Display for LocaleData {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{} ({})", self.description, self.code)
|
write!(f, "{} ({})", self.code, self.description)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LocaleData {
|
||||||
|
pub fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
code: "C.UTF-8".into(),
|
||||||
|
description: "C locale".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// gets locales list from system "/etc/locale.gen" file
|
||||||
|
/// Filters out non UTF-8 locales
|
||||||
fn get_locales_codes_list_blocking() -> std::io::Result<Vec<(String, String)>> {
|
fn get_locales_codes_list_blocking() -> std::io::Result<Vec<(String, String)>> {
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufReader, Read};
|
use std::io::{BufReader, Read};
|
||||||
@@ -76,6 +87,7 @@ fn get_locales_codes_list_blocking() -> std::io::Result<Vec<(String, String)>> {
|
|||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets locales description from "/usr/share/i18n/locales/" directory.
|
||||||
fn get_locales_description_blocking() -> Result<HashMap<String, String>, String> {
|
fn get_locales_description_blocking() -> Result<HashMap<String, String>, String> {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
@@ -120,7 +132,7 @@ fn get_locales_description_blocking() -> Result<HashMap<String, String>, String>
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LocaleStage {
|
pub struct LocaleStage {
|
||||||
lang_locale: Option<LocaleData>,
|
lang_locale: Option<LocaleData>,
|
||||||
all_locale: Option<LocaleData>,
|
formats_locale: Option<LocaleData>,
|
||||||
locales: Vec<LocaleData>,
|
locales: Vec<LocaleData>,
|
||||||
lang_locale_text: String,
|
lang_locale_text: String,
|
||||||
all_locale_text: String,
|
all_locale_text: String,
|
||||||
@@ -129,7 +141,7 @@ pub struct LocaleStage {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
SelectLangLocale(LocaleData),
|
SelectLangLocale(LocaleData),
|
||||||
SelectAllLocale(LocaleData),
|
SelectFormatsLocale(LocaleData),
|
||||||
Next,
|
Next,
|
||||||
Back,
|
Back,
|
||||||
}
|
}
|
||||||
@@ -141,25 +153,10 @@ impl LocaleStage {
|
|||||||
.get_stage("welcome")
|
.get_stage("welcome")
|
||||||
.and_then(|v| v.config.and_then(|v| v.get("loc_code").cloned()));
|
.and_then(|v| v.config.and_then(|v| v.get("loc_code").cloned()));
|
||||||
|
|
||||||
let loc_codes = get_locales_codes_list_blocking().unwrap();
|
let raw_loc_codes = get_locales_codes_list_blocking().unwrap();
|
||||||
|
|
||||||
// if we get lang code from wellcome stage, try to fing match with system locales
|
|
||||||
let lang_match = if let Some(ConfigValue::String(lang)) = maybe_lang {
|
|
||||||
let lang = lang.replace("-", "_").to_lowercase();
|
|
||||||
loc_codes
|
|
||||||
.iter()
|
|
||||||
.find(|(code, _)| code.to_lowercase().starts_with(&lang))
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(default_locale)
|
|
||||||
} else {
|
|
||||||
default_locale()
|
|
||||||
};
|
|
||||||
|
|
||||||
let raw_loc_descr = get_locales_description_blocking().unwrap();
|
let raw_loc_descr = get_locales_description_blocking().unwrap();
|
||||||
|
|
||||||
println!("{:?}", raw_loc_descr);
|
let locales: Vec<LocaleData> = raw_loc_codes
|
||||||
|
|
||||||
let locales: Vec<LocaleData> = loc_codes
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(code, _)| {
|
.filter_map(|(code, _)| {
|
||||||
code.split_once('.')
|
code.split_once('.')
|
||||||
@@ -176,33 +173,31 @@ impl LocaleStage {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
println!("{:?}", locales);
|
// if we get lang code from wellcome stage, try to fing match with system locales
|
||||||
|
let lang_match = if let Some(ConfigValue::String(lang)) = maybe_lang && lang != "en" {
|
||||||
|
let lang = lang.replace("-", "_").to_lowercase();
|
||||||
|
locales
|
||||||
|
.iter()
|
||||||
|
.find(|l| l.code.to_lowercase().starts_with(&lang))
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(LocaleData::default)
|
||||||
|
} else {
|
||||||
|
LocaleData::default()
|
||||||
|
};
|
||||||
|
|
||||||
let locale = locales.iter().find(|l| l.code == lang_match.0).cloned();
|
let lang_locale_text = format!(
|
||||||
|
|
||||||
let lang_locale_text = locale
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|l| {
|
|
||||||
Some(format!(
|
|
||||||
"{}: {}",
|
"{}: {}",
|
||||||
t!("locale.select_language_locale"),
|
t!("locale.select_language_locale"),
|
||||||
l.code
|
lang_match.code
|
||||||
))
|
);
|
||||||
})
|
let all_locale_text = format!(
|
||||||
.unwrap_or_else(|| String::new());
|
|
||||||
let all_locale_text = locale
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|l| {
|
|
||||||
Some(format!(
|
|
||||||
"{}: {}",
|
"{}: {}",
|
||||||
t!("locale.select_formats_locale"),
|
t!("locale.select_formats_locale"),
|
||||||
l.code
|
lang_match.code
|
||||||
))
|
);
|
||||||
})
|
|
||||||
.unwrap_or_else(|| String::new());
|
|
||||||
Self {
|
Self {
|
||||||
lang_locale: locale.clone(),
|
lang_locale: Some(lang_match.clone()),
|
||||||
all_locale: locale,
|
formats_locale: Some(lang_match),
|
||||||
locales: locales,
|
locales: locales,
|
||||||
lang_locale_text: lang_locale_text,
|
lang_locale_text: lang_locale_text,
|
||||||
all_locale_text: all_locale_text,
|
all_locale_text: all_locale_text,
|
||||||
@@ -211,39 +206,17 @@ impl LocaleStage {
|
|||||||
|
|
||||||
fn gen_result(&self) -> StageResult {
|
fn gen_result(&self) -> StageResult {
|
||||||
if let Some(lang_locale) = &self.lang_locale
|
if let Some(lang_locale) = &self.lang_locale
|
||||||
&& let Some(all_locale) = &self.all_locale
|
&& let Some(formats_locale) = &self.formats_locale
|
||||||
{
|
{
|
||||||
StageResult {
|
StageResult::new("locale")
|
||||||
name: "locale".to_string(),
|
.add_val_string("lang_locale", lang_locale.code.clone())
|
||||||
config: Some(HashMap::from([
|
.add_val_string("formats_locale", formats_locale.code.clone())
|
||||||
(
|
|
||||||
"lang_locale".to_string(),
|
|
||||||
ConfigValue::String(lang_locale.code.clone()),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"all_locale".to_string(),
|
|
||||||
ConfigValue::String(all_locale.code.clone()),
|
|
||||||
),
|
|
||||||
])),
|
|
||||||
resuts: None,
|
|
||||||
error: None,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
StageResult {
|
let loc = LocaleData::default();
|
||||||
name: "locale".to_string(),
|
StageResult::new("locale")
|
||||||
config: Some(HashMap::from([
|
.add_val_string("lang_locale", loc.code.clone())
|
||||||
(
|
.add_val_string("formats_locale", loc.code)
|
||||||
"lang_locale".to_string(),
|
|
||||||
ConfigValue::String(default_locale().0),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"all_locale".to_string(),
|
|
||||||
ConfigValue::String(default_locale().0),
|
|
||||||
),
|
|
||||||
])),
|
|
||||||
resuts: None,
|
|
||||||
error: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,10 +228,10 @@ impl LocaleStage {
|
|||||||
self.lang_locale = Some(loc);
|
self.lang_locale = Some(loc);
|
||||||
StageAction::None
|
StageAction::None
|
||||||
}
|
}
|
||||||
Message::SelectAllLocale(loc) => {
|
Message::SelectFormatsLocale(loc) => {
|
||||||
self.all_locale_text =
|
self.all_locale_text =
|
||||||
format!("{}: {}", t!("locale.select_formats_locale"), loc.code);
|
format!("{}: {}", t!("locale.select_formats_locale"), loc.code);
|
||||||
self.all_locale = Some(loc);
|
self.formats_locale = Some(loc);
|
||||||
StageAction::None
|
StageAction::None
|
||||||
}
|
}
|
||||||
Message::Back => StageAction::Back,
|
Message::Back => StageAction::Back,
|
||||||
@@ -279,20 +252,32 @@ impl LocaleStage {
|
|||||||
widget::image(welcome_logo_handle)
|
widget::image(welcome_logo_handle)
|
||||||
.width(iced::Pixels(128.0))
|
.width(iced::Pixels(128.0))
|
||||||
.height(iced::Pixels(128.0)),
|
.height(iced::Pixels(128.0)),
|
||||||
|
widget::container(
|
||||||
widget::column![
|
widget::column![
|
||||||
widget::text(self.lang_locale_text.as_str()),
|
widget::text(self.lang_locale_text.as_str()),
|
||||||
widget::pick_list(
|
widget::pick_list(
|
||||||
self.locales.clone(),
|
self.locales.clone(),
|
||||||
self.lang_locale.clone(),
|
self.lang_locale.clone(),
|
||||||
Message::SelectLangLocale,
|
Message::SelectLangLocale,
|
||||||
),
|
)
|
||||||
|
]
|
||||||
|
.spacing(5)
|
||||||
|
)
|
||||||
|
.style(widget::container::bordered_box)
|
||||||
|
.padding(5),
|
||||||
|
widget::container(
|
||||||
|
widget::column![
|
||||||
widget::text(self.all_locale_text.as_str()),
|
widget::text(self.all_locale_text.as_str()),
|
||||||
widget::pick_list(
|
widget::pick_list(
|
||||||
self.locales.clone(),
|
self.locales.clone(),
|
||||||
self.all_locale.clone(),
|
self.formats_locale.clone(),
|
||||||
Message::SelectAllLocale,
|
Message::SelectFormatsLocale,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
.spacing(5)
|
||||||
|
)
|
||||||
|
.style(widget::container::bordered_box)
|
||||||
|
.padding(5)
|
||||||
]
|
]
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
|
|||||||
@@ -20,3 +20,7 @@ pub mod license;
|
|||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod timezone;
|
pub mod timezone;
|
||||||
pub mod locale;
|
pub mod locale;
|
||||||
|
pub mod keyboard;
|
||||||
|
pub mod partition;
|
||||||
|
pub mod security;
|
||||||
|
pub mod install;
|
||||||
+25
-26
@@ -19,17 +19,16 @@
|
|||||||
it needed for installation to process.
|
it needed for installation to process.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use log;
|
||||||
use iced::{Alignment, Task, widget};
|
use iced::{Alignment, Task, widget};
|
||||||
use iced_moving_picture::widget::apng;
|
use iced_moving_picture::widget::apng;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use std::collections::HashMap;
|
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
|
||||||
use crate::stage;
|
use crate::stage;
|
||||||
use crate::stage::StageResult;
|
use crate::stage::StageResult;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
Cheking,
|
Cheking,
|
||||||
@@ -50,6 +49,7 @@ pub struct NetworkStage {
|
|||||||
use_net_settings: bool,
|
use_net_settings: bool,
|
||||||
reqire_network: bool,
|
reqire_network: bool,
|
||||||
spinner_frames: apng::Frames,
|
spinner_frames: apng::Frames,
|
||||||
|
logo_handle: widget::image::Handle,
|
||||||
status_message: String,
|
status_message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ fn check_connection_blocking() -> CheckResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ex) => {
|
Err(ex) => {
|
||||||
println!("Exception while trying to chen net connectivity: {}", ex);
|
log::error!("Exception while trying to chen net connectivity: {}", ex);
|
||||||
CheckResult::CheckError
|
CheckResult::CheckError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,19 +104,22 @@ impl NetworkStage {
|
|||||||
.and_then(|v| v.get("reqire_network"))
|
.and_then(|v| v.get("reqire_network"))
|
||||||
.and_then(|v| v.as_bool())
|
.and_then(|v| v.as_bool())
|
||||||
{
|
{
|
||||||
println!("Config value reqire_network read {}", reqire_network_conf);
|
log::debug!("Config value reqire_network read {}", reqire_network_conf);
|
||||||
reqire_network = reqire_network_conf;
|
reqire_network = reqire_network_conf;
|
||||||
} else {
|
} else {
|
||||||
println!("Error parsing network config. Using default values.");
|
log::error!("Error parsing network config. Using default values.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let spinner_frames =
|
let spinner_frames =
|
||||||
apng::Frames::from_bytes(crate::kira_theming::get_spiner_bytes()).unwrap();
|
apng::Frames::from_bytes(crate::kira_theming::get_spiner_bytes()).unwrap();
|
||||||
|
|
||||||
|
let logo_handle = widget::image::Handle::from_bytes(crate::kira_theming::get_logo_bytes());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
logo_handle: logo_handle,
|
||||||
internet_active: false,
|
internet_active: false,
|
||||||
state: State::Cheking,
|
state: State::Cheking,
|
||||||
use_net_settings: false,
|
use_net_settings: true,
|
||||||
reqire_network: reqire_network,
|
reqire_network: reqire_network,
|
||||||
spinner_frames: spinner_frames,
|
spinner_frames: spinner_frames,
|
||||||
status_message: t!("network.checking").into(),
|
status_message: t!("network.checking").into(),
|
||||||
@@ -124,21 +127,9 @@ impl NetworkStage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gen_result(&self) -> StageResult {
|
fn gen_result(&self) -> StageResult {
|
||||||
StageResult {
|
StageResult::new("network")
|
||||||
name: "network".into(),
|
.add_val_bool("internet_active", self.internet_active)
|
||||||
config: Some(HashMap::from([
|
.add_val_bool("use_net_settings", self.use_net_settings)
|
||||||
(
|
|
||||||
"internet_active".to_string(),
|
|
||||||
stage::ConfigValue::Bool(self.internet_active),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"use_net_settings".to_string(),
|
|
||||||
stage::ConfigValue::Bool(self.use_net_settings),
|
|
||||||
),
|
|
||||||
])),
|
|
||||||
resuts: None,
|
|
||||||
error: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, message: Message) -> UpdateResult {
|
pub fn update(&mut self, message: Message) -> UpdateResult {
|
||||||
@@ -178,10 +169,10 @@ impl NetworkStage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(&self) -> iced::Element<'_, Message> {
|
pub fn view(&self) -> iced::Element<'_, Message> {
|
||||||
let next_button = if (self.state == State::Cheking) || (!self.internet_active && self.reqire_network) {
|
let next_button =
|
||||||
|
if (self.state == State::Cheking) || (!self.internet_active && self.reqire_network) {
|
||||||
widget::button(widget::text(t!("button.next")))
|
widget::button(widget::text(t!("button.next")))
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
widget::button(widget::text(t!("button.next"))).on_press(Message::Next)
|
widget::button(widget::text(t!("button.next"))).on_press(Message::Next)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -202,8 +193,16 @@ impl NetworkStage {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let spinner_container = match self.state {
|
let spinner_container = match self.state {
|
||||||
State::Cheking => widget::container(apng(&self.spinner_frames)),
|
State::Cheking => widget::container(
|
||||||
State::Normal => widget::container(widget::space().height(128).width(128)),
|
apng(&self.spinner_frames)
|
||||||
|
.width(iced::Pixels(128.0).into())
|
||||||
|
.height(iced::Pixels(128.0).into()),
|
||||||
|
),
|
||||||
|
State::Normal => widget::container(
|
||||||
|
widget::image(&self.logo_handle)
|
||||||
|
.width(iced::Pixels(128.0))
|
||||||
|
.height(iced::Pixels(128.0)),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let info_column = widget::column![
|
let info_column = widget::column![
|
||||||
|
|||||||
@@ -0,0 +1,417 @@
|
|||||||
|
// <Kira Installer - universal Linux installer.>
|
||||||
|
// Copyright (C) <2026> <Kira Foundation>
|
||||||
|
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is partition stage!
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::char;
|
||||||
|
|
||||||
|
use crate::kira_color_bar;
|
||||||
|
use crate::kira_disk_layout::{BlkDev, FSType, PartInfo, PartLayout};
|
||||||
|
use crate::kira_size::KiraSize;
|
||||||
|
use crate::kira_sysinfo;
|
||||||
|
use crate::stage::{StageAction, StageResult};
|
||||||
|
use iced::{Alignment, Color, Element, Length, widget};
|
||||||
|
use log;
|
||||||
|
use rust_i18n::t;
|
||||||
|
use toml::Table;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum SwapMode {
|
||||||
|
NoSwap,
|
||||||
|
SwapNoHibernate,
|
||||||
|
SwapHibernate,
|
||||||
|
SwapCustom,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SwapMode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::NoSwap => f.write_str(t!("partition.no_swap").as_ref()),
|
||||||
|
Self::SwapNoHibernate => f.write_str(t!("partition.swap_no_hibernate").as_ref()),
|
||||||
|
Self::SwapHibernate => f.write_str(t!("partition.swap_hibernate").as_ref()),
|
||||||
|
Self::SwapCustom => f.write_str(t!("partition.swap_custom").as_ref()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SWAP_MODES: [SwapMode; 4] = [
|
||||||
|
SwapMode::NoSwap,
|
||||||
|
SwapMode::SwapNoHibernate,
|
||||||
|
SwapMode::SwapHibernate,
|
||||||
|
SwapMode::SwapCustom,
|
||||||
|
];
|
||||||
|
|
||||||
|
fn part_type_to_color(t: &FSType) -> Color {
|
||||||
|
match t {
|
||||||
|
FSType::VFAT => Color::from_rgb8(204, 121, 167),
|
||||||
|
FSType::XFS => Color::from_rgb8(0, 158, 115),
|
||||||
|
FSType::LUKS => Color::from_rgb8(240, 228, 66),
|
||||||
|
FSType::SWAP => Color::from_rgb8(213, 94, 0),
|
||||||
|
FSType::EXT4 => Color::from_rgb8(0, 114, 178),
|
||||||
|
FSType::BTRFS => Color::from_rgb8(86, 180, 233),
|
||||||
|
_ => Color::from_rgb8(230, 159, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts PartInfo into data for ColorBar widget
|
||||||
|
///
|
||||||
|
fn part_to_ct_params(part: &PartInfo, dev_size: KiraSize) -> (String, u16, Color) {
|
||||||
|
let fill_ratio: u16 = ((part.size.b() as f32 / dev_size.b() as f32) * 11.0) as u16;
|
||||||
|
let fill_ratio = fill_ratio.max(1);
|
||||||
|
let part_color = part_type_to_color(&part.fs_type);
|
||||||
|
(part.to_string(), fill_ratio, part_color)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PartitionStage {
|
||||||
|
device: Option<BlkDev>,
|
||||||
|
devices: Vec<BlkDev>,
|
||||||
|
swap_mode: Option<SwapMode>,
|
||||||
|
use_zram: bool,
|
||||||
|
secure_boot: bool,
|
||||||
|
encrypt_drive: bool,
|
||||||
|
secure_boot_in_setup_mode: bool,
|
||||||
|
custom_swap_size_mb: u32,
|
||||||
|
custom_swap_size_str: String,
|
||||||
|
selected_disk_parts: Option<Vec<(String, u16, Color)>>,
|
||||||
|
generated_disk_parts: Option<Vec<(String, u16, Color)>>,
|
||||||
|
system_ram_size: KiraSize,
|
||||||
|
vram_of_all_gpus_size: KiraSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
SelectDevice(BlkDev),
|
||||||
|
SelectSwapMode(SwapMode),
|
||||||
|
ToggleZram(bool),
|
||||||
|
ToggleSecureBoot(bool),
|
||||||
|
ToggleEncrypDrive(bool),
|
||||||
|
SwapSizeFieldChange(String),
|
||||||
|
SwapSizeIncrement,
|
||||||
|
SwapSizeDecrement,
|
||||||
|
Next,
|
||||||
|
Back,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartitionStage {
|
||||||
|
pub fn new(toml_config: &Table) -> Result<Self, String> {
|
||||||
|
let maybe_dev_list = BlkDev::list_sys_blk_dev();
|
||||||
|
log::info!("hiii from partition");
|
||||||
|
let min_disk_size_mb = toml_config
|
||||||
|
.get("partition")
|
||||||
|
.and_then(|v| v.as_table())
|
||||||
|
.and_then(|v| v.get("min_disk_size_mb"))
|
||||||
|
.and_then(|v| v.as_integer())
|
||||||
|
.unwrap_or(8192);
|
||||||
|
|
||||||
|
let min_disk_size: KiraSize = KiraSize::new_mb(min_disk_size_mb.try_into().unwrap_or(8192));
|
||||||
|
|
||||||
|
|
||||||
|
let sec_boot_setup = match kira_sysinfo::get_secure_boot_status() {
|
||||||
|
Ok(sb_status) => sb_status.setup_mode,
|
||||||
|
Err(ex) => {
|
||||||
|
log::error!("Unable to get secure boot status! {}", ex);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match maybe_dev_list {
|
||||||
|
Ok(dev_list) => {
|
||||||
|
let acepted_dev: Vec<BlkDev> = dev_list
|
||||||
|
.iter()
|
||||||
|
.filter(|v| v.size.b() >= min_disk_size.b())
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// getting system memory info
|
||||||
|
let system_ram_size = KiraSize::new_kb(kira_sysinfo::get_meminfo()["MemTotal"]); // from kB to bytes
|
||||||
|
let vram_of_all_gpus_size = KiraSize::new_b(
|
||||||
|
kira_sysinfo::get_gpus_list()
|
||||||
|
.iter()
|
||||||
|
.fold(0u64, |acc, gpi| acc + gpi.vis_vram_total),
|
||||||
|
);
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"stage init ram {} vram {}",
|
||||||
|
system_ram_size,
|
||||||
|
vram_of_all_gpus_size
|
||||||
|
);
|
||||||
|
Ok(Self {
|
||||||
|
device: None,
|
||||||
|
devices: acepted_dev,
|
||||||
|
swap_mode: Some(SwapMode::SwapNoHibernate),
|
||||||
|
use_zram: true,
|
||||||
|
secure_boot: false,
|
||||||
|
encrypt_drive: false,
|
||||||
|
secure_boot_in_setup_mode: sec_boot_setup,
|
||||||
|
custom_swap_size_mb: 1024,
|
||||||
|
custom_swap_size_str: "1024MB".into(),
|
||||||
|
selected_disk_parts: None,
|
||||||
|
generated_disk_parts: None,
|
||||||
|
system_ram_size: system_ram_size,
|
||||||
|
vram_of_all_gpus_size: vram_of_all_gpus_size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(ex) => Err(ex),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_swap_size(&self) -> KiraSize {
|
||||||
|
if let Some(s_mode) = self.swap_mode
|
||||||
|
&& s_mode == SwapMode::SwapCustom
|
||||||
|
{
|
||||||
|
return KiraSize::new_mb(self.custom_swap_size_mb as u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
let total_ram = self.system_ram_size + self.vram_of_all_gpus_size;
|
||||||
|
|
||||||
|
let sys_ram_gb = self.system_ram_size.gb();
|
||||||
|
log::debug!("sys_ram_gb {}", sys_ram_gb);
|
||||||
|
let mut disk_swap_gb = if sys_ram_gb >= 32 {
|
||||||
|
1u64
|
||||||
|
} else if sys_ram_gb >= 24 {
|
||||||
|
2u64
|
||||||
|
} else if sys_ram_gb >= 16 {
|
||||||
|
sys_ram_gb / 2
|
||||||
|
} else if sys_ram_gb >= 8 {
|
||||||
|
sys_ram_gb
|
||||||
|
} else {
|
||||||
|
sys_ram_gb * 2
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.use_zram {
|
||||||
|
disk_swap_gb = disk_swap_gb.saturating_sub(sys_ram_gb).max(2);
|
||||||
|
}
|
||||||
|
let disk_swap = KiraSize::new_gb(disk_swap_gb);
|
||||||
|
|
||||||
|
match self.swap_mode {
|
||||||
|
Some(SwapMode::NoSwap) => KiraSize::new_b(0),
|
||||||
|
Some(SwapMode::SwapHibernate) => disk_swap + total_ram,
|
||||||
|
Some(SwapMode::SwapNoHibernate) => disk_swap,
|
||||||
|
_ => KiraSize::new_gb(2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_layout(&mut self) {
|
||||||
|
if let Some(dev) = self.device.clone() {
|
||||||
|
let swap_size: KiraSize = self.get_swap_size();
|
||||||
|
self.generated_disk_parts = Some(
|
||||||
|
PartLayout::gen_classic_layout(dev.clone(), KiraSize::new_mb(512), swap_size)
|
||||||
|
.part_list
|
||||||
|
.iter()
|
||||||
|
.map(|part_info| part_to_ct_params(part_info, dev.size))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_result(&self) -> StageResult {
|
||||||
|
let swap_size: KiraSize = self.get_swap_size();
|
||||||
|
let dev_name = self
|
||||||
|
.device
|
||||||
|
.clone()
|
||||||
|
.expect("Device should be selected!")
|
||||||
|
.name;
|
||||||
|
StageResult::new("partition")
|
||||||
|
.add_val_string("device", dev_name)
|
||||||
|
.add_val_string(
|
||||||
|
"swap_mode",
|
||||||
|
self.swap_mode
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(SwapMode::NoSwap)
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.add_val_u64("swap_size_mb", swap_size.mb())
|
||||||
|
.add_val_bool("use_zram", self.use_zram)
|
||||||
|
.add_val_bool("secure_boot", self.secure_boot)
|
||||||
|
.add_val_bool("encrypt_drive", self.encrypt_drive)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, message: Message) -> StageAction {
|
||||||
|
match message {
|
||||||
|
Message::SelectDevice(dev) => {
|
||||||
|
self.selected_disk_parts = match PartLayout::read_from_disk(&dev) {
|
||||||
|
Ok(dev_parts) => Some(
|
||||||
|
dev_parts
|
||||||
|
.part_list
|
||||||
|
.iter()
|
||||||
|
.map(|part_inf| part_to_ct_params(&part_inf, dev.size))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
Err(ex) => {
|
||||||
|
log::error!("Error getting dev partitions info: {}", ex);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let swap_size: KiraSize = self.get_swap_size();
|
||||||
|
|
||||||
|
self.generated_disk_parts = Some(
|
||||||
|
PartLayout::gen_classic_layout(dev.clone(), KiraSize::new_mb(128), swap_size)
|
||||||
|
.part_list
|
||||||
|
.iter()
|
||||||
|
.map(|part_info| part_to_ct_params(part_info, dev.size))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.device = Some(dev);
|
||||||
|
StageAction::None
|
||||||
|
},
|
||||||
|
Message::SelectSwapMode(sm) => {
|
||||||
|
self.swap_mode = Some(sm);
|
||||||
|
self.gen_layout();
|
||||||
|
StageAction::None
|
||||||
|
},
|
||||||
|
Message::ToggleSecureBoot(t) => {
|
||||||
|
self.secure_boot = t;
|
||||||
|
StageAction::None
|
||||||
|
},
|
||||||
|
Message::ToggleEncrypDrive(t) => {
|
||||||
|
self.encrypt_drive = t;
|
||||||
|
StageAction::None
|
||||||
|
},
|
||||||
|
Message::ToggleZram(t) => {
|
||||||
|
self.use_zram = t;
|
||||||
|
self.gen_layout();
|
||||||
|
StageAction::None
|
||||||
|
},
|
||||||
|
Message::SwapSizeIncrement => {
|
||||||
|
self.custom_swap_size_mb = (self.custom_swap_size_mb / 1024u32)
|
||||||
|
.strict_mul(1024u32)
|
||||||
|
.saturating_add(1024u32);
|
||||||
|
self.custom_swap_size_str = format!("{}MB", self.custom_swap_size_mb);
|
||||||
|
self.gen_layout();
|
||||||
|
StageAction::None
|
||||||
|
},
|
||||||
|
Message::SwapSizeDecrement => {
|
||||||
|
self.custom_swap_size_mb = (self.custom_swap_size_mb / 1024u32)
|
||||||
|
.strict_mul(1024u32)
|
||||||
|
.saturating_sub(1024u32);
|
||||||
|
self.custom_swap_size_str = format!("{}MB", self.custom_swap_size_mb);
|
||||||
|
self.gen_layout();
|
||||||
|
StageAction::None
|
||||||
|
},
|
||||||
|
Message::SwapSizeFieldChange(f_v) => {
|
||||||
|
let mut only_num = f_v.trim().to_string();
|
||||||
|
only_num.retain(char::is_numeric);
|
||||||
|
self.custom_swap_size_mb = u32::from_str_radix(&only_num, 10).unwrap_or(1024);
|
||||||
|
self.custom_swap_size_str = format!("{}MB", self.custom_swap_size_mb);
|
||||||
|
self.gen_layout();
|
||||||
|
StageAction::None
|
||||||
|
},
|
||||||
|
Message::Back => StageAction::Back,
|
||||||
|
Message::Next => StageAction::Next(self.gen_result()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&self) -> iced::Element<'_, Message> {
|
||||||
|
let swap_size_select: Element<'_, _> = match self.swap_mode {
|
||||||
|
Some(SwapMode::SwapCustom) => widget::row![
|
||||||
|
widget::text_input("", &self.custom_swap_size_str)
|
||||||
|
.width(100)
|
||||||
|
.on_input(Message::SwapSizeFieldChange),
|
||||||
|
widget::button("+").on_press(Message::SwapSizeIncrement),
|
||||||
|
widget::button("-").on_press(Message::SwapSizeDecrement),
|
||||||
|
]
|
||||||
|
.spacing(2)
|
||||||
|
.into(),
|
||||||
|
_ => widget::space().into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let dev_parts_text = match self.generated_disk_parts {
|
||||||
|
Some(_) => widget::text(t!("partition.current_after_install")),
|
||||||
|
None => widget::text(""),
|
||||||
|
};
|
||||||
|
let dev_parts_content = kira_color_bar::color_bar(self.generated_disk_parts.clone(), 64);
|
||||||
|
let dev_parts_content = widget::column![dev_parts_text, dev_parts_content];
|
||||||
|
|
||||||
|
let back_button = widget::button(widget::text(t!("button.back"))).on_press(Message::Back);
|
||||||
|
let next_button = if self.device.is_some() {
|
||||||
|
widget::button(widget::text(t!("button.next"))).on_press(Message::Next)
|
||||||
|
} else {
|
||||||
|
widget::button(widget::text(t!("button.next")))
|
||||||
|
};
|
||||||
|
|
||||||
|
let selected_dev_text = match self.selected_disk_parts {
|
||||||
|
Some(_) => widget::text(t!("partition.current_dev_layout")),
|
||||||
|
None => widget::text(""),
|
||||||
|
};
|
||||||
|
let selected_dev_content = kira_color_bar::color_bar(self.selected_disk_parts.clone(), 64);
|
||||||
|
let selected_dev_content = widget::column![selected_dev_text, selected_dev_content];
|
||||||
|
|
||||||
|
let use_sec_boot_checkbox = if self.secure_boot_in_setup_mode {
|
||||||
|
widget::checkbox(self.secure_boot).label(t!("partition.use_secure_boot"))
|
||||||
|
.on_toggle(Message::ToggleSecureBoot)
|
||||||
|
}
|
||||||
|
else {widget::checkbox(self.secure_boot).label(t!("partition.use_secure_boot"))};
|
||||||
|
|
||||||
|
widget::column![
|
||||||
|
widget::container(
|
||||||
|
widget::column![
|
||||||
|
widget::rule::horizontal(2),
|
||||||
|
widget::text(t!("partition.select_device")),
|
||||||
|
widget::pick_list(
|
||||||
|
self.devices.clone(),
|
||||||
|
self.device.clone(),
|
||||||
|
Message::SelectDevice,
|
||||||
|
)
|
||||||
|
.placeholder(t!("partition.select_device_placeholder")),
|
||||||
|
widget::rule::horizontal(2),
|
||||||
|
widget::text(t!("partition.select_swapmode")),
|
||||||
|
widget::row![
|
||||||
|
widget::pick_list(SWAP_MODES, self.swap_mode, Message::SelectSwapMode,)
|
||||||
|
.placeholder(t!("partition.select_swapmode_placeholder")),
|
||||||
|
swap_size_select
|
||||||
|
]
|
||||||
|
.spacing(5)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.align_y(Alignment::Center),
|
||||||
|
widget::checkbox(self.use_zram)
|
||||||
|
.label(t!("partition.use_zram"))
|
||||||
|
.on_toggle(Message::ToggleZram),
|
||||||
|
widget::rule::horizontal(2),
|
||||||
|
use_sec_boot_checkbox,
|
||||||
|
widget::rule::horizontal(2),
|
||||||
|
widget::checkbox(self.encrypt_drive)
|
||||||
|
.label(t!("partition.encrypt_drive"))
|
||||||
|
.on_toggle(Message::ToggleEncrypDrive),
|
||||||
|
widget::rule::horizontal(2),
|
||||||
|
selected_dev_content,
|
||||||
|
dev_parts_content,
|
||||||
|
]
|
||||||
|
.padding(10)
|
||||||
|
.spacing(10)
|
||||||
|
.align_x(Alignment::Start)
|
||||||
|
)
|
||||||
|
.height(iced::Length::Fill)
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.align_x(Alignment::Start)
|
||||||
|
.align_y(Alignment::Center),
|
||||||
|
widget::row![
|
||||||
|
back_button,
|
||||||
|
widget::space::horizontal(), // Pushes the right button to the far right
|
||||||
|
next_button
|
||||||
|
]
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.align_y(Alignment::End)
|
||||||
|
.padding(10),
|
||||||
|
]
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.spacing(10)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
// <Kira Installer - universal Linux installer.>
|
||||||
|
// Copyright (C) <2026> <Kira Foundation>
|
||||||
|
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is Security stage, setup users logins and such
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::stage::{StageAction, StageResult};
|
||||||
|
use iced::{Alignment, Color, widget};
|
||||||
|
use rust_i18n::t;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
enum PasswordStrenght {
|
||||||
|
Low,
|
||||||
|
Middle,
|
||||||
|
Hight,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PasswordStrenght {
|
||||||
|
pub fn from_password(password: &str) -> Self {
|
||||||
|
if password.len() < 8 {
|
||||||
|
Self::Low
|
||||||
|
} else if password.len() < 16 {
|
||||||
|
Self::Middle
|
||||||
|
} else {
|
||||||
|
Self::Hight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn color(&self) -> Color {
|
||||||
|
match self {
|
||||||
|
Self::Low => Color::from_rgb8(212, 32, 32),
|
||||||
|
Self::Middle => Color::from_rgb8(212, 212, 32),
|
||||||
|
Self::Hight => Color::from_rgb8(32, 212, 32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for PasswordStrenght {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Low => f.write_str(t!("security.pass_low").as_ref()),
|
||||||
|
Self::Middle => f.write_str(t!("security.pass_middle").as_ref()),
|
||||||
|
Self::Hight => f.write_str(t!("security.pass_hight").as_ref()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SecurityStage {
|
||||||
|
user_name: String,
|
||||||
|
user_full_name: String,
|
||||||
|
user_password: String,
|
||||||
|
root_password: String,
|
||||||
|
reuse_user_password_for_roor: bool,
|
||||||
|
user_pass_is_secure: bool,
|
||||||
|
root_pass_is_secure: bool,
|
||||||
|
user_password_strenght: PasswordStrenght,
|
||||||
|
root_password_strenght: PasswordStrenght,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
ToggleReusePass(bool),
|
||||||
|
UserNameChange(String),
|
||||||
|
UserFullNameChange(String),
|
||||||
|
UserPasswordChange(String),
|
||||||
|
RootPasswordChange(String),
|
||||||
|
ToggleUserPassSecure,
|
||||||
|
ToggleRootPassSecure,
|
||||||
|
Next,
|
||||||
|
Back,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mothod safely retain first n chars in utf-8 string avoiding cutting utf-8 char in the middle
|
||||||
|
fn retain_first_n(s: &str, n: usize) -> &str {
|
||||||
|
s.char_indices()
|
||||||
|
.nth(n)
|
||||||
|
.and_then(|(idx, _)| Some(&s[..idx]))
|
||||||
|
.unwrap_or(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SecurityStage {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
user_name: "".into(),
|
||||||
|
user_full_name: "".into(),
|
||||||
|
user_password: "".into(),
|
||||||
|
root_password: "".into(),
|
||||||
|
reuse_user_password_for_roor: false,
|
||||||
|
user_pass_is_secure: true,
|
||||||
|
root_pass_is_secure: true,
|
||||||
|
user_password_strenght: PasswordStrenght::Low,
|
||||||
|
root_password_strenght: PasswordStrenght::Low,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_result(&self) -> StageResult {
|
||||||
|
StageResult::new("security")
|
||||||
|
.add_val_string("user_name", self.user_name.clone())
|
||||||
|
.add_val_string("user_full_name", self.user_full_name.clone())
|
||||||
|
.add_val_string("user_full_name", self.user_full_name.clone())
|
||||||
|
.add_val_string("user_password", self.user_password.clone())
|
||||||
|
.add_val_string("root_password", self.root_password.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, message: Message) -> StageAction {
|
||||||
|
match message {
|
||||||
|
Message::ToggleReusePass(reuse_pass) => {
|
||||||
|
self.reuse_user_password_for_roor = reuse_pass;
|
||||||
|
if reuse_pass {
|
||||||
|
self.root_password = self.user_password.clone();
|
||||||
|
}
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::UserNameChange(u_name) => {
|
||||||
|
let mut trimmed = u_name
|
||||||
|
.trim_start_matches(|c: char| !c.is_ascii_alphabetic())
|
||||||
|
.to_string();
|
||||||
|
trimmed.retain(|c| c.is_ascii_alphanumeric() || c == '-');
|
||||||
|
trimmed.truncate(30); // it is safe bacuse we retained only ascii characters
|
||||||
|
self.user_name = trimmed;
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::UserFullNameChange(f_name) => {
|
||||||
|
let mut res = f_name.clone();
|
||||||
|
res.retain(|c| c.is_alphanumeric() || ['_', '-', ' ', '.', ','].contains(&c));
|
||||||
|
self.user_full_name = retain_first_n(&res, 128).to_string();
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::UserPasswordChange(u_pass) => {
|
||||||
|
let mut res = u_pass;
|
||||||
|
res.retain(|c: char| !c.is_control());
|
||||||
|
self.user_password = res;
|
||||||
|
self.user_password_strenght = PasswordStrenght::from_password(&self.user_password);
|
||||||
|
if self.reuse_user_password_for_roor {
|
||||||
|
self.root_password_strenght = self.user_password_strenght;
|
||||||
|
self.root_password = self.user_password.clone();
|
||||||
|
}
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::RootPasswordChange(r_pass) => {
|
||||||
|
let mut res: String = r_pass;
|
||||||
|
res.retain(|c: char| !c.is_control());
|
||||||
|
self.root_password = res;
|
||||||
|
self.root_password_strenght = PasswordStrenght::from_password(&self.root_password);
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::ToggleUserPassSecure => {
|
||||||
|
self.user_pass_is_secure = !self.user_pass_is_secure;
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::ToggleRootPassSecure => {
|
||||||
|
self.root_pass_is_secure = !self.root_pass_is_secure;
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::Back => StageAction::Back,
|
||||||
|
Message::Next => StageAction::Next(self.gen_result()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&self) -> iced::Element<'_, Message> {
|
||||||
|
let back_button = widget::button(widget::text(t!("button.back"))).on_press(Message::Back);
|
||||||
|
let next_button = if self.user_name.is_empty()
|
||||||
|
|| self.user_password.is_empty()
|
||||||
|
|| self.root_password.is_empty()
|
||||||
|
{
|
||||||
|
widget::button(widget::text(t!("button.next")))
|
||||||
|
} else {
|
||||||
|
widget::button(widget::text(t!("button.next"))).on_press(Message::Next)
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_pass_sec_text = if self.user_pass_is_secure {
|
||||||
|
widget::text("👁")
|
||||||
|
} else {
|
||||||
|
widget::text("*")
|
||||||
|
};
|
||||||
|
let root_pass_sec_text = if self.root_pass_is_secure {
|
||||||
|
widget::text("👁")
|
||||||
|
} else {
|
||||||
|
widget::text("*")
|
||||||
|
};
|
||||||
|
let user_pass_sec_text = user_pass_sec_text
|
||||||
|
.width(12)
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.align_y(Alignment::Center);
|
||||||
|
let root_pass_sec_text = root_pass_sec_text
|
||||||
|
.width(12)
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.align_y(Alignment::Center);
|
||||||
|
let mut root_pass_input =
|
||||||
|
widget::text_input("", &self.root_password).secure(self.root_pass_is_secure);
|
||||||
|
if !self.reuse_user_password_for_roor {
|
||||||
|
root_pass_input = root_pass_input.on_input(Message::RootPasswordChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
let user_p_strenght_text = widget::text(self.user_password_strenght.to_string())
|
||||||
|
.color(self.user_password_strenght.color());
|
||||||
|
let root_p_strenght_text = widget::text(self.root_password_strenght.to_string())
|
||||||
|
.color(self.root_password_strenght.color());
|
||||||
|
|
||||||
|
widget::column![
|
||||||
|
widget::container(
|
||||||
|
widget::column![
|
||||||
|
widget::column![
|
||||||
|
widget::text(t!("security.user_name")),
|
||||||
|
widget::text_input("", &self.user_name)
|
||||||
|
.width(256)
|
||||||
|
.on_input(Message::UserNameChange)
|
||||||
|
],
|
||||||
|
widget::column![
|
||||||
|
widget::text(t!("security.user_full_name")),
|
||||||
|
widget::text_input("", &self.user_full_name)
|
||||||
|
.width(256)
|
||||||
|
.on_input(Message::UserFullNameChange)
|
||||||
|
],
|
||||||
|
widget::column![
|
||||||
|
widget::text(t!("security.user_password")),
|
||||||
|
widget::row![
|
||||||
|
widget::text_input("", &self.user_password)
|
||||||
|
.width(512)
|
||||||
|
.on_input(Message::UserPasswordChange)
|
||||||
|
.secure(self.user_pass_is_secure),
|
||||||
|
widget::button(user_pass_sec_text)
|
||||||
|
.on_press(Message::ToggleUserPassSecure),
|
||||||
|
widget::container(user_p_strenght_text)
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.style(widget::container::bordered_box)
|
||||||
|
.style(widget::container::rounded_box)
|
||||||
|
.padding(6)
|
||||||
|
.width(160),
|
||||||
|
]
|
||||||
|
.spacing(5)
|
||||||
|
.align_y(Alignment::Center)
|
||||||
|
],
|
||||||
|
widget::rule::horizontal(2),
|
||||||
|
widget::column![
|
||||||
|
widget::text(t!("security.root_password")),
|
||||||
|
widget::row![
|
||||||
|
root_pass_input.width(512),
|
||||||
|
widget::button(root_pass_sec_text)
|
||||||
|
.on_press(Message::ToggleRootPassSecure),
|
||||||
|
widget::container(root_p_strenght_text)
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.style(widget::container::rounded_box)
|
||||||
|
.style(widget::container::bordered_box)
|
||||||
|
.padding(6)
|
||||||
|
.width(160),
|
||||||
|
]
|
||||||
|
.spacing(5)
|
||||||
|
.align_y(Alignment::Center),
|
||||||
|
widget::checkbox(self.reuse_user_password_for_roor)
|
||||||
|
.label(t!("security.reuse_user_password"))
|
||||||
|
.on_toggle(Message::ToggleReusePass),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
.padding(10)
|
||||||
|
.spacing(10)
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
)
|
||||||
|
.height(iced::Length::Fill)
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.align_y(Alignment::Center),
|
||||||
|
widget::row![
|
||||||
|
back_button,
|
||||||
|
widget::space::horizontal(), // Pushes the right button to the far right
|
||||||
|
next_button,
|
||||||
|
]
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.align_y(Alignment::End)
|
||||||
|
.padding(10),
|
||||||
|
]
|
||||||
|
.align_x(Alignment::Center)
|
||||||
|
.spacing(10)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-35
@@ -18,12 +18,13 @@
|
|||||||
This is TimeZone stage, used to select timezone
|
This is TimeZone stage, used to select timezone
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::stage::{ConfigValue, StageAction, StageResult};
|
use log;
|
||||||
|
use crate::stage::{StageAction, StageResult};
|
||||||
use iced::{Alignment, Length, widget};
|
use iced::{Alignment, Length, widget};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
mod scroll_list;
|
use crate::kira_scroll_list;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct TimeZoneData {
|
pub struct TimeZoneData {
|
||||||
@@ -61,8 +62,8 @@ pub struct TimeZoneStage {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
SelectRegion(scroll_list::ListViewMessage),
|
SelectRegion(kira_scroll_list::ListViewMessage),
|
||||||
SelectZone(scroll_list::ListViewMessage),
|
SelectZone(kira_scroll_list::ListViewMessage),
|
||||||
Next,
|
Next,
|
||||||
Back,
|
Back,
|
||||||
}
|
}
|
||||||
@@ -127,7 +128,7 @@ impl TimeZoneStage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ex) => {
|
Err(ex) => {
|
||||||
println!(
|
log::error!(
|
||||||
"Exception while trying to get time zones list from system {}",
|
"Exception while trying to get time zones list from system {}",
|
||||||
ex
|
ex
|
||||||
);
|
);
|
||||||
@@ -146,38 +147,18 @@ impl TimeZoneStage {
|
|||||||
|
|
||||||
fn gen_result(&self) -> StageResult {
|
fn gen_result(&self) -> StageResult {
|
||||||
if let Some(time_zone) = &self.selected_zone {
|
if let Some(time_zone) = &self.selected_zone {
|
||||||
StageResult {
|
StageResult::new("time_zone")
|
||||||
name: "time_zone".to_string(),
|
.add_val_string("name", time_zone.to_string())
|
||||||
config: Some(HashMap::from([
|
.add_val_string("region", time_zone.region.clone())
|
||||||
(
|
.add_val_string("zone", time_zone.zone.clone())
|
||||||
"name".to_string(),
|
|
||||||
ConfigValue::String(time_zone.to_string()),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"region".to_string(),
|
|
||||||
ConfigValue::String(time_zone.region.clone()),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"zone".to_string(),
|
|
||||||
ConfigValue::String(time_zone.zone.clone()),
|
|
||||||
),
|
|
||||||
])),
|
|
||||||
resuts: None,
|
|
||||||
error: None,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
StageResult {
|
StageResult::new("time_zone")
|
||||||
name: "time_zone".to_string(),
|
|
||||||
config: None,
|
|
||||||
resuts: None,
|
|
||||||
error: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, message: Message) -> StageAction {
|
pub fn update(&mut self, message: Message) -> StageAction {
|
||||||
match message {
|
match message {
|
||||||
Message::SelectRegion(scroll_list::ListViewMessage::Select(region_id)) => {
|
Message::SelectRegion(kira_scroll_list::ListViewMessage::Select(region_id)) => {
|
||||||
let region = self.regions.get(region_id).unwrap();
|
let region = self.regions.get(region_id).unwrap();
|
||||||
self.zones = self
|
self.zones = self
|
||||||
.time_zones
|
.time_zones
|
||||||
@@ -191,7 +172,7 @@ impl TimeZoneStage {
|
|||||||
//self.selected_zone_text.clear();
|
//self.selected_zone_text.clear();
|
||||||
StageAction::None
|
StageAction::None
|
||||||
}
|
}
|
||||||
Message::SelectZone(scroll_list::ListViewMessage::Select(zone_id)) => {
|
Message::SelectZone(kira_scroll_list::ListViewMessage::Select(zone_id)) => {
|
||||||
let zone = TimeZoneData {
|
let zone = TimeZoneData {
|
||||||
region: self.regions[self.region_id.unwrap()].clone(),
|
region: self.regions[self.region_id.unwrap()].clone(),
|
||||||
zone: self.zones[zone_id].clone(),
|
zone: self.zones[zone_id].clone(),
|
||||||
@@ -218,12 +199,12 @@ impl TimeZoneStage {
|
|||||||
widget::column![
|
widget::column![
|
||||||
widget::text(t!("timezone.select_timezone")),
|
widget::text(t!("timezone.select_timezone")),
|
||||||
widget::row![
|
widget::row![
|
||||||
scroll_list::list_view(&self.regions, self.region_id, Length::Shrink)
|
kira_scroll_list::list_view(&self.regions, self.region_id, Length::Shrink)
|
||||||
.map(Message::SelectRegion),
|
.map(Message::SelectRegion),
|
||||||
scroll_list::list_view(&self.zones, self.zone_id, Length::FillPortion(1))
|
kira_scroll_list::list_view(&self.zones, self.zone_id, Length::FillPortion(1))
|
||||||
.map(Message::SelectZone),
|
.map(Message::SelectZone),
|
||||||
]
|
]
|
||||||
.padding(20)
|
.padding([0,10])
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.height(Length::Shrink),
|
.height(Length::Shrink),
|
||||||
widget::text(self.selected_zone_text.clone())
|
widget::text(self.selected_zone_text.clone())
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use iced::widget::{Column, button, container, scrollable, text};
|
|
||||||
use iced::{Alignment, Color, Element, Length, Theme};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ListViewMessage {
|
|
||||||
Select(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unselected_button_style(theme: &Theme, status: button::Status) -> button::Style {
|
|
||||||
//let palette = theme.extended_palette();
|
|
||||||
use iced::{Border, border::Radius};
|
|
||||||
|
|
||||||
match status {
|
|
||||||
button::Status::Hovered => button::Style {
|
|
||||||
border: Border {
|
|
||||||
color: Color::from_rgb(0.8, 0.3, 0.3),
|
|
||||||
width: 2.0,
|
|
||||||
radius: Radius::new(5.0),
|
|
||||||
},
|
|
||||||
..button::primary(theme, status)
|
|
||||||
},
|
|
||||||
button::Status::Active => button::Style {
|
|
||||||
background: Some(iced::Background::Color(crate::kira_theming::change_lightnes(
|
|
||||||
&theme.palette().primary,
|
|
||||||
-0.1,
|
|
||||||
))),
|
|
||||||
..button::primary(theme, status)
|
|
||||||
},
|
|
||||||
_ => button::primary(theme, status),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected_button_style(theme: &Theme, status: button::Status) -> button::Style {
|
|
||||||
//let palette = theme.extended_palette();
|
|
||||||
|
|
||||||
match status {
|
|
||||||
button::Status::Active => button::Style {
|
|
||||||
background: Some(iced::Background::Color(Color::from_rgb(0.8, 0.3, 0.3))),
|
|
||||||
..button::primary(theme, status)
|
|
||||||
},
|
|
||||||
_ => button::Style {
|
|
||||||
background: Some(iced::Background::Color(Color::from_rgb(0.8, 0.3, 0.3))),
|
|
||||||
..button::primary(theme, status)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn view() -> container::Style {
|
|
||||||
// container::Style {
|
|
||||||
// text_color: None,
|
|
||||||
// background: None,
|
|
||||||
// border: Border {
|
|
||||||
// color: Color::BLACK,
|
|
||||||
// width: 2.0,
|
|
||||||
// radius: Radius::new(5.0),
|
|
||||||
// },
|
|
||||||
// shadow: None,
|
|
||||||
// snap: false,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn border_style(theme: &Theme) -> container::Style {
|
|
||||||
// use iced::{Border, border::Radius};
|
|
||||||
|
|
||||||
// container::Style {
|
|
||||||
// border: Border {
|
|
||||||
// color: Color::from_rgb(0.8, 0.3, 0.3),
|
|
||||||
// width: 2.0,
|
|
||||||
// radius: Radius::new(5.0),
|
|
||||||
// },
|
|
||||||
// ..container::bordered_box(theme)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Assuming your Item struct or type implements Display or has a text representation
|
|
||||||
pub fn list_view<T: Display>(
|
|
||||||
items: &Vec<T>,
|
|
||||||
selected_id: Option<usize>,
|
|
||||||
container_height: Length,
|
|
||||||
) -> Element<'_, ListViewMessage> {
|
|
||||||
let mut column = Column::new()
|
|
||||||
.spacing(2)
|
|
||||||
.align_x(Alignment::Center)
|
|
||||||
.width(Length::Fill);
|
|
||||||
|
|
||||||
for (index, value) in items.iter().enumerate() {
|
|
||||||
// Create a row for each item, possibly with buttons or other controls
|
|
||||||
let list_item = if let Some(id) = selected_id
|
|
||||||
&& id == index
|
|
||||||
{
|
|
||||||
button(text(value.to_string())).style(selected_button_style)
|
|
||||||
} else {
|
|
||||||
button(text(value.to_string())).style(unselected_button_style)
|
|
||||||
};
|
|
||||||
let list_item = list_item
|
|
||||||
.width(Length::Fill)
|
|
||||||
.on_press(ListViewMessage::Select(index));
|
|
||||||
|
|
||||||
column = column.push(list_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap the column in a scrollable widget
|
|
||||||
container(scrollable(column).width(Length::Fill))
|
|
||||||
.style(container::bordered_box)
|
|
||||||
.height(container_height)
|
|
||||||
.padding(8)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
@@ -20,10 +20,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
use crate::{stage::{ConfigValue, StageAction, StageResult}, kira_theming};
|
use crate::{stage::{StageAction, StageResult}, kira_theming};
|
||||||
use iced::{Alignment, widget};
|
use iced::{Alignment, widget};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct LocData {
|
pub struct LocData {
|
||||||
@@ -70,37 +69,13 @@ impl WelcomeStage {
|
|||||||
|
|
||||||
fn gen_result(&self) -> StageResult {
|
fn gen_result(&self) -> StageResult {
|
||||||
if let Some(loc) = &self.locale {
|
if let Some(loc) = &self.locale {
|
||||||
StageResult {
|
StageResult::new("welcome")
|
||||||
name: "welcome".to_string(),
|
.add_val_string("loc_code", loc.code.clone())
|
||||||
config: Some(HashMap::from([
|
.add_val_string("loc_name", loc.name.clone())
|
||||||
(
|
|
||||||
"loc_code".to_string(),
|
|
||||||
ConfigValue::String(loc.code.clone()),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"loc_name".to_string(),
|
|
||||||
ConfigValue::String(loc.name.clone()),
|
|
||||||
),
|
|
||||||
])),
|
|
||||||
resuts: None,
|
|
||||||
error: None,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
StageResult {
|
StageResult::new("welcome")
|
||||||
name: "welcome".to_string(),
|
.add_val_string("loc_code", "en".to_string())
|
||||||
config: Some(HashMap::from([
|
.add_val_string("loc_name", "English".to_string())
|
||||||
(
|
|
||||||
"loc_code".to_string(),
|
|
||||||
ConfigValue::String("en".to_string()),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"loc_name".to_string(),
|
|
||||||
ConfigValue::String("English".to_string()),
|
|
||||||
),
|
|
||||||
])),
|
|
||||||
resuts: None,
|
|
||||||
error: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user