From 8b3f22909e7b40bc263222cae21a0bbcaa30a4686cb01604e63e602092b923ad Mon Sep 17 00:00:00 2001 From: Kira Date: Wed, 6 May 2026 20:39:26 +0200 Subject: [PATCH] Working on keyboard stage, closures! --- src/stages/keyboard/mod.rs | 252 ++++++++++++++++++++++++------------- src/stages/mod.rs | 1 + 2 files changed, 164 insertions(+), 89 deletions(-) diff --git a/src/stages/keyboard/mod.rs b/src/stages/keyboard/mod.rs index 7090ec2..f979f37 100644 --- a/src/stages/keyboard/mod.rs +++ b/src/stages/keyboard/mod.rs @@ -15,131 +15,205 @@ // along with this program. If not, see . /* - This is TimeZone stage, used to select timezone + This is Keyboar stage, used to select Keyboard Layouts */ +use crate::kira_scroll_list; use crate::stage::{ConfigValue, StageAction, StageResult}; use iced::{Alignment, Length, widget}; use rust_i18n::t; use std::collections::HashMap; -mod scroll_list; - #[derive(Debug, Clone, PartialEq, Eq)] -pub struct TimeZoneData { - region: String, - zone: String, +pub struct Layout { + layout: String, + description: String, } -impl TimeZoneData { - fn from_str(s: &str) -> Option { - let idx = s.find('/')?; - let (region, zone) = s.split_at(idx); - Some(Self { - region: region.into(), - zone: zone.trim_start_matches('/').into(), - }) +impl Layout { + pub fn from_str_touple(v: &(&str, &str)) -> Self { + Self { + layout: v.0.trim().to_string(), + description: v.1.trim().to_string(), + } } } -impl std::fmt::Display for TimeZoneData { +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Variant { + variant: String, + description: String, +} + +impl Variant { + pub fn from_str_touple(v: &(&str, &str)) -> Self { + Self { + variant: v.0.trim().to_string(), + description: v.1.trim().to_string(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct KeyboarLayout { + layout: Layout, + variant: Variant, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct KeyboarModel { + model: String, + description: String, +} +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.region, self.zone) + write!(f, "{}/{}", &self.layout.layout, &self.variant.variant) } } #[derive(Debug, Clone)] -pub struct TimeZoneStage { - time_zones: Result>, String>, - regions: Vec, - region_id: Option, - zones: Vec, - zone_id: Option, - selected_zone: Option, - selected_zone_text: String, +pub struct KeyboardStage { + models: Vec, + layouts: Vec, + variants_map: HashMap>, + variants: Vec, + model: Option, + layout: Option, + variant: Option, + keyboard_layouts: Vec, + selected_kbd_layout_id: Option, } #[derive(Debug, Clone)] pub enum Message { - SelectRegion(scroll_list::ListViewMessage), - SelectZone(scroll_list::ListViewMessage), + SelectKeyboardModel(kira_scroll_list::ListViewMessage), + SelectLayout(kira_scroll_list::ListViewMessage), + SelectVariant(kira_scroll_list::ListViewMessage), + + AddLayout(usize), + RemoveLayout(usize), + SelectAddedLayout(kira_scroll_list::ListViewMessage), + Next, Back, } -/// Getting list of awailable timezones from system. -fn get_timezones_blocking() -> Result, String> { - use std::process::Command; - //timedatectl list-timezones --no-pager - match Command::new("timedatectl") - .arg("list-timezones") - .arg("--no-pager") - .output() +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, Vec, HashMap>)> { + use std::fs::File; + use std::io::{BufReader, Read}; + + let mut file_content = String::new(); { - Ok(cmd_output) => { - if cmd_output.status.success() { - //Etc/UTC - match String::from_utf8(cmd_output.stdout) { - Ok(str_tz_list) => Ok(str_tz_list - .split("\n") - .filter(|sb| sb.contains('/')) - .filter_map(|str_t_zone| TimeZoneData::from_str(str_t_zone.trim())) - .collect::>()), - Err(ex) => Err(format!("Exception while converting to UTF8 {}", ex)), - } - } else { - Err(format!( - "Error getting timezones list: {}", - String::from_utf8(cmd_output.stderr).unwrap_or("UNKNOWN".into()) - )) - } - } - Err(ex) => Err(format!("Exception while trying to list time zones {}", ex)), + 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 = file_content.split("! ").map(str::to_string).collect(); + + + // Parsing models data + let models: Vec = 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 = 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> = HashMap::new(); + + 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))); + if maybe_key.is_none() { + variants.insert(v.0.to_string(), vec![v.1]); + } + }), + ) + }); + + Ok((models, layouts, variants)) } -impl TimeZoneStage { - pub fn new() -> Self { - match get_timezones_blocking() { - Ok(time_zones_data_list) => { - let mut time_zones_map: HashMap> = HashMap::new(); - for tz_data in &time_zones_data_list { - if let Some(zones) = time_zones_map.get_mut(&tz_data.region) { - zones.push(tz_data.zone.clone()); - } else { - time_zones_map.insert(tz_data.region.clone(), vec![tz_data.zone.clone()]); - } - } - // sort zones alphabetically - time_zones_map.values_mut().for_each(|v| v.sort()); - // sort region names alphabetically - let mut regions: Vec = time_zones_map.keys().cloned().collect(); - regions.sort(); +impl KeyboardStage { + pub fn new() -> Result { + match get_layouts_blocking() { + Ok((models, layouts, variants)) => { + - Self { - regions: regions, - zones: Vec::new(), - time_zones: Ok(time_zones_map), - region_id: None, - zone_id: None, - selected_zone: None, - selected_zone_text: String::new(), + Ok(Self { + + layouts: layouts, + variants_map: variants, + variants: Vec::new(), + model: Some(models[0].clone()), + models: models, + layout: None, + variant: None, + keyboard_layouts: Vec::new(), + selected_kbd_layout_id: None, } + ) } Err(ex) => { - println!( - "Exception while trying to get time zones list from system {}", + let err = format!( + "Exception while trying to get kayboard layouts data from system {}", ex ); - Self { - time_zones: Err(ex), - regions: Vec::new(), - zones: Vec::new(), - region_id: None, - zone_id: None, - selected_zone: None, - selected_zone_text: String::new(), - } + Err(err) } } } diff --git a/src/stages/mod.rs b/src/stages/mod.rs index f96d128..55242e0 100644 --- a/src/stages/mod.rs +++ b/src/stages/mod.rs @@ -20,3 +20,4 @@ pub mod license; pub mod network; pub mod timezone; pub mod locale; +pub mod keyboard; \ No newline at end of file