From 1594e0995153cf780f3bff596fc299e8ccbeabf3 Mon Sep 17 00:00:00 2001 From: Antoine POPINEAU Date: Wed, 18 Oct 2023 08:32:03 +0200 Subject: [PATCH] UI refactoring. --- src/greeter.rs | 90 +++++++++++++++++++++---------------------- src/info.rs | 14 +++++-- src/ipc.rs | 5 ++- src/keyboard.rs | 49 +++++++++++++---------- src/power.rs | 48 +++++++++++++---------- src/ui/common/menu.rs | 71 ++++++++++++++++++++++++++++++++++ src/ui/common/mod.rs | 1 + src/ui/mod.rs | 15 ++++---- src/ui/power.rs | 63 +++++------------------------- src/ui/sessions.rs | 67 ++++++++++++++------------------ src/ui/users.rs | 59 ++++++---------------------- 11 files changed, 240 insertions(+), 242 deletions(-) create mode 100644 src/ui/common/menu.rs create mode 100644 src/ui/common/mod.rs diff --git a/src/greeter.rs b/src/greeter.rs index 77b8ef5..297e87f 100644 --- a/src/greeter.rs +++ b/src/greeter.rs @@ -1,5 +1,4 @@ use std::{ - collections::HashMap, convert::TryInto, env, error::Error, @@ -25,6 +24,12 @@ use zeroize::Zeroize; use crate::{ info::{get_issue, get_last_session, get_last_session_path, get_last_user_name, get_last_user_session, get_last_user_session_path, get_last_user_username, get_min_max_uids, get_users}, power::PowerOption, + ui::{ + common::menu::Menu, + power::Power, + sessions::{Session, SessionType}, + users::User, + }, }; const DEFAULT_LOCALE: Locale = Locale::en_US; @@ -60,34 +65,6 @@ pub enum Mode { Processing, } -#[derive(SmartDefault, Debug, Copy, Clone, PartialEq)] -pub enum SessionType { - X11, - Wayland, - TTY, - #[default] - None, -} - -impl SessionType { - pub fn to_xdg_session_type(&self) -> &'static str { - match self { - SessionType::X11 => "x11", - SessionType::Wayland => "wayland", - SessionType::TTY => "tty", - SessionType::None => "unspecified", - } - } -} - -#[derive(SmartDefault, Clone)] -pub struct Session { - pub name: String, - pub command: String, - pub session_type: SessionType, - pub path: Option, -} - #[derive(SmartDefault)] pub struct Greeter { #[default(DEFAULT_LOCALE)] @@ -100,18 +77,14 @@ pub struct Greeter { pub previous_mode: Mode, pub cursor_offset: i16, - pub users: Vec<(String, Option)>, - pub selected_user: usize, + pub users: Menu, pub command: Option, pub new_command: String, pub session_path: Option, pub session_paths: Vec<(PathBuf, SessionType)>, - pub sessions: Vec, - pub selected_session: usize, + pub sessions: Menu, pub xsession_wrapper: Option, - pub selected_power_option: usize, - pub username: String, pub username_mask: Option, pub prompt: Option, @@ -129,7 +102,7 @@ pub struct Greeter { pub greeting: Option, pub message: Option, - pub power_commands: HashMap, + pub power_commands: Menu, pub power_command: Option, pub power_command_notify: Arc, pub power_setsid: bool, @@ -150,10 +123,21 @@ impl Greeter { let mut greeter = Self::default(); greeter.set_locale(); + + greeter.power_commands = Menu { + title: fl!("title_power"), + options: Default::default(), + selected: 0, + }; + greeter.parse_options().await; - greeter.sessions = crate::info::get_sessions(&greeter).unwrap_or_default(); + greeter.sessions = Menu { + title: fl!("title_session"), + options: crate::info::get_sessions(&greeter).unwrap_or_default(), + selected: 0, + }; - if let Some(Session { command, .. }) = greeter.sessions.get(0) { + if let Some(Session { command, .. }) = greeter.sessions.options.get(0) { if greeter.command.is_none() { greeter.command = Some(command.clone()); } @@ -186,7 +170,12 @@ impl Greeter { } } - greeter.selected_session = greeter.sessions.iter().position(|Session { path, .. }| path.as_deref() == greeter.session_path.as_deref()).unwrap_or(0); + greeter.sessions.selected = greeter + .sessions + .options + .iter() + .position(|Session { path, .. }| path.as_deref() == greeter.session_path.as_deref()) + .unwrap_or(0); greeter } @@ -391,7 +380,11 @@ impl Greeter { process::exit(1); } - self.users = get_users(min_uid, max_uid); + self.users = Menu { + title: fl!("title_users"), + options: get_users(min_uid, max_uid), + selected: 0, + } } if self.config().opt_present("remember-session") && self.config().opt_present("remember-user-session") { @@ -428,12 +421,17 @@ impl Greeter { self.greeting = get_issue(); } - if let Some(command) = self.config().opt_str("power-shutdown") { - self.power_commands.insert(PowerOption::Shutdown, command); - } - if let Some(command) = self.config().opt_str("power-reboot") { - self.power_commands.insert(PowerOption::Reboot, command); - } + self.power_commands.options.push(Power { + action: PowerOption::Shutdown, + label: fl!("shutdown"), + command: self.config().opt_str("power-shutdown"), + }); + + self.power_commands.options.push(Power { + action: PowerOption::Reboot, + label: fl!("reboot"), + command: self.config().opt_str("power-reboot"), + }); self.power_setsid = !self.config().opt_present("power-no-setsid"); diff --git a/src/info.rs b/src/info.rs index ce4d2b8..f585b49 100644 --- a/src/info.rs +++ b/src/info.rs @@ -11,7 +11,13 @@ use ini::Ini; use lazy_static::lazy_static; use nix::sys::utsname; -use crate::{Greeter, Session, SessionType}; +use crate::{ + ui::{ + sessions::{Session, SessionType}, + users::User, + }, + Greeter, +}; const LAST_USER_USERNAME: &str = "/var/cache/tuigreet/lastuser"; const LAST_USER_NAME: &str = "/var/cache/tuigreet/lastuser-name"; @@ -147,13 +153,13 @@ pub fn write_last_user_session(username: &str, session: &str) { let _ = fs::write(format!("{LAST_SESSION}-{username}"), session); } -pub fn get_users(min_uid: u16, max_uid: u16) -> Vec<(String, Option)> { +pub fn get_users(min_uid: u16, max_uid: u16) -> Vec { match File::open("/etc/passwd") { Err(_) => vec![], Ok(file) => { let file = BufReader::new(file); - let users: Vec<(String, Option)> = file + let users: Vec = file .lines() .filter_map(|line| { line @@ -176,7 +182,7 @@ pub fn get_users(min_uid: u16, max_uid: u16) -> Vec<(String, Option)> { .ok() .flatten() .filter(|(uid, _, _)| uid >= &min_uid && uid <= &max_uid) - .map(|(_, username, name)| (username, name)) + .map(|(_, username, name)| User { username, name }) }) .collect(); diff --git a/src/ipc.rs b/src/ipc.rs index 36ec5e2..b347af2 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -8,7 +8,8 @@ use tokio::sync::{ use crate::{ info::{write_last_user_session, write_last_user_session_path, write_last_username}, - AuthStatus, Greeter, Mode, Session, SessionType, + ui::sessions::{Session, SessionType}, + AuthStatus, Greeter, Mode, }; #[derive(Clone)] @@ -121,7 +122,7 @@ impl Ipc { greeter.done = true; greeter.mode = Mode::Processing; - let session = greeter.sessions.get(greeter.selected_session).filter(|s| &s.command == command); + let session = greeter.sessions.options.get(greeter.sessions.selected).filter(|s| &s.command == command); let mut command = command.clone(); let mut env = vec![]; diff --git a/src/keyboard.rs b/src/keyboard.rs index a3def69..f80837f 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -9,8 +9,8 @@ use crate::{ info::{delete_last_session_path, get_last_user_session, get_last_user_session_path, write_last_session, write_last_session_path}, ipc::Ipc, power::power, - ui::POWER_OPTIONS, - Greeter, Mode, Session, + ui::{sessions::Session, users::User}, + Greeter, Mode, }; pub async fn handle(greeter: Arc>, events: &mut Events, ipc: Ipc) -> Result<(), Box> { @@ -84,40 +84,40 @@ pub async fn handle(greeter: Arc>, events: &mut Events, ipc: Ipc KeyEvent { code: KeyCode::Up, .. } => { if let Mode::Users = greeter.mode { - if greeter.selected_user > 0 { - greeter.selected_user -= 1; + if greeter.users.selected > 0 { + greeter.users.selected -= 1; } } if let Mode::Sessions = greeter.mode { - if greeter.selected_session > 0 { - greeter.selected_session -= 1; + if greeter.sessions.selected > 0 { + greeter.sessions.selected -= 1; } } if let Mode::Power = greeter.mode { - if greeter.selected_power_option > 0 { - greeter.selected_power_option -= 1; + if greeter.power_commands.selected > 0 { + greeter.power_commands.selected -= 1; } } } KeyEvent { code: KeyCode::Down, .. } => { if let Mode::Users = greeter.mode { - if greeter.selected_user < greeter.users.len() - 1 { - greeter.selected_user += 1; + if greeter.users.selected < greeter.users.options.len() - 1 { + greeter.users.selected += 1; } } if let Mode::Sessions = greeter.mode { - if greeter.selected_session < greeter.sessions.len() - 1 { - greeter.selected_session += 1; + if greeter.sessions.selected < greeter.sessions.options.len() - 1 { + greeter.sessions.selected += 1; } } if let Mode::Power = greeter.mode { - if greeter.selected_power_option < POWER_OPTIONS.len() - 1 { - greeter.selected_power_option += 1; + if greeter.power_commands.selected < greeter.power_commands.options.len() - 1 { + greeter.power_commands.selected += 1; } } } @@ -176,7 +176,7 @@ pub async fn handle(greeter: Arc>, events: &mut Events, ipc: Ipc } Mode::Command => { - greeter.selected_session = 0; + greeter.sessions.selected = 0; greeter.command = Some(greeter.new_command.clone()); if greeter.remember_session { @@ -188,9 +188,9 @@ pub async fn handle(greeter: Arc>, events: &mut Events, ipc: Ipc } Mode::Users => { - let username = greeter.users.get(greeter.selected_user).cloned(); + let username = greeter.users.options.get(greeter.users.selected).cloned(); - if let Some((username, name)) = username { + if let Some(User { username, name }) = username { greeter.username = username; greeter.username_mask = name; } @@ -199,7 +199,7 @@ pub async fn handle(greeter: Arc>, events: &mut Events, ipc: Ipc } Mode::Sessions => { - let session = greeter.sessions.get(greeter.selected_session).cloned(); + let session = greeter.sessions.options.get(greeter.sessions.selected).cloned(); if let Some(Session { path, command, .. }) = session { if greeter.remember_session { @@ -218,8 +218,10 @@ pub async fn handle(greeter: Arc>, events: &mut Events, ipc: Ipc } Mode::Power => { - if let Some((option, _)) = POWER_OPTIONS.get(greeter.selected_power_option) { - power(&mut greeter, *option); + let power_command = greeter.power_commands.options.get(greeter.power_commands.selected).cloned(); + + if let Some(command) = power_command { + power(&mut greeter, command.action); } greeter.mode = greeter.previous_mode; @@ -304,7 +306,12 @@ async fn validate_username(greeter: &mut Greeter, ipc: &Ipc) { if greeter.remember_user_session { if let Ok(last_session) = get_last_user_session_path(&greeter.username) { - greeter.selected_session = greeter.sessions.iter().position(|Session { path, .. }| path.as_deref() == Some(last_session.as_ref())).unwrap_or(0); + greeter.sessions.selected = greeter + .sessions + .options + .iter() + .position(|Session { path, .. }| path.as_deref() == Some(last_session.as_ref())) + .unwrap_or(0); } if let Ok(command) = get_last_user_session(&greeter.username) { diff --git a/src/power.rs b/src/power.rs index fb58432..8153fd5 100644 --- a/src/power.rs +++ b/src/power.rs @@ -2,29 +2,20 @@ use std::{process::Stdio, sync::Arc}; use tokio::{process::Command, sync::RwLock}; -use crate::{Greeter, Mode}; +use crate::{ui::power::Power, Greeter, Mode}; -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(SmartDefault, Clone, Copy, PartialEq, Eq, Hash)] pub enum PowerOption { + #[default] Shutdown, Reboot, } pub fn power(greeter: &mut Greeter, option: PowerOption) { - let mut command = match greeter.power_commands.get(&option) { - None => { - let mut command = Command::new("shutdown"); - - match option { - PowerOption::Shutdown => command.arg("-h"), - PowerOption::Reboot => command.arg("-r"), - }; + let command = match greeter.power_commands.options.iter().find(|opt| opt.action == option) { + None => None, - command.arg("now"); - command - } - - Some(args) => { + Some(Power { command: Some(args), .. }) => { let command = match greeter.power_setsid { true => { let mut command = Command::new("setsid"); @@ -41,16 +32,31 @@ pub fn power(greeter: &mut Greeter, option: PowerOption) { } }; - command + Some(command) + } + + Some(_) => { + let mut command = Command::new("shutdown"); + + match option { + PowerOption::Shutdown => command.arg("-h"), + PowerOption::Reboot => command.arg("-r"), + }; + + command.arg("now"); + + Some(command) } }; - command.stdin(Stdio::null()); - command.stdout(Stdio::null()); - command.stderr(Stdio::null()); + if let Some(mut command) = command { + command.stdin(Stdio::null()); + command.stdout(Stdio::null()); + command.stderr(Stdio::null()); - greeter.power_command = Some(command); - greeter.power_command_notify.notify_one(); + greeter.power_command = Some(command); + greeter.power_command_notify.notify_one(); + } } pub async fn run(greeter: &Arc>, mut command: Command) { diff --git a/src/ui/common/menu.rs b/src/ui/common/menu.rs new file mode 100644 index 0000000..f9da982 --- /dev/null +++ b/src/ui/common/menu.rs @@ -0,0 +1,71 @@ +use std::error::Error; + +use tui::{ + prelude::Rect, + style::{Modifier, Style}, + text::Span, + widgets::{Block, BorderType, Borders, Paragraph}, +}; + +use crate::{ + ui::{ + util::{get_rect_bounds, titleize}, + Frame, + }, + Greeter, +}; + +pub trait MenuItem { + fn format(&self) -> String; +} + +#[derive(Default)] +pub struct Menu +where + T: MenuItem, +{ + pub title: String, + pub options: Vec, + pub selected: usize, +} + +impl Menu +where + T: MenuItem, +{ + pub fn draw(&self, greeter: &Greeter, f: &mut Frame) -> Result<(u16, u16), Box> { + let size = f.size(); + let (x, y, width, height) = get_rect_bounds(greeter, size, self.options.len()); + + let container = Rect::new(x, y, width, height); + + let title = Span::from(titleize(&self.title)); + let block = Block::default().title(title).borders(Borders::ALL).border_type(BorderType::Plain); + + for (index, option) in self.options.iter().enumerate() { + let name = option.format(); + let name = format!("{:1$}", name, greeter.width() as usize - 4); + + let frame = Rect::new(x + 2, y + 2 + index as u16, width - 4, 1); + let option_text = self.get_option(name, index); + let option = Paragraph::new(option_text); + + f.render_widget(option, frame); + } + + f.render_widget(block, container); + + Ok((1, 1)) + } + + fn get_option<'g, S>(&self, name: S, index: usize) -> Span<'g> + where + S: Into, + { + if self.selected == index { + Span::styled(name.into(), Style::default().add_modifier(Modifier::REVERSED)) + } else { + Span::from(name.into()) + } + } +} diff --git a/src/ui/common/mod.rs b/src/ui/common/mod.rs new file mode 100644 index 0000000..b9a0e3e --- /dev/null +++ b/src/ui/common/mod.rs @@ -0,0 +1 @@ +pub mod menu; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 7014b10..512ade0 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,10 +1,11 @@ mod command; +pub mod common; mod i18n; -mod power; +pub mod power; mod processing; mod prompt; -mod sessions; -mod users; +pub mod sessions; +pub mod users; mod util; use std::{ @@ -30,7 +31,7 @@ use crate::{ Greeter, Mode, }; -pub use self::{i18n::MESSAGES, power::OPTIONS as POWER_OPTIONS}; +pub use self::i18n::MESSAGES; const TITLEBAR_INDEX: usize = 1; const STATUSBAR_INDEX: usize = 3; @@ -109,9 +110,9 @@ pub async fn draw(greeter: Arc>, terminal: &mut Term) -> Result< let cursor = match greeter.mode { Mode::Command => self::command::draw(&mut greeter, f).ok(), - Mode::Sessions => self::sessions::draw(&mut greeter, f).ok(), - Mode::Power => self::power::draw(&mut greeter, f).ok(), - Mode::Users => self::users::draw(&mut greeter, f).ok(), + Mode::Sessions => greeter.sessions.draw(&greeter, f).ok(), + Mode::Power => greeter.power_commands.draw(&greeter, f).ok(), + Mode::Users => greeter.users.draw(&greeter, f).ok(), Mode::Processing => self::processing::draw(&mut greeter, f).ok(), _ => self::prompt::draw(&mut greeter, f).ok(), }; diff --git a/src/ui/power.rs b/src/ui/power.rs index ac8dff5..44acb4d 100644 --- a/src/ui/power.rs +++ b/src/ui/power.rs @@ -1,59 +1,14 @@ -use std::error::Error; +use crate::{power::PowerOption, ui::common::menu::MenuItem}; -use lazy_static::lazy_static; -use tui::{ - layout::Rect, - style::{Modifier, Style}, - text::Span, - widgets::{Block, BorderType, Borders, Paragraph}, -}; - -use crate::{ - power::PowerOption, - ui::{util::*, Frame}, - Greeter, -}; - -lazy_static! { - pub static ref OPTIONS: [(PowerOption, String); 2] = { - let shutdown = fl!("shutdown"); - let reboot = fl!("reboot"); - - [(PowerOption::Shutdown, shutdown), (PowerOption::Reboot, reboot)] - }; -} - -pub fn draw(greeter: &mut Greeter, f: &mut Frame<'_>) -> Result<(u16, u16), Box> { - let size = f.size(); - let (x, y, width, height) = get_rect_bounds(greeter, size, OPTIONS.len()); - - let container = Rect::new(x, y, width, height); - - let title = Span::from(titleize(&fl!("title_power"))); - let block = Block::default().title(title).borders(Borders::ALL).border_type(BorderType::Plain); - - for (index, (_, label)) in OPTIONS.iter().enumerate() { - let name = format!("{:1$}", label, greeter.width() as usize - 4); - - let frame = Rect::new(x + 2, y + 2 + index as u16, width - 4, 1); - let option_text = get_option(greeter, name, index); - let option = Paragraph::new(option_text); - - f.render_widget(option, frame); - } - - f.render_widget(block, container); - - Ok((1, 1)) +#[derive(Default, Clone)] +pub struct Power { + pub action: PowerOption, + pub label: String, + pub command: Option, } -fn get_option<'g, S>(greeter: &Greeter, name: S, index: usize) -> Span<'g> -where - S: Into, -{ - if greeter.selected_power_option == index { - Span::styled(name.into(), Style::default().add_modifier(Modifier::REVERSED)) - } else { - Span::from(name.into()) +impl MenuItem for Power { + fn format(&self) -> String { + self.label.clone() } } diff --git a/src/ui/sessions.rs b/src/ui/sessions.rs index ac95fa0..bf6e1ab 100644 --- a/src/ui/sessions.rs +++ b/src/ui/sessions.rs @@ -1,48 +1,37 @@ -use std::error::Error; +use std::path::PathBuf; -use tui::{ - layout::Rect, - style::{Modifier, Style}, - text::Span, - widgets::{Block, BorderType, Borders, Paragraph}, -}; +use super::common::menu::MenuItem; -use crate::{ - ui::{util::*, Frame}, - Greeter, -}; - -pub fn draw(greeter: &mut Greeter, f: &mut Frame) -> Result<(u16, u16), Box> { - let size = f.size(); - let (x, y, width, height) = get_rect_bounds(greeter, size, greeter.sessions.len()); - - let container = Rect::new(x, y, width, height); - - let title = Span::from(titleize(&fl!("title_session"))); - let block = Block::default().title(title).borders(Borders::ALL).border_type(BorderType::Plain); - - for (index, session) in greeter.sessions.iter().enumerate() { - let name = format!("{:1$}", session.name, greeter.width() as usize - 4); - - let frame = Rect::new(x + 2, y + 2 + index as u16, width - 4, 1); - let option_text = get_option(greeter, name, index); - let option = Paragraph::new(option_text); +#[derive(SmartDefault, Debug, Copy, Clone, PartialEq)] +pub enum SessionType { + X11, + Wayland, + TTY, + #[default] + None, +} - f.render_widget(option, frame); +impl SessionType { + pub fn to_xdg_session_type(&self) -> &'static str { + match self { + SessionType::X11 => "x11", + SessionType::Wayland => "wayland", + SessionType::TTY => "tty", + SessionType::None => "unspecified", + } } +} - f.render_widget(block, container); - - Ok((1, 1)) +#[derive(SmartDefault, Clone)] +pub struct Session { + pub name: String, + pub command: String, + pub session_type: SessionType, + pub path: Option, } -fn get_option<'g, S>(greeter: &Greeter, name: S, index: usize) -> Span<'g> -where - S: Into, -{ - if greeter.selected_session == index { - Span::styled(name.into(), Style::default().add_modifier(Modifier::REVERSED)) - } else { - Span::from(name.into()) +impl MenuItem for Session { + fn format(&self) -> String { + self.name.clone() } } diff --git a/src/ui/users.rs b/src/ui/users.rs index 5fb8ed3..745c7f5 100644 --- a/src/ui/users.rs +++ b/src/ui/users.rs @@ -1,53 +1,16 @@ -use std::error::Error; +use super::common::menu::MenuItem; -use tui::{ - layout::Rect, - style::{Modifier, Style}, - text::Span, - widgets::{Block, BorderType, Borders, Paragraph}, -}; - -use crate::{ - ui::{util::*, Frame}, - Greeter, -}; - -pub fn draw(greeter: &mut Greeter, f: &mut Frame) -> Result<(u16, u16), Box> { - let size = f.size(); - let (x, y, width, height) = get_rect_bounds(greeter, size, greeter.users.len()); - - let container = Rect::new(x, y, width, height); - - let title = Span::from(titleize(&fl!("title_users"))); - let block = Block::default().title(title).borders(Borders::ALL).border_type(BorderType::Plain); - - for (index, (username, name)) in greeter.users.iter().enumerate() { - let name = match name { - Some(name) => format!("{name} ({username})"), - None => username.clone(), - }; - - let name = format!("{:1$}", name, greeter.width() as usize - 4); - - let frame = Rect::new(x + 2, y + 2 + index as u16, width - 4, 1); - let option_text = get_option(greeter, name, index); - let option = Paragraph::new(option_text); - - f.render_widget(option, frame); - } - - f.render_widget(block, container); - - Ok((1, 1)) +#[derive(Default, Clone)] +pub struct User { + pub username: String, + pub name: Option, } -fn get_option<'g, S>(greeter: &Greeter, name: S, index: usize) -> Span<'g> -where - S: Into, -{ - if greeter.selected_user == index { - Span::styled(name.into(), Style::default().add_modifier(Modifier::REVERSED)) - } else { - Span::from(name.into()) +impl MenuItem for User { + fn format(&self) -> String { + match &self.name { + Some(name) => format!("{name} ({})", self.username), + None => self.username.clone(), + } } }