From be4a0c74bea56483b7342d653d128e01daaa6556 Mon Sep 17 00:00:00 2001 From: j-hc Date: Wed, 13 Sep 2023 01:08:13 +0300 Subject: [PATCH] refactor cli --- cli/src/main.rs | 70 ++++---- cli/src/menus.rs | 407 +++++++++++++++++++++++++---------------------- 2 files changed, 256 insertions(+), 221 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 3122981..3ee3539 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -9,6 +9,7 @@ use std::mem::size_of; use std::ops::Range; use std::panic::Location; use std::process::{Command, ExitCode}; +use termion::raw::IntoRawMode; use termion::event::Key; use termion::{clear, cursor}; @@ -17,7 +18,7 @@ mod colorize; use colorize::ToColored; mod menus; -use menus::{cursor_hide, cursor_show, select_menu, select_menu_numbered, select_menu_with_input}; +use menus::Menus; #[cfg(target_os = "android")] const MODULE_DETACH: &str = "/data/adb/modules/zygisk-detach/detach.bin"; @@ -62,7 +63,6 @@ type IOError = Result>; fn main() -> ExitCode { std::panic::set_hook(Box::new(|panic| { - use termion::raw::IntoRawMode; if let Ok(mut stderr) = io::stderr().into_raw_mode() { let _ = writeln!(stderr, "\r\n{panic}\r\n"); let _ = writeln!(stderr, "This should not have happened.\r"); @@ -73,6 +73,7 @@ fn main() -> ExitCode { let _ = write!(stderr, "{}", cursor::Show); } })); + let mut menus = Menus::new(); let mut args = std::env::args().skip(1); if matches!(args.next().as_deref(), Some("--serialize")) { @@ -93,14 +94,14 @@ fn main() -> ExitCode { } } - let ret = match interactive() { + let ret = match interactive(&mut menus) { Ok(()) => ExitCode::SUCCESS, Err(err) => { eprintln!("\rERROR: {err}"); ExitCode::FAILURE } }; - cursor_show().unwrap(); + menus.cursor_show().unwrap(); ret } @@ -126,19 +127,19 @@ fn serialize_txt(path: &str) -> IOError<()> { Ok(()) } -fn interactive() -> IOError<()> { - cursor_hide()?; +fn interactive(menus: &mut Menus) -> IOError<()> { + menus.cursor_hide()?; print!("zygisk-detach cli by github.com/j-hc\r\n\n"); loop { - match main_menu()? { - Op::DetachSelect => detach_menu()?, - Op::ReattachSelect => reattach_menu()?, + match main_menu(menus)? { + Op::DetachSelect => detach_menu(menus)?, + Op::ReattachSelect => reattach_menu(menus)?, Op::Reset => { if fs::remove_file(MODULE_DETACH).is_ok() { let _ = kill_store(); - text!("Reset"); + text!(menus, "Reset"); } else { - text!("Already empty"); + text!(menus, "Already empty"); } } Op::CopyToSd => { @@ -147,9 +148,9 @@ fn interactive() -> IOError<()> { #[cfg(target_os = "linux")] const SDCARD_DETACH: &str = "detach_sdcard.bin"; match fs::copy(MODULE_DETACH, SDCARD_DETACH) { - Ok(_) => text!("Copied"), + Ok(_) => text!(menus, "Copied"), Err(err) if err.kind() == io::ErrorKind::NotFound => { - text!("detach.bin not found"); + text!(menus, "detach.bin not found"); } Err(err) => return Err(err.into()), } @@ -160,7 +161,7 @@ fn interactive() -> IOError<()> { } } -fn reattach_menu() -> IOError<()> { +fn reattach_menu(menus: &mut Menus) -> IOError<()> { let mut detach_txt = match fs::OpenOptions::new() .write(true) .read(true) @@ -168,7 +169,7 @@ fn reattach_menu() -> IOError<()> { { Ok(v) => v, Err(e) if e.kind() == io::ErrorKind::NotFound => { - text!("detach.bin not found"); + text!(menus, "detach.bin not found"); return Ok(()); } Err(e) => return Err(e.into()), @@ -176,14 +177,14 @@ fn reattach_menu() -> IOError<()> { let mut content = Vec::new(); detach_txt.read_to_end(&mut content)?; detach_txt.seek(io::SeekFrom::Start(0))?; - let detached_apps = get_detached_apps(&content); + let detached_apps = get_detached_apps(menus, &content); let detach_len = detached_apps.len(); if detach_len == 0 { - text!("detach.bin is empty"); + text!(menus, "detach.bin is empty"); return Ok(()); } let list = detached_apps.iter().map(|e| e.0.as_str()); - let Some(i) = select_menu( + let Some(i) = menus.select_menu( list, "Select the app to re-attach ('q' to leave):", "✖".red(), @@ -193,7 +194,7 @@ fn reattach_menu() -> IOError<()> { return Ok(()); }; - textln!("{}: {}", "re-attach".red(), detached_apps[i].0); + textln!(menus, "{}: {}", "re-attach".red(), detached_apps[i].0); content.drain(detached_apps[i].1.clone()); detach_txt.set_len(0)?; detach_txt.write_all(&content)?; @@ -201,7 +202,7 @@ fn reattach_menu() -> IOError<()> { Ok(()) } -fn get_detached_apps(detach_txt: &[u8]) -> Vec<(String, Range)> { +fn get_detached_apps(menus: &mut Menus, detach_txt: &[u8]) -> Vec<(String, Range)> { let mut i = 0; let mut detached = Vec::new(); while i < detach_txt.len() { @@ -210,7 +211,7 @@ fn get_detached_apps(detach_txt: &[u8]) -> Vec<(String, Range)> { i += SZ_LEN; let Some(encoded_name) = &detach_txt.get(i..i + len as usize) else { eprintln!("Corrupted detach.bin. Reset and try again."); - let _ = cursor_show(); + let _ = menus.cursor_show(); std::process::exit(1); }; let name = String::from_utf8(encoded_name.iter().step_by(2).cloned().collect()).unwrap(); @@ -244,7 +245,7 @@ enum Op { Nop, } -fn main_menu() -> IOError { +fn main_menu(menus: &mut Menus) -> IOError { struct OpText { desc: &'static str, op: Op, @@ -265,16 +266,16 @@ fn main_menu() -> IOError { OpText::new("Reset detached apps", Op::Reset), OpText::new("Copy detach.bin to /sdcard", Op::CopyToSd), ]; - let i = select_menu_numbered(ops.iter(), Key::Char('q'), "- Selection:")?; + let i = menus.select_menu_numbered(ops.iter(), Key::Char('q'), "- Selection:")?; use menus::SelectNumberedResp as SN; match i { SN::Index(i) => Ok(ops[i].op), SN::UndefinedKey(Key::Char(c)) => { - text!("Undefined key {c:?}"); + text!(menus, "Undefined key {c:?}"); Ok(Op::Nop) } SN::UndefinedKey(k @ (Key::Down | Key::Up | Key::Left | Key::Right)) => { - text!("Undefined key {k:?}"); + text!(menus, "Undefined key {k:?}"); Ok(Op::Nop) } SN::Quit => Ok(Op::Quit), @@ -300,7 +301,7 @@ fn bin_serialize(app: &str, sink: impl Write) -> IOError<()> { Ok(()) } -fn detach_menu() -> IOError<()> { +fn detach_menu(menus: &mut Menus) -> IOError<()> { let installed_apps = get_installed_apps()?; let apps: Vec<&str> = installed_apps[..installed_apps.len() - 1] .split(|&e| e == b'\n') @@ -310,8 +311,8 @@ fn detach_menu() -> IOError<()> { }) .map(|e| std::str::from_utf8(e).expect("non utf-8 package names?")) .collect(); - cursor_show()?; - let selected = select_menu_with_input( + menus.cursor_show()?; + let selected = menus.select_menu_with_input( |input| { let input = input.trim(); if !input.is_empty() { @@ -330,7 +331,7 @@ fn detach_menu() -> IOError<()> { "- app: ", None, )?; - cursor_hide()?; + menus.cursor_hide()?; if let Some(detach_app) = selected { let mut f = fs::OpenOptions::new() .create(true) @@ -339,13 +340,16 @@ fn detach_menu() -> IOError<()> { .open(MODULE_DETACH)?; let mut buf: Vec = Vec::new(); f.read_to_end(&mut buf)?; - if !get_detached_apps(&buf).iter().any(|(s, _)| s == detach_app) { + if !get_detached_apps(menus, &buf) + .iter() + .any(|(s, _)| s == detach_app) + { bin_serialize(detach_app, f)?; - textln!("{} {}", "detach:".green(), detach_app); - textln!("Changes are applied. No need for a reboot!"); + textln!(menus, "{} {}", "detach:".green(), detach_app); + textln!(menus, "Changes are applied. No need for a reboot!"); detach_bin_changed(); } else { - textln!("{} {}", "already detached:".green(), detach_app); + textln!(menus, "{} {}", "already detached:".green(), detach_app); } } Ok(()) diff --git a/cli/src/menus.rs b/cli/src/menus.rs index 8f6b2d8..4a8ccc5 100644 --- a/cli/src/menus.rs +++ b/cli/src/menus.rs @@ -1,224 +1,255 @@ use crate::colorize::ToColored; use std::fmt::Display; -use std::io::{self, BufWriter, Write}; +use std::io::Write; +use std::io::{self, BufWriter, Stdout}; use termion::input::TermRead; -use termion::raw::IntoRawMode; +use termion::raw::{IntoRawMode, RawTerminal}; use termion::{clear, cursor, event::Key}; #[macro_export] macro_rules! text { - ($($arg:tt)*) => {{ - write!( - ::std::io::stdout(), - "{}{}{}{}\r", - cursor::Up(1), - clear::CurrentLine, - format_args!($($arg)*), - cursor::Down(1) - )?; - ::std::io::stdout().flush()?; + ($dst:expr, $($arg:tt)*) => {{ + write!( + $dst.stdout, + "{}{}{}{}\r", + cursor::Up(1), + clear::CurrentLine, + format_args!($($arg)*), + cursor::Down(1) + )?; + $dst.stdout.flush()?; }}; } #[macro_export] macro_rules! textln { - ($($arg:tt)*) => {{ - text!("{}\n", format_args!($($arg)*)); + ($dst:expr, $($arg:tt)*) => {{ + text!($dst, "{}\n", format_args!($($arg)*)); }}; } -pub fn cursor_hide() -> io::Result<()> { - let mut stdout = io::stdout().into_raw_mode()?; - write!(stdout, "{}", cursor::Hide)?; - Ok(()) +pub enum SelectNumberedResp { + Index(usize), + UndefinedKey(Key), + Quit, } - -pub fn cursor_show() -> io::Result<()> { - let mut stdout = io::stdout().into_raw_mode()?; - write!(stdout, "{}", cursor::Show)?; - Ok(()) +pub struct Menus { + pub(crate) stdout: BufWriter>, } - -pub fn select_menu + Clone>( - list: I, - title: impl Display, - prompt: impl Display, - quit: Option, -) -> io::Result> { - let mut stdout = BufWriter::new(io::stdout().lock().into_raw_mode()?); - let mut select_idx = 0; - let list_len = list.clone().count(); - let mut keys = io::stdin().lock().keys(); - write!(stdout, "{}\r\n", title)?; - let ret = loop { - for (i, selection) in list.clone().enumerate() { - if i == select_idx { - write!(stdout, "{} {}\r\n", prompt, selection.black().white_bg())?; - } else { - write!(stdout, "{}\r\n", selection.faint())?; - } +impl Menus { + pub fn new() -> Self { + Self { + stdout: BufWriter::new(io::stdout().into_raw_mode().unwrap()), } - stdout.flush()?; + } - let key = keys - .next() - .expect("keys() should block") - .expect("faulty keyboard?"); - write!( - stdout, - "\r{}{}", - cursor::Up(list_len as u16), - clear::AfterCursor - )?; - match key { - Key::Char('\n') => { - break Ok(Some(select_idx)); - } - Key::Up => select_idx = select_idx.saturating_sub(1), - Key::Down => { - if select_idx + 1 < list_len { - select_idx += 1; + pub fn cursor_hide(&mut self) -> io::Result<()> { + write!(self.stdout, "{}", cursor::Hide)?; + Ok(()) + } + + pub fn cursor_show(&mut self) -> io::Result<()> { + write!(self.stdout, "{}", cursor::Show)?; + Ok(()) + } + + pub fn select_menu + Clone>( + &mut self, + list: I, + title: impl Display, + prompt: impl Display, + quit: Option, + ) -> io::Result> { + let mut select_idx = 0; + let list_len = list.clone().count(); + let mut keys = io::stdin().lock().keys(); + write!(self.stdout, "{}\r\n", title)?; + let ret = loop { + for (i, selection) in list.clone().enumerate() { + if i == select_idx { + write!( + self.stdout, + "{} {}\r\n", + prompt, + selection.black().white_bg() + )?; + } else { + write!(self.stdout, "{}\r\n", selection.faint())?; } } - k if k == Key::Ctrl('c') || quit.is_some_and(|q| q == key) => { - break Ok(None); - } - _ => {} - } - }; - write!(stdout, "{}{}", cursor::Up(1), clear::CurrentLine)?; - stdout.flush()?; - ret -} + self.stdout.flush()?; -pub fn select_menu_with_input Vec, L: Display>( - lister: F, - prompt: impl Display, - input_prompt: &str, - quit: Option, -) -> io::Result> { - let mut stdout = BufWriter::new(io::stdout().lock().into_raw_mode()?); - let mut select_idx = 0; - let mut cursor = 0; - let mut input = String::new(); + let key = keys + .next() + .expect("keys() should block") + .expect("faulty keyboard?"); + write!( + self.stdout, + "\r{}{}", + cursor::Up(list_len as u16), + clear::AfterCursor + )?; + match key { + Key::Char('\n') => { + break Ok(Some(select_idx)); + } + Key::Up => select_idx = select_idx.saturating_sub(1), + Key::Down => { + if select_idx + 1 < list_len { + select_idx += 1; + } + } + k if k == Key::Ctrl('c') || quit.is_some_and(|q| q == key) => { + break Ok(None); + } + _ => {} + } + }; + write!(self.stdout, "{}{}", cursor::Up(1), clear::CurrentLine)?; + self.stdout.flush()?; + ret + } - let mut keys = io::stdin().lock().keys(); - let ret = loop { - write!( - stdout, - "\r{}{}{}", - clear::AfterCursor, - input_prompt.magenta(), - input, - )?; - let mut list = lister(&input); - let list_len = list.len(); + pub fn select_menu_with_input Vec, L: Display>( + &mut self, + lister: F, + prompt: impl Display, + input_prompt: &str, + quit: Option, + ) -> io::Result> { + let mut select_idx = 0; + let mut cursor = 0; + let mut input = String::new(); - select_idx = select_idx.min(list_len); - if list_len > 0 { - write!(stdout, "\r\n\n↑ and ↓ to navigate")?; - write!(stdout, "\n\rENTER to select\r\n")?; - } + let mut keys = io::stdin().lock().keys(); + let ret = loop { + write!( + self.stdout, + "\r{}{}{}", + clear::AfterCursor, + input_prompt.magenta(), + input, + )?; + let mut list = lister(&input); + let list_len = list.len(); - for (i, selection) in list.iter().enumerate() { - if i == select_idx { - write!(stdout, "{} {}\r\n", prompt, selection.black().white_bg())?; - } else { - write!(stdout, "{}\r\n", selection.faint())?; + select_idx = select_idx.min(list_len); + if list_len > 0 { + write!(self.stdout, "\r\n\n↑ and ↓ to navigate")?; + write!(self.stdout, "\n\rENTER to select\r\n")?; } - } - if list_len > 0 { - write!(stdout, "{}", cursor::Up(list_len as u16 + 4))?; - } - write!( - stdout, - "\r{}", - cursor::Right(input_prompt.len() as u16 + cursor as u16) - )?; - stdout.flush()?; - write!(stdout, "\r{}", clear::AfterCursor)?; - match keys - .next() - .expect("keys() should block") - .expect("faulty keyboard?") - { - Key::Char('\n') => { - break Ok(if list_len > select_idx { - Some(list.remove(select_idx)) + for (i, selection) in list.iter().enumerate() { + if i == select_idx { + write!( + self.stdout, + "{} {}\r\n", + prompt, + selection.black().white_bg() + )?; } else { - None - }); - } - Key::Up => select_idx = select_idx.saturating_sub(1), - Key::Down => { - if select_idx + 1 < list_len { - select_idx += 1; - } - } - Key::Backspace => { - if cursor > 0 { - cursor -= 1; - input.remove(cursor); + write!(self.stdout, "{}\r\n", selection.faint())?; } } - Key::Char(c) => { - input.insert(cursor, c); - cursor += 1; + if list_len > 0 { + write!(self.stdout, "{}", cursor::Up(list_len as u16 + 4))?; } - Key::Right => { - if cursor < input.len() { - cursor += 1 + write!( + self.stdout, + "\r{}", + cursor::Right(input_prompt.len() as u16 + cursor as u16) + )?; + self.stdout.flush()?; + write!(self.stdout, "\r{}", clear::AfterCursor)?; + + match keys + .next() + .expect("keys() should block") + .expect("faulty keyboard?") + { + Key::Char('\n') => { + break Ok(if list_len > select_idx { + Some(list.remove(select_idx)) + } else { + None + }); } + Key::Up => select_idx = select_idx.saturating_sub(1), + Key::Down => { + if select_idx + 1 < list_len { + select_idx += 1; + } + } + Key::Backspace => { + if cursor > 0 { + cursor -= 1; + input.remove(cursor); + } + } + Key::Char(c) => { + if c.is_ascii() { + input.insert(cursor, c); + cursor += 1; + } else { + write!( + self.stdout, + "{}{}{}{}\r", + cursor::Up(1), + clear::CurrentLine, + format_args!("Only ASCII characters"), + cursor::Down(1) + )?; + } + } + Key::Right => { + if cursor < input.len() { + cursor += 1 + } + } + Key::Left => cursor = cursor.saturating_sub(1), + k if k == Key::Ctrl('c') || quit.is_some_and(|q| q == k) => { + break Ok(None); + } + _ => {} } - Key::Left => cursor = cursor.saturating_sub(1), - k if k == Key::Ctrl('c') || quit.is_some_and(|q| q == k) => { - break Ok(None); - } - _ => {} - } - }; - stdout.flush()?; - ret -} - -pub enum SelectNumberedResp { - Index(usize), - UndefinedKey(Key), - Quit, -} - -pub fn select_menu_numbered + Clone>( - list: I, - quit: Key, - title: &str, -) -> io::Result { - let mut stdout = BufWriter::new(io::stdout().lock().into_raw_mode()?); - let list_len = list.clone().count(); - write!(stdout, "\r{title}\r\n")?; - for (i, s) in list.enumerate() { - write!(stdout, "{}. {}\r\n", (i + 1).green(), s)?; + }; + write!(self.stdout, "\r{}{}\r\n", cursor::Up(1), clear::AfterCursor)?; + self.stdout.flush()?; + ret } - write!(stdout, "{}. Quit\r\n", 'q'.green())?; - stdout.flush()?; - let key = io::stdin() - .lock() - .keys() - .next() - .expect("keys() should block") - .expect("faulty keyboard?"); - write!( - stdout, - "\r{}{}", - cursor::Up(list_len as u16 + 2), - clear::AfterCursor, - )?; - stdout.flush()?; - match key { - Key::Char(c) if c.to_digit(10).is_some_and(|c| c as usize <= list_len) => Ok( - SelectNumberedResp::Index(c.to_digit(10).unwrap() as usize - 1), - ), - k if k == Key::Ctrl('c') || k == quit => Ok(SelectNumberedResp::Quit), - k => Ok(SelectNumberedResp::UndefinedKey(k)), + + pub fn select_menu_numbered + Clone>( + &mut self, + list: I, + quit: Key, + title: &str, + ) -> io::Result { + let list_len = list.clone().count(); + write!(self.stdout, "\r{title}\r\n")?; + for (i, s) in list.enumerate() { + write!(self.stdout, "{}. {}\r\n", (i + 1).green(), s)?; + } + write!(self.stdout, "{}. Quit\r\n", 'q'.green())?; + self.stdout.flush()?; + let key = io::stdin() + .lock() + .keys() + .next() + .expect("keys() should block") + .expect("faulty keyboard?"); + write!( + self.stdout, + "\r{}{}", + cursor::Up(list_len as u16 + 2), + clear::AfterCursor, + )?; + self.stdout.flush()?; + match key { + Key::Char(c) if c.to_digit(10).is_some_and(|c| c as usize <= list_len) => Ok( + SelectNumberedResp::Index(c.to_digit(10).unwrap() as usize - 1), + ), + k if k == Key::Ctrl('c') || k == quit => Ok(SelectNumberedResp::Quit), + k => Ok(SelectNumberedResp::UndefinedKey(k)), + } } }