Refactored code and applied rustfmt.

This commit is contained in:
Antoine POPINEAU 2020-06-28 12:02:56 +02:00
parent e850f93269
commit 372c6b7a46
No known key found for this signature in database
GPG Key ID: A78AC64694F84063
10 changed files with 545 additions and 591 deletions

4
.rustfmt.toml Normal file
View File

@ -0,0 +1,4 @@
fn_args_density = "Compressed"
merge_imports = true
max_width = 200
tab_spaces = 2

View File

@ -12,3 +12,6 @@ tui = "0.9.5"
nix = "0.17.0"
textwrap = "0.12.0"
chrono = "0.4.11"
[profile.release]
lto = true

130
src/config.rs Normal file
View File

@ -0,0 +1,130 @@
use std::{env, error::Error, os::unix::net::UnixStream, process};
use getopts::{Matches, Options};
use greetd_ipc::Request;
use crate::info::get_issue;
pub enum AuthStatus {
Success,
Failure,
Cancel,
}
pub enum Mode {
Username,
Password,
}
impl Default for Mode {
fn default() -> Mode {
Mode::Username
}
}
pub struct Greeter {
pub config: Option<Matches>,
pub stream: UnixStream,
pub command: Option<String>,
pub mode: Mode,
pub request: Option<Request>,
pub cursor_offset: i16,
pub username: String,
pub answer: String,
pub secret: bool,
pub prompt: String,
pub greeting: Option<String>,
pub message: Option<String>,
pub working: bool,
pub done: bool,
}
impl Greeter {
pub fn new() -> Result<Self, Box<dyn Error>> {
let stream = UnixStream::connect(env::var("GREETD_SOCK")?)?;
Ok(Self {
stream,
config: Default::default(),
command: Default::default(),
mode: Default::default(),
request: Default::default(),
cursor_offset: Default::default(),
username: Default::default(),
answer: Default::default(),
secret: Default::default(),
prompt: Default::default(),
greeting: Default::default(),
message: Default::default(),
working: Default::default(),
done: Default::default(),
})
}
pub fn config<'g>(&'g self) -> &'g Matches {
self.config.as_ref().unwrap()
}
pub fn option(&self, name: &str) -> Option<String> {
match self.config().opt_str(name) {
Some(value) => Some(value),
None => None,
}
}
pub fn width(&self) -> u16 {
if let Some(value) = self.option("width") {
if let Ok(width) = value.parse::<u16>() {
return width;
}
}
80
}
}
pub fn parse_options(greeter: Greeter) -> Greeter {
let mut greeter = greeter;
let mut opts = Options::new();
opts.optflag("h", "help", "show this usage information");
opts.optopt("c", "cmd", "command to run", "COMMAND");
opts.optopt("", "width", "width of the main prompt (default: 80)", "WIDTH");
opts.optflag("i", "issue", "show the host's issue file");
opts.optopt("g", "greeting", "show custom text above login prompt", "GREETING");
opts.optflag("t", "time", "display the current date and time");
greeter.config = match opts.parse(&env::args().collect::<Vec<String>>()) {
Ok(matches) => Some(matches),
Err(usage) => {
println!("{}", usage);
print_usage(opts);
process::exit(1);
}
};
if greeter.config().opt_present("help") {
print_usage(opts);
std::process::exit(0);
}
if greeter.config().opt_present("issue") && greeter.config().opt_present("greeting") {
eprintln!("Only one of --issue and --greeting may be used at the same time");
print_usage(opts);
std::process::exit(0);
}
greeter.greeting = greeter.option("greeting");
greeter.command = greeter.option("cmd");
if greeter.config().opt_present("issue") {
greeter.greeting = get_issue();
}
greeter
}
fn print_usage(opts: Options) {
eprint!("{}", opts.usage("Usage: greetd-tui [OPTIONS]"));
}

View File

