mirror of
https://github.com/apognu/tuigreet.git
synced 2024-11-22 04:22:27 +03:00
Refactored code and applied rustfmt.
This commit is contained in:
parent
e850f93269
commit
372c6b7a46
4
.rustfmt.toml
Normal file
4
.rustfmt.toml
Normal file
@ -0,0 +1,4 @@
|
||||
fn_args_density = "Compressed"
|
||||
merge_imports = true
|
||||
max_width = 200
|
||||
tab_spaces = 2
|
@ -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
130
src/config.rs
Normal 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]"));
|
||||
}
|
72
src/event.rs
72
src/event.rs
@ -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()
|
||||
}
|
||||
}
|
||||
|
38
src/info.rs
38
src/info.rs
@ -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
|
||||
}
|
||||
|
161
src/ipc.rs
161
src/ipc.rs
@ -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();
|
||||
}
|
||||
|
185
src/keyboard.rs
185
src/keyboard.rs
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
154
src/main.rs
154
src/main.rs
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
110
src/ui/mod.rs
110
src/ui/mod.rs
@ -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())
|
||||
}
|
||||
|
279
src/ui/prompt.rs
279
src/ui/prompt.rs
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user