From 85ca5ccedabd55c2d07aeedcf5aa9a074fbfede3 Mon Sep 17 00:00:00 2001 From: Antoine POPINEAU Date: Wed, 1 Jul 2020 13:44:24 +0200 Subject: [PATCH] Make changing command more discoverable and UI friendly. --- README.md | 2 +- src/config.rs | 4 ++ src/keyboard.rs | 62 ++++++++++++++++++------------ src/ui/mod.rs | 19 +++++++++- src/ui/prompt.rs | 98 ++++++++++++++++++++++++++++++------------------ 5 files changed, 120 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 0e731e3..ef131c3 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The default configuration tends to be as minimal as possible, visually speaking, The initial prompt container will be 80 column wide. You may change this with `--width` in case you need more space (for example, to account for large PAM challenge messages). -You may change the command that will be executed after opening a session by prefixing it with `!` in the username input box, for example `!bash`. +You may change the command that will be executed after opening a session by huttint `F2` and amending the command. ## Install diff --git a/src/config.rs b/src/config.rs index b8f9642..9b2cd12 100644 --- a/src/config.rs +++ b/src/config.rs @@ -27,9 +27,11 @@ impl Display for AuthStatus { impl Error for AuthStatus {} +#[derive(Copy, Clone, PartialEq)] pub enum Mode { Username, Password, + Command, } impl Default for Mode { @@ -43,11 +45,13 @@ pub struct Greeter { pub config: Option, pub stream: Option, pub command: Option, + pub previous_mode: Mode, pub mode: Mode, pub request: Option, pub cursor_offset: i16, pub username: String, pub answer: String, + pub new_command: String, pub secret: bool, pub prompt: String, pub greeting: Option, diff --git a/src/keyboard.rs b/src/keyboard.rs index 7bf102b..90d0466 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -11,40 +11,50 @@ use crate::{ pub fn handle(greeter: &mut Greeter, events: &Events) -> Result<(), Box> { if let Event::Input(input) = events.next()? { match input { - Key::Esc => crate::exit(greeter, AuthStatus::Cancel)?, + Key::Esc => { + if let Mode::Command = greeter.mode { + greeter.mode = greeter.previous_mode; + } else { + crate::exit(greeter, AuthStatus::Cancel)?; + } + } Key::Left => greeter.cursor_offset -= 1, Key::Right => greeter.cursor_offset += 1, + Key::F(2) => { + greeter.new_command = greeter.command.clone().unwrap_or_else(String::new); + greeter.previous_mode = greeter.mode; + greeter.mode = Mode::Command; + } + Key::Ctrl('a') => greeter.cursor_offset = -(greeter.username.len() as i16), Key::Ctrl('e') => greeter.cursor_offset = 0, - Key::Char('\n') | Key::Char('\t') => { - greeter.working = true; - greeter.message = None; - - match greeter.mode { - Mode::Username => { - if greeter.username.starts_with('!') { - greeter.command = Some(greeter.username.trim_start_matches('!').to_string()); - greeter.username = String::new(); - greeter.working = false; - - return Ok(()); - } - - greeter.request = Some(Request::CreateSession { username: greeter.username.clone() }); - } - - Mode::Password => { - greeter.request = Some(Request::PostAuthMessageResponse { - response: Some(greeter.answer.clone()), - }) - } + Key::Char('\n') | Key::Char('\t') => match greeter.mode { + Mode::Username => { + greeter.working = true; + greeter.message = None; + greeter.request = Some(Request::CreateSession { username: greeter.username.clone() }); + greeter.answer = String::new(); } - greeter.answer = String::new(); - } + Mode::Password => { + greeter.working = true; + greeter.message = None; + + greeter.request = Some(Request::PostAuthMessageResponse { + response: Some(greeter.answer.clone()), + }); + + greeter.answer = String::new(); + } + + Mode::Command => { + greeter.command = Some(greeter.new_command.clone()); + greeter.mode = greeter.previous_mode; + } + }, Key::Char(c) => insert_key(greeter, c), @@ -61,6 +71,7 @@ fn insert_key(greeter: &mut Greeter, c: char) { let value = match greeter.mode { Mode::Username => &mut greeter.username, Mode::Password => &mut greeter.answer, + Mode::Command => &mut greeter.new_command, }; let index = value.len() as i16 + greeter.cursor_offset; @@ -72,6 +83,7 @@ fn delete_key(greeter: &mut Greeter, key: Key) { let value = match greeter.mode { Mode::Username => &mut greeter.username, Mode::Password => &mut greeter.answer, + Mode::Command => &mut greeter.new_command, }; let index = match key { diff --git a/src/ui/mod.rs b/src/ui/mod.rs index beb5271..2d5404f 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -18,6 +18,7 @@ use tui::{ use crate::Greeter; const EXIT: &str = "Exit"; +const CHANGE_COMMAND: &str = "Change command"; const COMMAND: &str = "COMMAND"; pub fn draw(terminal: &mut Terminal>>, greeter: &mut Greeter) -> Result<(), Box> { @@ -50,7 +51,14 @@ pub fn draw(terminal: &mut Terminal>>, gr } let command = greeter.command.clone().unwrap_or_else(|| "-".to_string()); - let status_text = [status_label("ESC"), status_value(format!(" {} ", EXIT)), status_label(COMMAND), status_value(format!(" {} ", command))]; + let status_text = [ + status_label("ESC"), + status_value(EXIT), + status_label("F2"), + status_value(CHANGE_COMMAND), + status_label(COMMAND), + status_value(command), + ]; let status = Paragraph::new(status_text.iter()); f.render_widget(status, chunks[2]); @@ -82,5 +90,12 @@ fn status_value<'s, S>(text: S) -> Text<'s> where S: Into, { - Text::raw(text.into()) + Text::raw(format!(" {} ", text.into())) +} + +fn prompt_value<'s, S>(text: S) -> Text<'s> +where + S: Into, +{ + Text::styled(text.into(), Style::default().modifier(Modifier::BOLD)) } diff --git a/src/ui/prompt.rs b/src/ui/prompt.rs index 7535237..5064b84 100644 --- a/src/ui/prompt.rs +++ b/src/ui/prompt.rs @@ -4,11 +4,11 @@ use termion::raw::RawTerminal; use tui::{ backend::TermionBackend, layout::{Alignment, Constraint, Direction, Layout, Rect}, - style::{Modifier, Style}, widgets::{Block, BorderType, Borders, Paragraph, Text}, Frame, }; +use super::prompt_value; use crate::{info::get_hostname, Greeter, Mode}; const PADDING: u16 = 2; @@ -20,6 +20,7 @@ const MESSAGE_INDEX: usize = 3; const TITLE: &str = "Authenticate into"; const USERNAME: &str = "Username:"; +const COMMAND: &str = "New command:"; const WORKING: &str = "Please wait..."; pub fn draw(greeter: &mut Greeter, f: &mut Frame>>) -> Result<(u16, u16), Box> { @@ -59,51 +60,65 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame { + let command_label_text = [prompt_value(COMMAND)]; + let command_label = Paragraph::new(command_label_text.iter()); + let command_value_text = [Text::raw(&greeter.new_command)]; + let command_value = Paragraph::new(command_value_text.iter()); + f.render_widget(command_label, chunks[USERNAME_INDEX]); f.render_widget( - answer_value, - Rect::new( - chunks[ANSWER_INDEX].x + greeter.prompt.len() as u16, - chunks[ANSWER_INDEX].y, - get_input_width(greeter, &greeter.prompt), - 1, - ), + command_value, + Rect::new(1 + chunks[USERNAME_INDEX].x + COMMAND.len() as u16, chunks[USERNAME_INDEX].y, get_input_width(greeter, COMMAND), 1), ); } - } - if let Some(ref message) = message { - let message_text = [Text::raw(message)]; - let message = Paragraph::new(message_text.iter()); + Mode::Username | Mode::Password => { + f.render_widget(username_label, chunks[USERNAME_INDEX]); + f.render_widget( + username_value, + Rect::new(1 + chunks[USERNAME_INDEX].x + USERNAME.len() as u16, chunks[USERNAME_INDEX].y, get_input_width(greeter, USERNAME), 1), + ); - match greeter.mode { - Mode::Username => f.render_widget(message, chunks[ANSWER_INDEX]), - Mode::Password => f.render_widget(message, chunks[MESSAGE_INDEX]), + let answer_text = if greeter.working { [Text::raw(WORKING)] } else { [prompt_value(&greeter.prompt)] }; + let answer_label = Paragraph::new(answer_text.iter()); + + if greeter.mode == Mode::Password || greeter.previous_mode == Mode::Password { + f.render_widget(answer_label, chunks[ANSWER_INDEX]); + + if !greeter.secret { + let answer_value_text = [Text::raw(&greeter.answer)]; + let answer_value = Paragraph::new(answer_value_text.iter()); + + f.render_widget( + answer_value, + Rect::new( + chunks[ANSWER_INDEX].x + greeter.prompt.len() as u16, + chunks[ANSWER_INDEX].y, + get_input_width(greeter, &greeter.prompt), + 1, + ), + ); + } + } + + if let Some(ref message) = message { + let message_text = [Text::raw(message)]; + let message = Paragraph::new(message_text.iter()); + + match greeter.mode { + Mode::Username => f.render_widget(message, chunks[ANSWER_INDEX]), + Mode::Password => f.render_widget(message, chunks[MESSAGE_INDEX]), + Mode::Command => {} + } + } } } @@ -123,6 +138,12 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame { + let offset = get_cursor_offset(greeter, greeter.new_command.len()); + + Ok((2 + cursor.x + COMMAND.len() as u16 + offset as u16, 1 + cursor.y)) + } } } @@ -131,11 +152,14 @@ fn get_height(greeter: &Greeter) -> u16 { let (_, greeting_height) = get_greeting_height(greeter, 1, 0); let initial = match greeter.mode { - Mode::Username => 5, + Mode::Username | Mode::Command => 5, Mode::Password => 7, }; - initial + greeting_height + message_height + match greeter.mode { + Mode::Command => initial + greeting_height, + _ => initial + greeting_height + message_height, + } } fn get_greeting_height(greeter: &Greeter, padding: u16, fallback: u16) -> (Option, u16) {