From f851c6fef07976e2ad6a7739528eff87f427a9bc1bdc9fb4820b1d3146c68a70 Mon Sep 17 00:00:00 2001 From: Kira Date: Mon, 11 May 2026 23:01:54 +0200 Subject: [PATCH] More work on partition stage. --- src/kira_config.toml | 3 + src/kira_disk_layout.rs | 76 ++++++++++++ src/locales/en.json | 10 +- src/main.rs | 13 ++- src/stages/partition/mod.rs | 222 +++++++++++++++++++++++++++++------- 5 files changed, 280 insertions(+), 44 deletions(-) create mode 100644 src/kira_disk_layout.rs diff --git a/src/kira_config.toml b/src/kira_config.toml index feb6b74..eb82653 100644 --- a/src/kira_config.toml +++ b/src/kira_config.toml @@ -1,3 +1,6 @@ [network] reqire_network = true + +[partition] +min_disk_size_mb = 8192 diff --git a/src/kira_disk_layout.rs b/src/kira_disk_layout.rs new file mode 100644 index 0000000..3ea399f --- /dev/null +++ b/src/kira_disk_layout.rs @@ -0,0 +1,76 @@ +use std::fmt::Display; +use std::collections::HashMap; +use iced::widget::{Column, button, container, scrollable, text}; +use iced::{Alignment, Color, Element, Length, Theme}; + + +fn colors_map() -> HashMap { + HashMap::from([ + ("vfat".into(), Color::from_rgb8(204,121,167)), + ("xfs".into(), Color::from_rgb8(0,158,115)), + ("crypto_LUKS".into(), Color::from_rgb8(240,228,66)), + ("swap".into(), Color::from_rgb8(213,94,0)), + ("ext4".into(), Color::from_rgb8(0,114,178)), + ("btrfs".into(), Color::from_rgb8(86,180,233)), + ("other".into(), Color::from_rgb8(230,159,0)), + ]) +} + + +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( + items: &Vec<(T, f32, Color)>, +) -> Element<'_> { + 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() +} diff --git a/src/locales/en.json b/src/locales/en.json index 19c2322..1c5d188 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -27,9 +27,13 @@ "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": "Do not create swap partition", + "partition.no_swap": "No swap partition", "partition.swap_no_hibernate": "Swap without hibernate", - "partition.swap_hibernate": "Swap partition with hibernation support", + "partition.swap_hibernate": "Swap with hibernation", "partition.select_device_placeholder": "Select Device", - "partition.select_device": "Select device for system installation" + "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" } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index f7b04a0..e47b494 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,7 @@ use crate::stages::welcome::WelcomeStage; rust_i18n::i18n!("src/locales", fallback = "en"); mod kira_theming; mod kira_scroll_list; +//mod kira_disk_layout; mod stage; mod stages; @@ -230,8 +231,16 @@ fn update(k_state: &mut KiraState, message: Message) -> Task { if let Views::Keyboard(keyboard_view) = &mut k_state.current_view { match keyboard_view.update(keyboard_msg) { StageAction::Next(keyboard_res) => { - k_state.config.config_trail.push(keyboard_res); - k_state.current_view = Views::Partition(stages::partition::PartitionStage::new()); + match stages::partition::PartitionStage::new(&k_state.toml_config) { + Ok(part_stage) => { + k_state.config.config_trail.push(keyboard_res); + k_state.current_view = Views::Partition(part_stage); + }, + Err(ex) => { + println!("Error, cannot init partition stage: {}", ex); + } + + } Task::none() } StageAction::Back => { diff --git a/src/stages/partition/mod.rs b/src/stages/partition/mod.rs index cb13dbc..cb1dd06 100644 --- a/src/stages/partition/mod.rs +++ b/src/stages/partition/mod.rs @@ -18,18 +18,13 @@ This is partition stage! */ -use crate::{ - kira_theming, - stage::{ConfigValue, StageAction, StageResult}, -}; -use iced::{ - Alignment, - widget::{self, text::Format}, -}; +use crate::stage::{ConfigValue, StageAction, StageResult}; + +use iced::{Alignment, Color, Length, widget::{self, container}}; use rust_i18n::t; use std::collections::HashMap; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SwapMode { NoSwap, SwapNoHibernate, @@ -46,18 +41,49 @@ impl std::fmt::Display for SwapMode { } } +const SWAP_MODES: [SwapMode; 3] = [ + SwapMode::NoSwap, + SwapMode::SwapNoHibernate, + SwapMode::SwapHibernate, +]; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BlkDev { + name: String, + size_mb: u64, +} + +impl std::fmt::Display for BlkDev { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} ({}MB)", &self.name, self.size_mb) + } +} + +impl BlkDev { + pub fn from_lsblk_out_vec(v: &Vec) -> Self { + let name = v[0].clone(); + let size = u64::from_str_radix(&v[3], 10).unwrap_or(0) / 1048576; + Self { + name: name, + size_mb: size, + } + } +} + #[derive(Debug, Clone)] pub struct PartitionStage { - device: Option, - devices: Vec, + device: Option, + devices: Vec, swap_mode: Option, use_zram: bool, secure_boot: bool, + min_disk_size_mb: u64, + selected_disk_partitions: Option>, } #[derive(Debug, Clone)] pub enum Message { - SelectDevice(String), + SelectDevice(BlkDev), SelectSwapMode(SwapMode), ToggleZram(bool), ToggleSecureBoot(bool), @@ -65,10 +91,11 @@ pub enum Message { Back, } -pub fn get_disks_info_blocking() -> Result, String> { +/// returns list of system block devices with their size in MB +pub fn get_disks_info_blocking() -> Result, String> { use std::process::Command; - match Command::new("lsblk").args(["-n", "-d"]).output() { + match Command::new("lsblk").args(["-n", "-d", "-b"]).output() { Ok(cmd_output) => { if cmd_output.status.success() { match String::from_utf8(cmd_output.stdout) { @@ -80,7 +107,7 @@ pub fn get_disks_info_blocking() -> Result, String> { .collect::>() }) .filter(|l_v| l_v.len() == 6) - .map(|dev_params| dev_params[0].clone()) + .map(|dev_params| BlkDev::from_lsblk_out_vec(&dev_params)) .collect()), Err(ex) => Err(format!( "Exception while converting disks info to UTF8 {}", @@ -98,27 +125,96 @@ pub fn get_disks_info_blocking() -> Result, String> { } } + +pub fn get_dev_part_blocking(dev_name: &str) -> Result, String> { + + fn part_from_vect(v: &Vec) -> (String, String, u64) { + let name = v[0].clone(); + let size_mb = u64::from_str_radix(&v[1], 10).unwrap_or(0) / 1048576; + let part_type = v[2].clone(); + (name, part_type, size_mb) + } + + use std::process::Command; + + //"lsblk -o NAME,SIZE,FSTYPE -l -n /dev/nvme0n1" + + match Command::new("lsblk").args(["-o", "NAME,SIZE,FSTYPE,TYPE", "-l", "-n", "-b", 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() + .map(str::to_string) + .collect::>() + }) + .map(|v| {println!("{:?}", &v); v}) + .filter(|l_v| l_v.len() ==4 && l_v[3] == "part") + .map(|l_v| part_from_vect(&l_v)) + .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()), + } +} + +fn part_colors_map() -> HashMap { + HashMap::from([ + ("vfat".into(), Color::from_rgb8(204,121,167)), + ("xfs".into(), Color::from_rgb8(0,158,115)), + ("crypto_LUKS".into(), Color::from_rgb8(240,228,66)), + ("swap".into(), Color::from_rgb8(213,94,0)), + ("ext4".into(), Color::from_rgb8(0,114,178)), + ("btrfs".into(), Color::from_rgb8(86,180,233)), + ("other".into(), Color::from_rgb8(230,159,0)), + ]) +} + +use toml::Table; impl PartitionStage { - pub fn new() -> Self { + pub fn new(toml_config: &Table) -> Result { let maybe_dev_list = get_disks_info_blocking(); + + 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_mb: u64 = min_disk_size_mb.try_into().unwrap_or(8192); + match maybe_dev_list { - Ok(dev_list) => Self { - device: None, - devices: dev_list, - swap_mode: Some(SwapMode::SwapNoHibernate), - use_zram: true, - secure_boot: false, - }, - Err(ex) => { - println!("{}", ex); - Self { + Ok(dev_list) => { + let acepted_dev: Vec = dev_list + .iter() + .filter(|v| v.size_mb > min_disk_size_mb) + .cloned() + .collect(); + + Ok(Self { device: None, - devices: Vec::new(), + devices: acepted_dev, swap_mode: Some(SwapMode::SwapNoHibernate), use_zram: true, secure_boot: false, - } + min_disk_size_mb: min_disk_size_mb, + selected_disk_partitions: None, + }) } + Err(ex) => Err(ex), } } @@ -128,7 +224,12 @@ impl PartitionStage { config: Some(HashMap::from([ ( "device".to_string(), - ConfigValue::String(self.device.clone().unwrap_or_else(|| "NONE".to_string())), + ConfigValue::String( + self.device + .as_ref() + .and_then(|v| Some(v.name.clone())) + .unwrap_or_else(|| "NONE".to_string()), + ), ), ( "swap_mode".to_string(), @@ -153,6 +254,20 @@ impl PartitionStage { pub fn update(&mut self, message: Message) -> StageAction { match message { Message::SelectDevice(dev) => { + + self.selected_disk_partitions = match get_dev_part_blocking(&dev.name) { + Ok(dev_parts) => Some(dev_parts.iter().map(|(p_name, p_type, p_size)| { + let fill_ratio: u16 = ((*p_size as f32 / dev.size_mb as f32) * 15.0) as u16; + let fill_ratio = fill_ratio.max(2); + let p_clor = part_colors_map()[p_type]; + let p_text = format!("{} {} {}", p_name, p_type, p_size); + (p_text, fill_ratio, p_clor)}).collect()), + Err(ex) => { + println!("Error getting dev partitions info: {}", ex); + None + } + }; + self.device = Some(dev); StageAction::None } @@ -177,23 +292,52 @@ impl PartitionStage { let back_button = widget::button(widget::text(t!("button.back"))).on_press(Message::Back); let next_button = widget::button(widget::text(t!("button.next"))).on_press(Message::Next); - // Embed the image bytes into the executable - let welcom_logo_handle = widget::image::Handle::from_bytes(kira_theming::get_logo_bytes()); + + + let mut selected_dev_content = widget::Row::new().height(48); + if let Some(dev_cont) = &self.selected_disk_partitions { + for part_info in dev_cont { + selected_dev_content = selected_dev_content.push( + widget::container(widget::text(&part_info.0).color(Color::BLACK)) + .height(Length::Fill).width(Length::FillPortion(part_info.1)) + .style(|_| container::background(part_info.2)) + .align_x(Alignment::Center).align_y(Alignment::Center) + ); + } + } widget::column![ widget::container( widget::column![ - widget::text(t!("partition.select_device")), - widget::pick_list( - self.devices.clone(), - self.device.clone(), - Message::SelectDevice, - ) - .placeholder(t!("partition.select_device_placeholder")) + widget::row![ + widget::pick_list( + self.devices.clone(), + self.device.clone(), + Message::SelectDevice, + ) + .placeholder(t!("partition.select_device_placeholder")), + widget::text(t!("partition.select_device")) + ] + .spacing(5) + .align_y(Alignment::Center), + widget::row![ + widget::pick_list(SWAP_MODES, self.swap_mode, Message::SelectSwapMode,) + .placeholder(t!("partition.select_swapmode_placeholder")), + widget::text(t!("partition.select_swapmode")) + ] + .spacing(5) + .align_y(Alignment::Center), + widget::checkbox(self.use_zram) + .label(t!("partition.use_zram")) + .on_toggle(Message::ToggleZram), + widget::checkbox(self.secure_boot) + .label(t!("partition.use_secure_boot")) + .on_toggle(Message::ToggleSecureBoot), + widget::container(selected_dev_content).style(widget::container::bordered_box), ] .padding(10) .spacing(10) - .align_x(Alignment::Center) + .align_x(Alignment::Start) ) .height(iced::Length::Fill) .width(iced::Length::Fill)