@ -3,61 +3,59 @@ use std::{io, sync::mpsc, thread, time::Duration};
use termion::{event::Key, input::TermRead};
pub enum Event<I> {
Input(I),
Tick,
Input(I),
Tick,
}
pub struct Events {
rx: mpsc::Receiver<Event<Key>>,
rx: mpsc::Receiver<Event<Key>>,
}
#[derive(Debug, Clone, Copy)]
pub struct Config {
pub tick_rate: Duration,
pub tick_rate: Duration,
}
impl Default for Config {
fn default() -> Config {
Config {
tick_rate: Duration::from_millis(250),
}
fn default() -> Config {
Config {
tick_rate: Duration::from_millis(250),
}
}
}
impl Events {
pub fn new() -> Events {
let (tx, rx) = mpsc::channel();
pub fn new() -> Events {
let (tx, rx) = mpsc::channel();
{
let tx = tx.clone();
{
let tx = tx.clone();
{
let tx = tx.clone();
thread::spawn(move || {
let stdin = io::stdin();
thread::spawn(move || {
let stdin = io::stdin();
for evt in stdin.keys() {
if let Ok(key) = evt {
if let Err(_) = tx.send(Event::Input(key)) {
return;
}
}
}
})
};
for evt in stdin.keys() {
if let Ok(key) = evt {
if let Err(_) = tx.send(Event::Input(key)) {
return;
}
}
}
})
};
{
thread::spawn(move || loop {
tx.send(Event::Tick).unwrap();
thread::sleep(Duration::from_millis(250));
})
};
};
{
thread::spawn(move || loop {
tx.send(Event::Tick).unwrap();
Events { rx }
}
thread::sleep(Duration::from_millis(250));
})
};
pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> {
self.rx.recv()
}
Events { rx }
}
pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> {
self.rx.recv()
}
}

View File

@ -3,30 +3,26 @@ use std::{env, fs};
use nix::sys::utsname;
pub fn get_hostname() -> String {
utsname::uname().nodename().to_string()
utsname::uname().nodename().to_string()
}
pub fn get_issue() -> Option<String> {
let vtnr: usize = env::var("XDG_VTNR")
.unwrap_or_else(|_| "0".to_string())
.parse()
.expect("unable to parse VTNR");
let vtnr: usize = env::var("XDG_VTNR").unwrap_or_else(|_| "0".to_string()).parse().expect("unable to parse VTNR");
let uts = utsname::uname();
let uts = utsname::uname();
if let Ok(issue) = fs::read_to_string("/etc/issue") {
return Some(
issue
.replace("\\S", "Linux")
.replace("\\l", &format!("tty{}", vtnr))
.replace("\\s", &uts.sysname())
.replace("\\r", &uts.release())
.replace("\\v", &uts.version())
.replace("\\n", &uts.nodename())
.replace("\\m", &uts.machine())
.replace("\\\\", "\\"),
);
}
if let Ok(issue) = fs::read_to_string("/etc/issue") {
return Some(
issue
.replace("\\S", "Linux")
.replace("\\l", &format!("tty{}", vtnr))
.replace("\\s", &uts.sysname())
.replace("\\r", &uts.release())
.replace("\\v", &uts.version())
.replace("\\n", &uts.nodename())
.replace("\\m", &uts.machine())
.replace("\\\\", "\\"),
);
}
None
None
}

View File

@ -1,96 +1,87 @@
use std::{error::Error, os::unix::net::UnixStream};
use std::error::Error;
use greetd_ipc::{codec::SyncCodec, AuthMessageType, ErrorType, Request, Response};
use crate::{AuthStatus, Greeter, Mode};
pub fn handle(greeter: &mut Greeter, stream: &mut UnixStream) -> Result<(), Box<dyn Error>> {
if let Some(ref mut request) = &mut greeter.request {
request.write_to(stream)?;
pub fn handle(greeter: &mut Greeter) -> Result<(), Box<dyn Error>> {
if let Some(ref mut request) = &mut greeter.request {
request.write_to(&mut greeter.stream)?;
greeter.request = None;
let response = Response::read_from(&mut greeter.stream)?;
parse_response(greeter, response)?;
}
Ok(())
}
fn parse_response(greeter: &mut Greeter, response: Response) -> Result<(), Box<dyn Error>> {
match response {
Response::AuthMessage { auth_message_type, auth_message } => match auth_message_type {
AuthMessageType::Secret => {
greeter.mode = Mode::Password;
greeter.working = false;
greeter.secret = true;
greeter.prompt = auth_message;
}
AuthMessageType::Visible => {
greeter.mode = Mode::Password;
greeter.working = false;
greeter.secret = false;
greeter.prompt = auth_message;
}
AuthMessageType::Error => greeter.message = Some(auth_message),
AuthMessageType::Info => {
if let Some(message) = &mut greeter.message {
message.push_str(&auth_message.trim_end());
message.push('\n');
} else {
greeter.message = Some(auth_message.trim_end().to_string());
if let Some(message) = &mut greeter.message {
message.push('\n');
}
}
Request::PostAuthMessageResponse { response: None }.write_to(&mut greeter.stream)?;
greeter.request = None;
let response = Response::read_from(stream)?;
let response = Response::read_from(&mut greeter.stream)?;
parse_response(greeter, stream, response)?;
}
parse_response(greeter, response)?;
}
},
Ok(())
Response::Success => match greeter.done {
true => crate::exit(greeter, AuthStatus::Success),
false => {
if let Some(command) = &greeter.command {
greeter.done = true;
greeter.request = Some(Request::StartSession { cmd: vec![command.clone()] })
} else {
crate::exit(greeter, AuthStatus::Failure);
}
}
},
Response::Error { error_type, description } => match error_type {
ErrorType::AuthError => {
crate::exit(greeter, AuthStatus::Failure);
}
ErrorType::Error => {
cancel(greeter);
greeter.message = Some(description)
}
},
}
Ok(())
}
fn parse_response(
greeter: &mut Greeter,
stream: &mut UnixStream,
response: Response,
) -> Result<(), Box<dyn Error>> {
match response {
Response::AuthMessage {
auth_message_type,
auth_message,
} => match auth_message_type {
AuthMessageType::Secret => {
greeter.mode = Mode::Password;
greeter.working = false;
greeter.secret = true;
greeter.prompt = auth_message;
}
AuthMessageType::Visible => {
greeter.mode = Mode::Password;
greeter.working = false;
greeter.secret = false;
greeter.prompt = auth_message;
}
AuthMessageType::Error => greeter.message = Some(auth_message),
AuthMessageType::Info => {
if let Some(message) = &mut greeter.message {
message.push_str(&auth_message.trim_end());
message.push('\n');
} else {
greeter.message = Some(auth_message.trim_end().to_string());
if let Some(message) = &mut greeter.message {
message.push('\n');
}
}
Request::PostAuthMessageResponse { response: None }.write_to(stream)?;
greeter.request = None;
let response = Response::read_from(stream)?;
parse_response(greeter, stream, response)?;
}
},
Response::Success => match greeter.done {
true => crate::exit(AuthStatus::Success, stream),
false => {
if let Some(command) = &greeter.command {
greeter.done = true;
greeter.request = Some(Request::StartSession {
cmd: vec![command.clone()],
})
} else {
crate::exit(AuthStatus::Failure, stream);
}
}
},
Response::Error {
error_type,
description,
} => match error_type {
ErrorType::AuthError => {
crate::exit(AuthStatus::Failure, stream);
}
ErrorType::Error => {
Request::CancelSession.write_to(stream).unwrap();
greeter.message = Some(description)
}
},
}
Ok(())
pub fn cancel(greeter: &mut Greeter) {
Request::CancelSession.write_to(&mut greeter.stream).unwrap();
}

View File

@ -1,119 +1,90 @@
use std::{error::Error, os::unix::net::UnixStream};
use std::error::Error;
use greetd_ipc::{codec::SyncCodec, Request};
use greetd_ipc::Request;
use termion::event::Key;
use crate::{
event::{Event, Events},
AuthStatus, Greeter, Mode,
event::{Event, Events},
AuthStatus, Greeter, Mode,
};
pub fn handle(
greeter: &mut Greeter,
events: &Events,
stream: &mut UnixStream,
) -> Result<(), Box<dyn Error>> {
if let Event::Input(input) = events.next()? {
match input {
Key::Esc => {
Request::CancelSession.write_to(stream).unwrap();
crate::exit(AuthStatus::Success, stream);
pub fn handle(greeter: &mut Greeter, events: &Events) -> Result<(), Box<dyn Error>> {
if let Event::Input(input) = events.next()? {
match input {
Key::Esc => crate::exit(greeter, AuthStatus::Cancel),
Key::Left => greeter.cursor_offset -= 1,
Key::Right => greeter.cursor_offset += 1,
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(());
}
Key::Left => greeter.cursor_offset -= 1,
Key::Right => greeter.cursor_offset += 1,
greeter.request = Some(Request::CreateSession { username: greeter.username.clone() });
}
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()),
})
}
}
greeter.answer = String::new();
}
Key::Char(char) => match greeter.mode {
Mode::Username => {
let index = greeter.username.len() as i16 + greeter.cursor_offset;
greeter.username.insert(index as usize, char);
}
Mode::Password => {
let index = greeter.answer.len() as i16 + greeter.cursor_offset;
greeter.answer.insert(index as usize, char);
}
},
Key::Backspace => {
match greeter.mode {
Mode::Username => {
let index = greeter.username.len() as i16 + greeter.cursor_offset - 1;
if let Some(_) = greeter.username.chars().nth(index as usize) {
greeter.username.remove(index as usize);
}
}
Mode::Password => {
let index = greeter.answer.len() as i16 + greeter.cursor_offset - 1;
if let Some(_) = greeter.answer.chars().nth(index as usize) {
greeter.answer.remove(index as usize);
}
}
};
}
Key::Delete => {
match greeter.mode {
Mode::Username => {
let index = greeter.username.len() as i16 + greeter.cursor_offset;
if let Some(_) = greeter.username.chars().nth(index as usize) {
greeter.username.remove(index as usize);
greeter.cursor_offset += 1;
}
}
Mode::Password => {
let index = greeter.answer.len() as i16 + greeter.cursor_offset;
if let Some(_) = greeter.answer.chars().nth(index as usize) {
greeter.answer.remove(index as usize);
greeter.cursor_offset += 1;
}
}
};
}
_ => {}
Mode::Password => {
greeter.request = Some(Request::PostAuthMessageResponse {
response: Some(greeter.answer.clone()),
})
}
}
}
Ok(())
greeter.answer = String::new();
}
Key::Char(c) => insert_key(greeter, c),
Key::Backspace | Key::Delete => delete_key(greeter, input),
_ => {}
}
}
Ok(())
}
fn insert_key(greeter: &mut Greeter, c: char) {
let value = match greeter.mode {
Mode::Username => &mut greeter.username,
Mode::Password => &mut greeter.answer,
};
let index = value.len() as i16 + greeter.cursor_offset;
value.insert(index as usize, c);
}
fn delete_key(greeter: &mut Greeter, key: Key) {
let value = match greeter.mode {
Mode::Username => &mut greeter.username,
Mode::Password => &mut greeter.answer,
};
let index = match key {
Key::Backspace => value.len() as i16 + greeter.cursor_offset - 1,
Key::Delete => value.len() as i16 + greeter.cursor_offset,
_ => 0,
};
if let Some(_) = value.chars().nth(index as usize) {
value.remove(index as usize);
if let Key::Delete = key {
greeter.cursor_offset += 1;
}
}
}

