From 212fa7ed44a8a8b89215743583b7211e25c66f5cc559fe0f12d6d31c6350a51b Mon Sep 17 00:00:00 2001 From: Kira Date: Wed, 24 Jun 2026 17:44:17 +0200 Subject: [PATCH] Most stuff should be complete. --- Cargo.lock | 14 ++++ Cargo.toml | 3 +- src/main.rs | 169 +++++++++++++++++++++++++++++++++------------ src/nyan_system.rs | 40 +++++------ 4 files changed, 161 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 548acf1..cddb2d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -290,6 +290,7 @@ name = "nyanit" version = "0.1.0" dependencies = [ "ratatui", + "ratatui-textarea", "regex", "termion", ] @@ -394,6 +395,19 @@ dependencies = [ "termion", ] +[[package]] +name = "ratatui-textarea" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c78d5ba0f26f97baed69a4c479f268a31c7b5b89d68ab939842152e383d6e73" +dependencies = [ + "ratatui-core", + "ratatui-termion", + "ratatui-widgets", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "ratatui-widgets" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 038bdfd..d22c2d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] ratatui = { version = "0.30", default-features = false, features = ["termion"] } +ratatui-textarea = { version = "0.9", default-features = false, features = ["termion"] } regex = "1.12.4" termion = "4" @@ -14,5 +15,5 @@ lto = true debug = false incremental = false codegen-units = 1 -opt-level = 3 +opt-level = 2 strip = true diff --git a/src/main.rs b/src/main.rs index 44364c0..b90153d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,25 +14,27 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use ratatui::widgets::Borders; -use ratatui::{Frame, Terminal, backend::TermionBackend, widgets::Paragraph}; +use ratatui::{Frame, Terminal, backend::TermionBackend}; use ratatui::{ layout::{Constraint, Layout}, - style::{Style, Stylize}, - text::{Line}, - widgets::{Block, List, ListState}, + style::{Color, Style, Stylize}, + text::Line, + widgets::{Block, Borders, List, ListState, Paragraph}, }; + +use ratatui_textarea::TextArea; + use std::io::{self, Read, stdout}; use std::process::ExitCode; use std::thread; use std::time::Duration; -use termion::event::{Event, Key}; +use termion::event::{self, Event}; use termion::raw::IntoRawMode; mod nyan_system; -use nyan_system::{ExecCommandError}; +use nyan_system::ExecCommandError; const BEE_CAT: &str = include_str!("bee_cat.txt"); @@ -71,17 +73,8 @@ enum NyanitState { HandOff(String), } -fn init_stage() -> Result { - // mount sysfss - nyan_system::prepare_env()?; - - // check if device is encrypted - if nyan_system::bzzpss_dev_encrypted(ROOT_DEV)? { - // if encrypted return EnterPassword so used get promted for pass - return Ok(NyanitState::EnterPassword); - } - - // if unencrypted +/// Mounting bee-root and chiking if there any snapshots +fn mount_and_check_snaps() -> Result { // mount bee-root nyan_system::mount_bee_root(TMP_ROOT, ROOT_DEV)?; @@ -93,24 +86,40 @@ fn init_stage() -> Result { return Ok(NyanitState::WaitForF2(snaps_list)); } - // tell to finish boot + // else tell to finish boot Ok(NyanitState::HandOff(TMP_ROOT.into())) } -pub struct NyanitTUI { +fn init_stage() -> Result { + // mount sysfss + nyan_system::prepare_env()?; + + // check if device is encrypted + if nyan_system::bzzpss_dev_encrypted(ROOT_DEV)? { + // if encrypted return EnterPassword so used get promted for pass + return Ok(NyanitState::EnterPassword); + } + + // if unencrypted + mount_and_check_snaps() +} + +struct NyanitTUI<'a> { exit: bool, state: NyanitState, list_state: ListState, time_counter: u32, + textarea: Option>, } -impl NyanitTUI { +impl NyanitTUI<'_> { pub fn new() -> io::Result { Ok(Self { exit: false, state: NyanitState::Init, list_state: ListState::default().with_selected(Some(0)), time_counter: 0, + textarea: None, }) } @@ -127,19 +136,34 @@ impl NyanitTUI { }; // if drive unencrypted and there is no root snapshots finish boot - if let NyanitState::HandOff(root_mnt) = &self.state { - if let Err(ex) = nyan_system::hand_off_control(root_mnt, SYS_INIT) { - self.state = NyanitState::Error(ex.to_string()) + match &self.state { + // if drive unencrypted and there is no root snapshots finish boot + NyanitState::HandOff(root_mnt) => { + if let Err(ex) = nyan_system::hand_off_control(root_mnt, SYS_INIT) { + self.state = NyanitState::Error(ex.to_string()) + } } + // init password input + NyanitState::EnterPassword => { + let mut textarea: TextArea<'_> = TextArea::default(); + textarea.set_cursor_line_style(Style::default()); + textarea.set_mask_char('\u{1F41D}'); //U+2022 BULLET (•) + textarea.set_placeholder_text("Please enter your password"); + textarea.set_style(Style::default().fg(Color::LightYellow)); + textarea.set_block(Block::default().borders(Borders::ALL).title("Password")); + self.textarea = Some(textarea); + } + _ => (), } + // Init terminal // 1. Enable raw mode let stdout = stdout().into_raw_mode()?; // 2. Setup Terminal let backend = TermionBackend::new(stdout); let mut terminal = Terminal::new(backend)?; - // init tty input and start actually drawing things now! + // init tty async input and start actually drawing things now! let mut tty_input_async = termion::async_stdin().bytes(); self.time_counter = 0; @@ -207,37 +231,94 @@ impl NyanitTUI { &mut self.list_state, ); } + NyanitState::EnterPassword => { + frame.render_widget(self.textarea.as_ref().unwrap(), vertical_chunks[1]); + } + NyanitState::HandOff(_) => { + let ok_text = Paragraph::new("Booting\nKira Linux\n \u{1F41D} \u{1F41D} :3") + .block(Block::bordered().light_green()) + .light_green(); + frame.render_widget(ok_text, vertical_chunks[1]); + // just some time to look at message + thread::sleep(Duration::from_millis(1000)) + + } _ => (), }; } fn handle_events(&mut self, tty_input: &mut io::Bytes) -> io::Result<()> { - // cheking if there is any input to read if let Some(item) = tty_input.next() { let read_byte = item?; - - let e = termion::event::parse_event(read_byte, tty_input)?; + + let e = event::parse_event(read_byte, tty_input)?; match &self.state { - NyanitState::WaitForF2(vol_list) => { - if e == Event::Key(Key::F(2)) { - self.state = NyanitState::ChooseSnapshot(vol_list.clone()); - } - }, - NyanitState::ChooseSnapshot(vol_list) => { - match e { - Event::Key(Key::Down) => self.list_state.select_next(), - Event::Key(Key::Up) => self.list_state.select_previous(), - Event::Key(Key::Char('\n')) => if let Some(idx) = self.list_state.selected() { - self.state = NyanitState::HandOff(vol_list[idx].clone()); - }, - _ => (), + // finishing boot + NyanitState::HandOff(root_mnt) => { + if let Err(ex) = nyan_system::hand_off_control(root_mnt, SYS_INIT) { + self.state = NyanitState::Error(ex.to_string()) } + } + NyanitState::WaitForF2(vol_list) => { + if e == Event::Key(event::Key::F(2)) { + self.state = NyanitState::ChooseSnapshot(vol_list.clone()); + } + } + NyanitState::ChooseSnapshot(vol_list) => match e { + Event::Key(event::Key::Down) => self.list_state.select_next(), + Event::Key(event::Key::Up) => self.list_state.select_previous(), + Event::Key(event::Key::Char('\n')) => { + if let Some(idx) = self.list_state.selected() { + self.state = NyanitState::HandOff(vol_list[idx].clone()); + } + } + _ => (), }, - _ => () + NyanitState::EnterPassword => { + match e { + // try to unlock if enter pressed + Event::Key(event::Key::Char('\n')) => { + let password = self.textarea.as_ref().unwrap().lines()[0].clone(); + match nyan_system::bcachefs_unlock(password, ROOT_DEV) { + Ok(unlocked) => { + if unlocked { + // need to check for subvolumes and stuff + self.state = match mount_and_check_snaps() { + Ok(st) => st, + Err(ex) => NyanitState::Error(ex.to_string()), + } + } + // ask to enter password again + else { + self.textarea + .as_mut() + .unwrap() + .set_placeholder_text("Wrong password, try again"); + self.textarea + .as_mut() + .unwrap() + .set_style(Style::default().fg(Color::LightRed)); + self.textarea.as_mut().unwrap().clear(); + } + } + Err(ex) => { + self.state = NyanitState::Error(ex.to_string()); + } + } + } + _ => { + self.textarea + .as_mut() + .unwrap() + .set_style(Style::default().fg(Color::LightYellow)); + self.textarea.as_mut().unwrap().input(e); + } + } + } + _ => (), } - } - else { + } else { // Tick time thread::sleep(Duration::from_millis(500)); self.time_counter = self.time_counter.wrapping_add(1u32); diff --git a/src/nyan_system.rs b/src/nyan_system.rs index fff294c..519878a 100644 --- a/src/nyan_system.rs +++ b/src/nyan_system.rs @@ -111,29 +111,29 @@ pub fn exec_command(cmd: &str, args: &Vec<&str>) -> Result<(), ExecCommandError> /// exec given command with arguments, returning stdout parsed to UTF-8 string /// capturing stderr in case or failure -pub fn exec_command_out(cmd: &str, args: &Vec<&str>) -> Result { - let out = Command::new(cmd).args(args).output()?; - if out.status.success() { - Ok(String::from_utf8(out.stdout)?) - } else { - Err(CommandFailed::new(cmd, args, out.stderr).into()) - } -} +// pub fn exec_command_out(cmd: &str, args: &Vec<&str>) -> Result { +// let out = Command::new(cmd).args(args).output()?; +// if out.status.success() { +// Ok(String::from_utf8(out.stdout)?) +// } else { +// Err(CommandFailed::new(cmd, args, out.stderr).into()) +// } +// } // ls /dev/disk/by-partlabel/ // here we can find gpt part labels -pub fn list_gpt_labeled_devs() -> Result, ExecCommandError> { - use std::os::unix::fs::FileTypeExt; - let mut res: Vec = Vec::new(); - for maybe_entry in fs::read_dir("/dev/disk/by-partlabel/")? { - let entry = maybe_entry?; - if entry.file_type()?.is_block_device() { - res.push(entry.path().to_str().unwrap().to_string()); - } - } +// pub fn list_gpt_labeled_devs() -> Result, ExecCommandError> { +// use std::os::unix::fs::FileTypeExt; +// let mut res: Vec = Vec::new(); +// for maybe_entry in fs::read_dir("/dev/disk/by-partlabel/")? { +// let entry = maybe_entry?; +// if entry.file_type()?.is_block_device() { +// res.push(entry.path().to_str().unwrap().to_string()); +// } +// } - Ok(res) -} +// Ok(res) +// } /// bcachefs unlock --file=/tmp/bzzpsspass.txt /dev/loop0somedev /// Ok so. It is stupid, because bcacheutils far from ideal. @@ -183,7 +183,7 @@ pub fn bcachefs_unlock(pass: String, dev: &str) -> Result Result { - match exec_command("bcachefs", &vec!["unlock", "--check"]) { + match exec_command("bcachefs", &vec!["unlock", "--check", dev_name]) { Ok(_) => Ok(true), // if status OK - dev encrypted Err(ExecCommandError::CommandFailed(_)) => Ok(false), // if bcachefs returned error - unencrypted Err(ex) => Err(ex), // othervice its error