mirror of
https://github.com/apognu/tuigreet.git
synced 2024-10-05 20:07:32 +03:00
Compare commits
15 Commits
8275064fc2
...
e8c6b57dda
Author | SHA1 | Date | |
---|---|---|---|
|
e8c6b57dda | ||
|
86bf7bbc15 | ||
|
976534d90d | ||
|
0baa018679 | ||
|
e5c8c996d5 | ||
|
8a3643f324 | ||
|
4ac12261cc | ||
|
598a997d45 | ||
|
18bc57c379 | ||
|
ca5762d56a | ||
|
6239bf0f31 | ||
|
f67e43ffa0 | ||
|
96f7d28377 | ||
|
dd56f3fb7a | ||
|
59c4fa4cfe |
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1690,7 +1690,7 @@ checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
|
||||
|
||||
[[package]]
|
||||
name = "tuigreet"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossterm",
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "tuigreet"
|
||||
version = "0.9.0"
|
||||
authors = ["Antoine POPINEAU <antoine.popineau@appscho.com>"]
|
||||
version = "0.9.1"
|
||||
authors = ["Antoine POPINEAU <antoine@popineau.eu>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
|
15
README.md
15
README.md
@ -138,6 +138,21 @@ Please refer to the snippet below for the minimal `tuigreet` configuration:
|
||||
|
||||
Pre-built binaries of `tuigreet` for several architectures can be found in the [releases](https://github.com/apognu/tuigreet/releases) section of this repository. The [tip prerelease](https://github.com/apognu/tuigreet/releases/tag/tip) is continuously built and kept in sync with the `master` branch.
|
||||
|
||||
## Running the tests
|
||||
|
||||
Tests from the default features should run without any special consideration by running `cargo test`.
|
||||
|
||||
If you intend to run the whole test suite, you will need to perform some setup. One of our features uses NSS to list and filter existing users on the system, and in order not to rely on actual users being created on the host, we use [libnss_wrapper](https://cwrap.org/nss_wrapper.html) to mock responses from NSS. Without this, the tests would use the real user list from your system and probably fail because it cannot find the one it looks for.
|
||||
|
||||
After installing `libnss_wrapper` on your system (or compiling it to get the `.so`), you can run those specific tests as such:
|
||||
|
||||
```
|
||||
$ export NSS_WRAPPER_PASSWD=contrib/fixtures/passwd
|
||||
$ export NSS_WRAPPER_GROUP=contrib/fixtures/group
|
||||
$ LD_PRELOAD=/path/to/libnss_wrapper.so cargo test --features nsswrapper nsswrapper_ # To run those tests specifically
|
||||
$ LD_PRELOAD=/path/to/libnss_wrapper.so cargo test --all-features # To run the whole test suite
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `/etc/greetd/config.toml` and set the `command` setting to use `tuigreet`:
|
||||
|
@ -21,6 +21,7 @@ new_command = New command:
|
||||
shutdown = Shut down
|
||||
reboot = Reboot
|
||||
|
||||
command_missing = No command configured
|
||||
command_exited = Command exited with
|
||||
command_failed = Command failed
|
||||
|
||||
|
@ -21,6 +21,7 @@ command = Nouvelle commande :
|
||||
shutdown = Éteindre
|
||||
reboot = Redémarrer
|
||||
|
||||
command_missing = Aucune commande configurée
|
||||
command_exited = La commande a retourné
|
||||
command_failed = Échec de la commande
|
||||
|
||||
|
@ -105,6 +105,10 @@ tuigreet - A graphical console greeter for greetd
|
||||
*--prompt-padding ROWS*
|
||||
Add spacing between form fields.
|
||||
|
||||
*--greet-align [left|center|right]*
|
||||
Alignment of the greeting text in the main prompt container
|
||||
(default: 'center').
|
||||
|
||||
*--power-shutdown CMD [ARGS]...*
|
||||
Customize the command run when instructed to shut down the machine. This must
|
||||
be a non-interactive command (sudo cannot prompt for a password, for example).
|
||||
|
@ -23,9 +23,7 @@ use zeroize::Zeroize;
|
||||
|
||||
use crate::{
|
||||
event::Event,
|
||||
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_sessions, get_users,
|
||||
},
|
||||
info::{get_issue, get_last_command, get_last_session_path, get_last_user_command, get_last_user_name, get_last_user_session, get_last_user_username, get_min_max_uids, get_sessions, get_users},
|
||||
power::PowerOption,
|
||||
ui::{
|
||||
common::{masked::MaskedString, menu::Menu, style::Theme},
|
||||
@ -91,6 +89,15 @@ impl SecretDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
// This enum models text alignment options
|
||||
#[derive(SmartDefault, Debug, Clone)]
|
||||
pub enum GreetAlign {
|
||||
#[default]
|
||||
Center,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
pub struct Greeter {
|
||||
pub debug: bool,
|
||||
@ -220,28 +227,39 @@ impl Greeter {
|
||||
greeter.connect().await;
|
||||
}
|
||||
|
||||
let sessions = get_sessions(&greeter).unwrap_or_default();
|
||||
|
||||
if let SessionSource::None = greeter.session_source {
|
||||
if !sessions.is_empty() {
|
||||
greeter.session_source = SessionSource::Session(0);
|
||||
}
|
||||
}
|
||||
|
||||
greeter.sessions = Menu {
|
||||
title: fl!("title_session"),
|
||||
options: get_sessions(&greeter).unwrap_or_default(),
|
||||
options: sessions,
|
||||
selected: 0,
|
||||
};
|
||||
|
||||
// If we should remember the last logged-in user.
|
||||
if greeter.remember {
|
||||
if let Some(username) = get_last_user_username() {
|
||||
greeter.username = MaskedString::from(username.clone(), get_last_user_name());
|
||||
greeter.username = MaskedString::from(username, get_last_user_name());
|
||||
|
||||
// If, on top of that, we should remember their last session.
|
||||
if greeter.remember_user_session {
|
||||
if let Ok(ref session_path) = get_last_user_session_path(&username) {
|
||||
// Set the selected menu option and the session source.
|
||||
greeter.sessions.selected = greeter.sessions.options.iter().position(|Session { path, .. }| path.as_deref() == Some(session_path)).unwrap_or(0);
|
||||
greeter.session_source = SessionSource::Session(greeter.sessions.selected);
|
||||
// See if we have the last free-form command from the user.
|
||||
if let Ok(command) = get_last_user_command(greeter.username.get()) {
|
||||
greeter.session_source = SessionSource::Command(command);
|
||||
}
|
||||
|
||||
// See if we have the last free-form command from the user.
|
||||
if let Ok(command) = get_last_user_session(&username) {
|
||||
greeter.session_source = SessionSource::Command(command);
|
||||
// If a session was saved, use it and its name.
|
||||
if let Ok(ref session_path) = get_last_user_session(greeter.username.get()) {
|
||||
// Set the selected menu option and the session source.
|
||||
if let Some(index) = greeter.sessions.options.iter().position(|Session { path, .. }| path.as_deref() == Some(session_path)) {
|
||||
greeter.sessions.selected = index;
|
||||
greeter.session_source = SessionSource::Session(greeter.sessions.selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -249,13 +267,15 @@ impl Greeter {
|
||||
|
||||
// Same thing, but not user specific.
|
||||
if greeter.remember_session {
|
||||
if let Ok(ref session_path) = get_last_session_path() {
|
||||
greeter.sessions.selected = greeter.sessions.options.iter().position(|Session { path, .. }| path.as_deref() == Some(session_path)).unwrap_or(0);
|
||||
greeter.session_source = SessionSource::Session(greeter.sessions.selected);
|
||||
if let Ok(command) = get_last_command() {
|
||||
greeter.session_source = SessionSource::Command(command.trim().to_string());
|
||||
}
|
||||
|
||||
if let Ok(command) = get_last_session() {
|
||||
greeter.session_source = SessionSource::Command(command.trim().to_string());
|
||||
if let Ok(ref session_path) = get_last_session_path() {
|
||||
if let Some(index) = greeter.sessions.options.iter().position(|Session { path, .. }| path.as_deref() == Some(session_path)) {
|
||||
greeter.sessions.selected = index;
|
||||
greeter.session_source = SessionSource::Session(greeter.sessions.selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,7 +315,7 @@ impl Greeter {
|
||||
self.connect().await;
|
||||
}
|
||||
|
||||
// Connect to `greetd` and return a strea we can safely write to.
|
||||
// Connect to `greetd` and return a stream we can safely write to.
|
||||
pub async fn connect(&mut self) {
|
||||
match UnixStream::connect(&self.socket).await {
|
||||
Ok(stream) => self.stream = Some(Arc::new(RwLock::new(stream))),
|
||||
@ -365,6 +385,18 @@ impl Greeter {
|
||||
1
|
||||
}
|
||||
|
||||
pub fn greet_align(&self) -> GreetAlign {
|
||||
if let Some(value) = self.option("greet-align") {
|
||||
match value.as_str() {
|
||||
"left" => GreetAlign::Left,
|
||||
"right" => GreetAlign::Right,
|
||||
_ => GreetAlign::Center,
|
||||
}
|
||||
} else {
|
||||
GreetAlign::default()
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the locale that will be used for this invocation from environment.
|
||||
fn set_locale(&mut self) {
|
||||
let locale = DesktopLanguageRequester::requested_languages()
|
||||
@ -409,6 +441,12 @@ impl Greeter {
|
||||
opts.optopt("", "window-padding", "padding inside the terminal area (default: 0)", "PADDING");
|
||||
opts.optopt("", "container-padding", "padding inside the main prompt container (default: 1)", "PADDING");
|
||||
opts.optopt("", "prompt-padding", "padding between prompt rows (default: 1)", "PADDING");
|
||||
opts.optopt(
|
||||
"",
|
||||
"greet-align",
|
||||
"alignment of the greeting text in the main prompt container (default: 'center')",
|
||||
"[left|center|right]",
|
||||
);
|
||||
|
||||
opts.optopt("", "power-shutdown", "command to run to shut down the system", "'CMD [ARGS]...'");
|
||||
opts.optopt("", "power-reboot", "command to run to reboot the system", "'CMD [ARGS]...'");
|
||||
|
50
src/info.rs
50
src/info.rs
@ -23,8 +23,8 @@ use crate::{
|
||||
|
||||
const LAST_USER_USERNAME: &str = "/var/cache/tuigreet/lastuser";
|
||||
const LAST_USER_NAME: &str = "/var/cache/tuigreet/lastuser-name";
|
||||
const LAST_SESSION: &str = "/var/cache/tuigreet/lastsession";
|
||||
const LAST_SESSION_PATH: &str = "/var/cache/tuigreet/lastsession-path";
|
||||
const LAST_COMMAND: &str = "/var/cache/tuigreet/lastsession";
|
||||
const LAST_SESSION: &str = "/var/cache/tuigreet/lastsession-path";
|
||||
|
||||
const DEFAULT_MIN_UID: u16 = 1000;
|
||||
const DEFAULT_MAX_UID: u16 = 60000;
|
||||
@ -104,7 +104,7 @@ pub fn get_last_user_name() -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn write_last_username(username: &MaskedString) {
|
||||
let _ = fs::write(LAST_USER_USERNAME, username.value.clone());
|
||||
let _ = fs::write(LAST_USER_USERNAME, &username.value);
|
||||
|
||||
if let Some(ref name) = username.mask {
|
||||
let _ = fs::write(LAST_USER_NAME, name);
|
||||
@ -114,55 +114,59 @@ pub fn write_last_username(username: &MaskedString) {
|
||||
}
|
||||
|
||||
pub fn get_last_session_path() -> Result<PathBuf, io::Error> {
|
||||
Ok(PathBuf::from(fs::read_to_string(LAST_SESSION_PATH)?.trim()))
|
||||
Ok(PathBuf::from(fs::read_to_string(LAST_SESSION)?.trim()))
|
||||
}
|
||||
|
||||
pub fn get_last_session() -> Result<String, io::Error> {
|
||||
Ok(fs::read_to_string(LAST_SESSION)?.trim().to_string())
|
||||
pub fn get_last_command() -> Result<String, io::Error> {
|
||||
Ok(fs::read_to_string(LAST_COMMAND)?.trim().to_string())
|
||||
}
|
||||
|
||||
pub fn write_last_session_path<P>(session: &P)
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let _ = fs::write(LAST_SESSION_PATH, session.as_ref().to_string_lossy().as_bytes());
|
||||
let _ = fs::write(LAST_SESSION, session.as_ref().to_string_lossy().as_bytes());
|
||||
}
|
||||
|
||||
pub fn write_last_session(session: &str) {
|
||||
let _ = fs::write(LAST_SESSION, session);
|
||||
pub fn write_last_command(session: &str) {
|
||||
let _ = fs::write(LAST_COMMAND, session);
|
||||
}
|
||||
|
||||
pub fn get_last_user_session_path(username: &str) -> Result<PathBuf, io::Error> {
|
||||
Ok(PathBuf::from(fs::read_to_string(format!("{LAST_SESSION_PATH}-{username}"))?.trim()))
|
||||
pub fn get_last_user_session(username: &str) -> Result<PathBuf, io::Error> {
|
||||
Ok(PathBuf::from(fs::read_to_string(format!("{LAST_SESSION}-{username}"))?.trim()))
|
||||
}
|
||||
|
||||
pub fn get_last_user_session(username: &str) -> Result<String, io::Error> {
|
||||
Ok(fs::read_to_string(format!("{LAST_SESSION}-{username}"))?.trim().to_string())
|
||||
pub fn get_last_user_command(username: &str) -> Result<String, io::Error> {
|
||||
Ok(fs::read_to_string(format!("{LAST_COMMAND}-{username}"))?.trim().to_string())
|
||||
}
|
||||
|
||||
pub fn write_last_user_session_path<P>(username: &str, session: P)
|
||||
pub fn write_last_user_session<P>(username: &str, session: P)
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let _ = fs::write(format!("{LAST_SESSION_PATH}-{username}"), session.as_ref().to_string_lossy().as_bytes());
|
||||
let _ = fs::write(format!("{LAST_SESSION}-{username}"), session.as_ref().to_string_lossy().as_bytes());
|
||||
}
|
||||
|
||||
pub fn delete_last_session_path() {
|
||||
let _ = fs::remove_file(LAST_SESSION_PATH);
|
||||
pub fn delete_last_session() {
|
||||
let _ = fs::remove_file(LAST_SESSION);
|
||||
}
|
||||
|
||||
pub fn write_last_user_session(username: &str, session: &str) {
|
||||
let _ = fs::write(format!("{LAST_SESSION}-{username}"), session);
|
||||
}
|
||||
|
||||
pub fn delete_last_user_session_path(username: &str) {
|
||||
let _ = fs::remove_file(format!("{LAST_SESSION_PATH}-{username}"));
|
||||
pub fn write_last_user_command(username: &str, session: &str) {
|
||||
let _ = fs::write(format!("{LAST_COMMAND}-{username}"), session);
|
||||
}
|
||||
|
||||
pub fn delete_last_user_session(username: &str) {
|
||||
let _ = fs::remove_file(format!("{LAST_SESSION}-{username}"));
|
||||
}
|
||||
|
||||
pub fn delete_last_command() {
|
||||
let _ = fs::remove_file(LAST_COMMAND);
|
||||
}
|
||||
|
||||
pub fn delete_last_user_command(username: &str) {
|
||||
let _ = fs::remove_file(format!("{LAST_COMMAND}-{username}"));
|
||||
}
|
||||
|
||||
pub fn get_users(min_uid: u16, max_uid: u16) -> Vec<User> {
|
||||
let users = unsafe { uzers::all_users() };
|
||||
|
||||
|
74
src/ipc.rs
74
src/ipc.rs
@ -8,7 +8,7 @@ use tokio::sync::{
|
||||
|
||||
use crate::{
|
||||
event::Event,
|
||||
info::{delete_last_user_session, delete_last_user_session_path, write_last_user_session, write_last_user_session_path, write_last_username},
|
||||
info::{delete_last_user_command, delete_last_user_session, write_last_user_command, write_last_user_session, write_last_username},
|
||||
macros::SafeDebug,
|
||||
ui::sessions::{Session, SessionSource, SessionType},
|
||||
AuthStatus, Greeter, Mode,
|
||||
@ -69,7 +69,11 @@ impl Ipc {
|
||||
}
|
||||
|
||||
async fn parse_response(&mut self, greeter: &mut Greeter, response: Response) -> Result<(), Box<dyn Error>> {
|
||||
tracing::info!("received greetd message: {:?}", response);
|
||||
// Do not display actual message from greetd, which may contain entered information, sometimes passwords.
|
||||
match response {
|
||||
Response::Error { ref error_type, .. } => tracing::info!("received greetd error message: {error_type:?}"),
|
||||
ref response => tracing::info!("received greetd message: {:?}", response),
|
||||
}
|
||||
|
||||
match response {
|
||||
Response::AuthMessage { auth_message_type, auth_message } => match auth_message_type {
|
||||
@ -124,16 +128,16 @@ impl Ipc {
|
||||
SessionSource::Command(ref command) => {
|
||||
tracing::info!("caching last user command: {command}");
|
||||
|
||||
write_last_user_session(&greeter.username.value, command);
|
||||
delete_last_user_session_path(&greeter.username.value);
|
||||
write_last_user_command(&greeter.username.value, command);
|
||||
delete_last_user_session(&greeter.username.value);
|
||||
}
|
||||
|
||||
SessionSource::Session(index) => {
|
||||
if let Some(Session { path: Some(session_path), .. }) = greeter.sessions.options.get(index) {
|
||||
tracing::info!("caching last user session: {session_path:?}");
|
||||
|
||||
write_last_user_session_path(&greeter.username.value, session_path);
|
||||
delete_last_user_session(&greeter.username.value);
|
||||
write_last_user_session(&greeter.username.value, session_path);
|
||||
delete_last_user_command(&greeter.username.value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,36 +152,51 @@ impl Ipc {
|
||||
} else {
|
||||
tracing::info!("authentication successful, starting session");
|
||||
|
||||
let command = greeter.session_source.command(greeter).map(str::to_string);
|
||||
match greeter.session_source.command(greeter).map(str::to_string) {
|
||||
None => {
|
||||
Ipc::cancel(greeter).await;
|
||||
|
||||
if let Some(command) = command {
|
||||
greeter.done = true;
|
||||
greeter.mode = Mode::Processing;
|
||||
greeter.message = Some(fl!("command_missing"));
|
||||
greeter.reset(false).await;
|
||||
}
|
||||
|
||||
let session = Session::get_selected(greeter);
|
||||
let (command, env) = wrap_session_command(greeter, session, &command);
|
||||
Some(command) if command.is_empty() => {
|
||||
Ipc::cancel(greeter).await;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
self.send(Request::StartSession { cmd: vec![command.to_string()], env }).await;
|
||||
greeter.message = Some(fl!("command_missing"));
|
||||
greeter.reset(false).await;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let _ = command;
|
||||
let _ = env;
|
||||
Some(command) => {
|
||||
greeter.done = true;
|
||||
greeter.mode = Mode::Processing;
|
||||
|
||||
self
|
||||
.send(Request::StartSession {
|
||||
cmd: vec!["true".to_string()],
|
||||
env: vec![],
|
||||
})
|
||||
.await;
|
||||
let session = Session::get_selected(greeter);
|
||||
let (command, env) = wrap_session_command(greeter, session, &command);
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
self.send(Request::StartSession { cmd: vec![command.to_string()], env }).await;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let _ = command;
|
||||
let _ = env;
|
||||
|
||||
self
|
||||
.send(Request::StartSession {
|
||||
cmd: vec!["true".to_string()],
|
||||
env: vec![],
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Response::Error { error_type, description } => {
|
||||
tracing::info!("received an error from greetd: {error_type:?} - {description}");
|
||||
Response::Error { error_type, .. } => {
|
||||
// Do not display actual message from greetd, which may contain entered information, sometimes passwords.
|
||||
tracing::info!("received an error from greetd: {error_type:?}");
|
||||
|
||||
Ipc::cancel(greeter).await;
|
||||
|
||||
@ -193,7 +212,8 @@ impl Ipc {
|
||||
}
|
||||
|
||||
ErrorType::Error => {
|
||||
greeter.message = Some(description);
|
||||
// Do not display actual message from greetd, which may contain entered information, sometimes passwords.
|
||||
greeter.message = Some("An error was received from greetd".to_string());
|
||||
greeter.reset(false).await;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use greetd_ipc::Request;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
info::{delete_last_session_path, get_last_user_session, get_last_user_session_path, write_last_session, write_last_session_path},
|
||||
info::{delete_last_command, delete_last_session, get_last_user_command, get_last_user_session, write_last_command, write_last_session_path},
|
||||
ipc::Ipc,
|
||||
power::power,
|
||||
ui::{
|
||||
@ -225,8 +225,8 @@ pub async fn handle(greeter: Arc<RwLock<Greeter>>, input: KeyEvent, ipc: Ipc) ->
|
||||
greeter.session_source = SessionSource::Command(greeter.buffer.clone());
|
||||
|
||||
if greeter.remember_session {
|
||||
write_last_session(&greeter.buffer);
|
||||
delete_last_session_path();
|
||||
write_last_command(&greeter.buffer);
|
||||
delete_last_session();
|
||||
}
|
||||
|
||||
greeter.buffer = greeter.previous_buffer.take().unwrap_or_default();
|
||||
@ -248,13 +248,12 @@ pub async fn handle(greeter: Arc<RwLock<Greeter>>, input: KeyEvent, ipc: Ipc) ->
|
||||
Mode::Sessions => {
|
||||
let session = greeter.sessions.options.get(greeter.sessions.selected).cloned();
|
||||
|
||||
if let Some(Session { path, command, .. }) = session {
|
||||
if let Some(Session { path, .. }) = session {
|
||||
if greeter.remember_session {
|
||||
if let Some(ref path) = path {
|
||||
write_last_session_path(path);
|
||||
delete_last_command();
|
||||
}
|
||||
|
||||
write_last_session(&command);
|
||||
}
|
||||
|
||||
greeter.session_source = SessionSource::Session(greeter.sessions.selected);
|
||||
@ -365,7 +364,7 @@ async fn validate_username(greeter: &mut Greeter, ipc: &Ipc) {
|
||||
greeter.buffer = String::new();
|
||||
|
||||
if greeter.remember_user_session {
|
||||
if let Ok(last_session) = get_last_user_session_path(&greeter.username.value) {
|
||||
if let Ok(last_session) = get_last_user_session(&greeter.username.value) {
|
||||
if let Some(last_session) = Session::from_path(greeter, last_session).cloned() {
|
||||
tracing::info!("remembered user session is {}", last_session.name);
|
||||
|
||||
@ -374,7 +373,7 @@ async fn validate_username(greeter: &mut Greeter, ipc: &Ipc) {
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(command) = get_last_user_session(&greeter.username.value) {
|
||||
if let Ok(command) = get_last_user_command(&greeter.username.value) {
|
||||
tracing::info!("remembered user command is {}", command);
|
||||
|
||||
greeter.session_source = SessionSource::Command(command);
|
||||
|
12
src/main.rs
12
src/main.rs
@ -23,6 +23,7 @@ use crossterm::{
|
||||
};
|
||||
use event::Event;
|
||||
use greetd_ipc::Request;
|
||||
use power::PowerPostAction;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
@ -112,12 +113,21 @@ where
|
||||
}
|
||||
|
||||
Some(Event::PowerCommand(command)) => {
|
||||
power::run(&greeter, command).await;
|
||||
if let PowerPostAction::ClearScreen = power::run(&greeter, command).await {
|
||||
execute!(io::stdout(), LeaveAlternateScreen)?;
|
||||
terminal.set_cursor(1, 1)?;
|
||||
terminal.clear()?;
|
||||
disable_raw_mode()?;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn exit(greeter: &mut Greeter, status: AuthStatus) {
|
||||
|
17
src/power.rs
17
src/power.rs
@ -60,7 +60,12 @@ pub async fn power(greeter: &mut Greeter, option: PowerOption) {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(greeter: &Arc<RwLock<Greeter>>, mut command: Command) {
|
||||
pub enum PowerPostAction {
|
||||
Noop,
|
||||
ClearScreen,
|
||||
}
|
||||
|
||||
pub async fn run(greeter: &Arc<RwLock<Greeter>>, mut command: Command) -> PowerPostAction {
|
||||
tracing::info!("executing power command: {:?}", command);
|
||||
|
||||
greeter.write().await.mode = Mode::Processing;
|
||||
@ -85,6 +90,12 @@ pub async fn run(greeter: &Arc<RwLock<Greeter>>, mut command: Command) {
|
||||
|
||||
let mut greeter = greeter.write().await;
|
||||
|
||||
greeter.mode = mode;
|
||||
greeter.message = message;
|
||||
if message.is_none() {
|
||||
PowerPostAction::ClearScreen
|
||||
} else {
|
||||
greeter.mode = mode;
|
||||
greeter.message = message;
|
||||
|
||||
PowerPostAction::Noop
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame) -> Result<(u16, u16), Box<dyn
|
||||
|
||||
let command_label_text = prompt_value(theme, Some(fl!("new_command")));
|
||||
let command_label = Paragraph::new(command_label_text).style(theme.of(&[Themed::Prompt]));
|
||||
let command_value_text = Span::from(greeter.buffer.clone());
|
||||
let command_value_text = Span::from(&greeter.buffer);
|
||||
let command_value = Paragraph::new(command_value_text).style(theme.of(&[Themed::Input]));
|
||||
|
||||
f.render_widget(command_label, chunks[0]);
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::error::Error;
|
||||
use std::{borrow::Cow, error::Error};
|
||||
|
||||
use tui::{
|
||||
prelude::Rect,
|
||||
@ -18,7 +18,7 @@ use crate::{
|
||||
use super::style::Themed;
|
||||
|
||||
pub trait MenuItem {
|
||||
fn format(&self) -> String;
|
||||
fn format(&self) -> Cow<'_, str>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::{power::PowerOption, ui::common::menu::MenuItem};
|
||||
|
||||
#[derive(SmartDefault, Clone)]
|
||||
@ -8,7 +10,7 @@ pub struct Power {
|
||||
}
|
||||
|
||||
impl MenuItem for Power {
|
||||
fn format(&self) -> String {
|
||||
self.label.clone()
|
||||
fn format(&self) -> Cow<'_, str> {
|
||||
Cow::Borrowed(&self.label)
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use tui::{
|
||||
use crate::{
|
||||
info::get_hostname,
|
||||
ui::{prompt_value, util::*, Frame},
|
||||
Greeter, Mode, SecretDisplay,
|
||||
Greeter, Mode, SecretDisplay, GreetAlign
|
||||
};
|
||||
|
||||
use super::common::style::Themed;
|
||||
@ -27,6 +27,11 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame) -> Result<(u16, u16), Box<dyn
|
||||
|
||||
let container_padding = greeter.container_padding();
|
||||
let prompt_padding = greeter.prompt_padding();
|
||||
let greeting_alignment = match greeter.greet_align() {
|
||||
GreetAlign::Center => Alignment::Center,
|
||||
GreetAlign::Left => Alignment::Left,
|
||||
GreetAlign::Right => Alignment::Right
|
||||
};
|
||||
|
||||
let container = Rect::new(x, y, width, height);
|
||||
let frame = Rect::new(x + container_padding, y + container_padding, width - (2 * container_padding), height - (2 * container_padding));
|
||||
@ -59,7 +64,7 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame) -> Result<(u16, u16), Box<dyn
|
||||
|
||||
if let Some(greeting) = &greeting {
|
||||
let greeting_text = greeting.trim_end();
|
||||
let greeting_label = Paragraph::new(greeting_text).alignment(Alignment::Center).style(theme.of(&[Themed::Greet]));
|
||||
let greeting_label = Paragraph::new(greeting_text).alignment(greeting_alignment).style(theme.of(&[Themed::Greet]));
|
||||
|
||||
f.render_widget(greeting_label, chunks[GREETING_INDEX]);
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::Greeter;
|
||||
|
||||
@ -86,8 +89,8 @@ pub struct Session {
|
||||
}
|
||||
|
||||
impl MenuItem for Session {
|
||||
fn format(&self) -> String {
|
||||
self.name.clone()
|
||||
fn format(&self) -> Cow<'_, str> {
|
||||
Cow::Borrowed(&self.name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::common::menu::MenuItem;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
@ -7,10 +9,10 @@ pub struct User {
|
||||
}
|
||||
|
||||
impl MenuItem for User {
|
||||
fn format(&self) -> String {
|
||||
fn format(&self) -> Cow<'_, str> {
|
||||
match &self.name {
|
||||
Some(name) => format!("{name} ({})", self.username),
|
||||
None => self.username.clone(),
|
||||
Some(name) => Cow::Owned(format!("{name} ({})", self.username)),
|
||||
None => Cow::Borrowed(&self.username),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user