View File

@ -1,148 +1,48 @@
mod config;
mod event;
mod info;
mod ipc;
mod keyboard;
mod ui;
use std::{env, error::Error, io, os::unix::net::UnixStream, process};
use std::{error::Error, io, process};
use getopts::{Matches, Options};
use greetd_ipc::{codec::SyncCodec, Request};
use termion::raw::IntoRawMode;
use tui::{backend::TermionBackend, Terminal};
use self::{event::Events, info::get_issue};
pub enum AuthStatus {
Success,
Failure,
}
pub enum Mode {
Username,
Password,
}
impl Default for Mode {
fn default() -> Mode {
Mode::Username
}
}
#[derive(Default)]
pub struct Greeter {
config: Option<Matches>,
pub command: Option<String>,
pub mode: Mode,
pub request: Option<Request>,
pub cursor_offset: i16,
pub username: String,
pub answer: String,
pub secret: bool,
pub prompt: String,
pub greeting: Option<String>,
pub message: Option<String>,
pub working: bool,
pub done: bool,
}
impl Greeter {
pub fn config(&self) -> Matches {
self.config.clone().unwrap()
}
pub fn width(&self) -> u16 {
if let Some(value) = self.config().opt_str("width") {
if let Ok(width) = value.parse::<u16>() {
return width;
}
}
80
}
}
pub use self::config::*;
use self::event::Events;
fn main() -> Result<(), Box<dyn Error>> {
let mut greeter = Greeter::default();
let mut greeter = config::parse_options(Greeter::new()?);
let mut opts = Options::new();
opts.optflag("h", "help", "show this usage information");
opts.optopt("c", "cmd", "command to run", "COMMAND");
opts.optopt(
"",
"width",
"width of the main prompt (default: 80)",
"WIDTH",
);
opts.optflag("i", "issue", "show the host's issue file");
opts.optopt(
"g",
"greeting",
"show custom text above login prompt",
"GREETING",
);
opts.optflag("t", "time", "display the current date and time");
let stdout = io::stdout().into_raw_mode()?;
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
greeter.config = match opts.parse(&env::args().collect::<Vec<String>>()) {
Ok(matches) => Some(matches),
terminal.clear()?;
Err(usage) => {
println!("{}", usage);
print_usage(opts);
process::exit(1);
}
};
let events = Events::new();
if greeter.config().opt_present("help") {
print_usage(opts);
std::process::exit(0);
}
if greeter.config().opt_present("issue") && greeter.config().opt_present("greeting") {
eprintln!("Only one of --issue and --greeting may be used at the same time");
print_usage(opts);
std::process::exit(0);
}
if greeter.config().opt_present("cmd") {
greeter.command = greeter.config().opt_str("cmd");
}
let mut stream = UnixStream::connect(env::var("GREETD_SOCK")?)?;
let stdout = io::stdout().into_raw_mode()?;
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
terminal.clear()?;
let events = Events::new();
if greeter.config().opt_present("issue") {
greeter.greeting = get_issue();
}
if greeter.config().opt_present("greeting") {
greeter.greeting = greeter.config().opt_str("greeting");
}
loop {
ui::draw(&mut terminal, &mut greeter)?;
ipc::handle(&mut greeter, &mut stream)?;
keyboard::handle(&mut greeter, &events, &mut stream)?;
}
loop {
ui::draw(&mut terminal, &mut greeter)?;
ipc::handle(&mut greeter)?;
keyboard::handle(&mut greeter, &events)?;
}
}
fn print_usage(opts: Options) {
eprint!("{}", opts.usage("Usage: greetd-tui [OPTIONS]"));
}
pub fn exit(greeter: &mut Greeter, status: AuthStatus) {
match status {
AuthStatus::Success => process::exit(0),
pub fn exit(status: AuthStatus, stream: &mut UnixStream) {
match status {
AuthStatus::Success => process::exit(0),
AuthStatus::Failure => {
Request::CancelSession.write_to(stream).unwrap();
process::exit(1);
}
AuthStatus::Failure => {
ipc::cancel(greeter);
process::exit(1);
}
AuthStatus::Cancel => {
ipc::cancel(greeter);
process::exit(0);
}
}
}

View File

@ -1,94 +1,86 @@
mod prompt;
use std::{
error::Error,
io::{self, Write},
error::Error,
io::{self, Write},
};
use chrono::prelude::*;
use termion::{cursor::Goto, raw::RawTerminal};
use tui::{
backend::TermionBackend,
layout::{Alignment, Constraint, Layout},
style::{Modifier, Style},
widgets::{Paragraph, Text},
Terminal,
backend::TermionBackend,
layout::{Alignment, Constraint, Layout},
style::{Modifier, Style},
widgets::{Paragraph, Text},
Terminal,
};
use crate::Greeter;
const EXIT: &'static str = "Exit";
const COMMAND: &'static str = "SESSION";
const COMMAND: &'static str = "COMMAND";
pub fn draw(
terminal: &mut Terminal<TermionBackend<RawTerminal<io::Stdout>>>,
greeter: &mut Greeter,
) -> Result<(), Box<dyn Error>> {
if greeter.working {
terminal.hide_cursor()?;
} else {
terminal.show_cursor()?;
pub fn draw(terminal: &mut Terminal<TermionBackend<RawTerminal<io::Stdout>>>, greeter: &mut Greeter) -> Result<(), Box<dyn Error>> {
if greeter.working {
terminal.hide_cursor()?;
} else {
terminal.show_cursor()?;
}
let mut cursor: Option<(u16, u16)> = None;
terminal.draw(|mut f| {
let size = f.size();
let chunks = Layout::default()
.constraints(
[
Constraint::Length(1), // Date and time
Constraint::Min(1), // Main area
Constraint::Length(1), // Status line
]
.as_ref(),
)
.split(size);
if greeter.config().opt_present("time") {
let time_text = [Text::raw(get_time())];
let time = Paragraph::new(time_text.iter()).alignment(Alignment::Center);
f.render_widget(time, chunks[0]);
}
let mut cursor: Option<(u16, u16)> = None;
let command = greeter.command.clone().unwrap_or("-".to_string());
let status_text = [status_label("ESC"), status_value(format!(" {} ", EXIT)), status_label(COMMAND), status_value(format!(" {} ", command))];
let status = Paragraph::new(status_text.iter());
terminal.draw(|mut f| {
let size = f.size();
let chunks = Layout::default()
.constraints(
[
Constraint::Length(1),
Constraint::Min(1),
Constraint::Length(1),
]
.as_ref(),
)
.split(size);
f.render_widget(status, chunks[2]);
if greeter.config().opt_present("time") {
let time_text = [Text::raw(get_time())];
let time = Paragraph::new(time_text.iter()).alignment(Alignment::Center);
cursor = self::prompt::draw(greeter, &mut f).ok();
})?;
f.render_widget(time, chunks[0]);
}
if let Some(cursor) = cursor {
write!(terminal.backend_mut(), "{}", Goto(cursor.0, cursor.1))?;
}
let command = greeter.command.clone().unwrap_or("-".to_string());
let status_text = [
status_label("ESC"),
status_value(format!(" {} ", EXIT)),
status_label(COMMAND),
status_value(format!(" {} ", command)),
];
let status = Paragraph::new(status_text.iter());
io::stdout().flush()?;
f.render_widget(status, chunks[2]);
cursor = self::prompt::draw(greeter, &mut f).ok();
})?;
if let Some(cursor) = cursor {
write!(terminal.backend_mut(), "{}", Goto(cursor.0, cursor.1))?;
}
io::stdout().flush()?;
Ok(())
Ok(())
}
fn get_time() -> String {
Local::now().format("%b, %d %h %Y - %H:%M").to_string()
Local::now().format("%b, %d %h %Y - %H:%M").to_string()
}
fn status_label<'s, S>(text: S) -> Text<'s>
where
S: Into<String>,
S: Into<String>,
{
Text::styled(text.into(), Style::default().modifier(Modifier::REVERSED))
Text::styled(text.into(), Style::default().modifier(Modifier::REVERSED))
}
fn status_value<'s, S>(text: S) -> Text<'s>
where
S: Into<String>,
S: Into<String>,
{
Text::raw(text.into())
Text::raw(text.into())
}

View File

@ -2,11 +2,11 @@ use std::{error::Error, io};
use termion::raw::RawTerminal;
use tui::{
backend::TermionBackend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Modifier, Style},
widgets::{Block, BorderType, Borders, Paragraph, Text},
Frame,
backend::TermionBackend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Modifier, Style},
widgets::{Block, BorderType, Borders, Paragraph, Text},
Frame,
};
use crate::{info::get_hostname, Greeter, Mode};
@ -22,192 +22,161 @@ const TITLE: &'static str = "Authenticate into";
const USERNAME: &'static str = "Username:";
const WORKING: &'static str = "Please wait...";
pub fn draw(
greeter: &mut Greeter,
f: &mut Frame<TermionBackend<RawTerminal<io::Stdout>>>,
) -> Result<(u16, u16), Box<dyn Error>> {
let size = f.size();
pub fn draw(greeter: &mut Greeter, f: &mut Frame<TermionBackend<RawTerminal<io::Stdout>>>) -> Result<(u16, u16), Box<dyn Error>> {
let size = f.size();
let width = greeter.width();
let height = get_height(&greeter);
let x = (size.width - width) / 2;
let y = (size.height - height) / 2;
let width = greeter.width();
let height = get_height(&greeter);
let x = (size.width - width) / 2;
let y = (size.height - height) / 2;
let container = Rect::new(x, y, width, height);
let frame = Rect::new(x + PADDING, y + PADDING, width - PADDING, height - PADDING);
let container = Rect::new(x, y, width, height);
let frame = Rect::new(x + PADDING, y + PADDING, width - PADDING, height - PADDING);
let hostname = format!(" {} {} ", TITLE, get_hostname());
let hostname = format!(" {} {} ", TITLE, get_hostname());
let block = Block::default().title(&hostname).borders(Borders::ALL).border_type(BorderType::Plain);
let block = Block::default()
.title(&hostname)
.borders(Borders::ALL)
.border_type(BorderType::Plain);
f.render_widget(block, container);
f.render_widget(block, container);
let (message, message_height) = get_message_height(greeter, 1, 1);
let (greeting, greeting_height) = get_greeting_height(greeter, 1, 0);
let (message, message_height) = get_message_height(greeter, 1, 1);
let (greeting, greeting_height) = get_greeting_height(greeter, 1, 0);
let constraints = [
Constraint::Length(greeting_height), // Greeting
Constraint::Length(2), // Username
Constraint::Length(if let Mode::Username = greeter.mode { message_height } else { 2 }), // Message or answer
Constraint::Length(if let Mode::Password = greeter.mode { message_height } else { 1 }), // Message
];
let constraints = [
Constraint::Length(greeting_height), // Greeting
Constraint::Length(2), // Username
Constraint::Length(if let Mode::Username = greeter.mode {
message_height
} else {
2
}), // Message or answer
Constraint::Length(if let Mode::Password = greeter.mode {
message_height
} else {
1
}), // Message
];
let chunks = Layout::default().direction(Direction::Vertical).constraints(constraints.as_ref()).split(frame);
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(constraints.as_ref())
.split(frame);
let cursor = chunks[USERNAME_INDEX];
let cursor = chunks[USERNAME_INDEX];
if let Some(greeting) = &greeting {
let greeting_text = [Text::raw(greeting.trim_end())];
let greeting_label = Paragraph::new(greeting_text.iter()).alignment(Alignment::Center);
if let Some(greeting) = &greeting {
let greeting_text = [Text::raw(greeting.trim_end())];
let greeting_label = Paragraph::new(greeting_text.iter()).alignment(Alignment::Center);
f.render_widget(greeting_label, chunks[GREETING_INDEX]);
}
f.render_widget(greeting_label, chunks[GREETING_INDEX]);
}
let username_text = [Text::styled(USERNAME, Style::default().modifier(Modifier::BOLD))];
let username_label = Paragraph::new(username_text.iter());
let username_text = [Text::styled(
USERNAME,
Style::default().modifier(Modifier::BOLD),
)];
let username_label = Paragraph::new(username_text.iter());
let username_value_text = [Text::raw(&greeter.username)];
let username_value = Paragraph::new(username_value_text.iter());
let username_value_text = [Text::raw(&greeter.username)];
let username_value = Paragraph::new(username_value_text.iter());
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),
);
let answer_text = if greeter.working {
[Text::raw(WORKING)]
} else {
[Text::styled(
&greeter.prompt,
Style::default().modifier(Modifier::BOLD),
)]
};
let answer_label = Paragraph::new(answer_text.iter());
let answer_text = if greeter.working {
[Text::raw(WORKING)]
} else {
[Text::styled(&greeter.prompt, Style::default().modifier(Modifier::BOLD))]
};
let answer_label = Paragraph::new(answer_text.iter());
f.render_widget(username_label, chunks[USERNAME_INDEX]);
f.render_widget(
username_value,
if let Mode::Password = greeter.mode {
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(
1 + chunks[USERNAME_INDEX].x + USERNAME.len() as u16,
chunks[USERNAME_INDEX].y,
30,
1,
chunks[ANSWER_INDEX].x + greeter.prompt.len() as u16,
chunks[ANSWER_INDEX].y,
get_input_width(greeter, &greeter.prompt),
1,
),
);
if let Mode::Password = greeter.mode {
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,
30,
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]),
}
}
if let Some(ref message) = message {
let message_text = [Text::raw(message)];
let message = Paragraph::new(message_text.iter());
match greeter.mode {
Mode::Username => {
let username = greeter.username.clone();
let offset = get_cursor_offset(greeter, username);
Ok((
2 + cursor.x + USERNAME.len() as u16 + offset as u16,
1 + cursor.y,
))
}
Mode::Password => {
let answer = greeter.answer.clone();
let offset = get_cursor_offset(greeter, answer);
if greeter.secret {
Ok((1 + cursor.x + greeter.prompt.len() as u16, 3 + cursor.y))
} else {
Ok((
1 + cursor.x + greeter.prompt.len() as u16 + offset as u16,
3 + cursor.y,
))
}
}
Mode::Username => f.render_widget(message, chunks[ANSWER_INDEX]),
Mode::Password => f.render_widget(message, chunks[MESSAGE_INDEX]),
}
}
match greeter.mode {
Mode::Username => {
let username = greeter.username.clone();
let offset = get_cursor_offset(greeter, username);
Ok((2 + cursor.x + USERNAME.len() as u16 + offset as u16, 1 + cursor.y))
}
Mode::Password => {
let answer = greeter.answer.clone();
let offset = get_cursor_offset(greeter, answer);
if greeter.secret {
Ok((1 + cursor.x + greeter.prompt.len() as u16, 3 + cursor.y))
} else {
Ok((1 + cursor.x + greeter.prompt.len() as u16 + offset as u16, 3 + cursor.y))
}
}
}
}
fn get_height(greeter: &Greeter) -> u16 {
let (_, message_height) = get_message_height(greeter, 2, 0);
let (_, greeting_height) = get_greeting_height(greeter, 1, 0);
let initial = match greeter.mode {
Mode::Username => 5,
Mode::Password => 7,
};
let (_, message_height) = get_message_height(greeter, 2, 0);
let (_, greeting_height) = get_greeting_height(greeter, 1, 0);
let initial = match greeter.mode {
Mode::Username => 5,
Mode::Password => 7,
};
initial + greeting_height + message_height
initial + greeting_height + message_height
}
fn get_greeting_height(greeter: &Greeter, padding: u16, fallback: u16) -> (Option<String>, u16) {
if let Some(greeting) = &greeter.greeting {
let width = greeter.width();
let wrapped = textwrap::fill(greeting, width as usize - 4);
let height = wrapped.trim_end().matches('\n').count();
if let Some(greeting) = &greeter.greeting {
let width = greeter.width();
let wrapped = textwrap::fill(greeting, width as usize - 4);
let height = wrapped.trim_end().matches('\n').count();
(Some(wrapped), height as u16 + 1 + padding)
} else {
(None, fallback)
}
(Some(wrapped), height as u16 + 1 + padding)
} else {
(None, fallback)
}
}
fn get_input_width(greeter: &Greeter, label: &str) -> u16 {
greeter.width() - label.len() as u16 - 4 - 1
}
fn get_message_height(greeter: &Greeter, padding: u16, fallback: u16) -> (Option<String>, u16) {
if let Some(message) = &greeter.message {
let width = greeter.width();
let wrapped = textwrap::fill(message.trim_end(), width as usize - 4);
let height = wrapped.trim_end().matches('\n').count();
if let Some(message) = &greeter.message {
let width = greeter.width();
let wrapped = textwrap::fill(message.trim_end(), width as usize - 4);
let height = wrapped.trim_end().matches('\n').count();
(Some(wrapped), height as u16 + padding)
} else {
(None, fallback)
}
(Some(wrapped), height as u16 + padding)
} else {
(None, fallback)
}
}
fn get_cursor_offset(greeter: &mut Greeter, text: String) -> i16 {
let mut offset = text.len() as i16 + greeter.cursor_offset;
if offset < 0 {
offset = 0;
greeter.cursor_offset = -(text.len() as i16);
}
if offset > text.len() as i16 {
offset = text.len() as i16;
greeter.cursor_offset = 0;
}
let mut offset = text.len() as i16 + greeter.cursor_offset;
if offset < 0 {
offset = 0;
greeter.cursor_offset = -(text.len() as i16);
}
if offset > text.len() as i16 {
offset = text.len() as i16;
greeter.cursor_offset = 0;
}
offset
offset
}