mirror of
https://github.com/apognu/tuigreet.git
synced 2024-11-26 07:28:57 +03:00
Use setsid on power commands to ensure they do not use our TTY (disablable). Run commands in a thread to prevent UI blocking.
This commit is contained in:
parent
a34140eb7b
commit
f12688fa0d
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -492,6 +492,12 @@ dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.3.1"
|
||||
@ -786,6 +792,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.6.1"
|
||||
@ -904,7 +919,9 @@ dependencies = [
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"tokio-macros",
|
||||
"winapi",
|
||||
]
|
||||
|
@ -19,7 +19,7 @@ rust-embed = "^5.9.0"
|
||||
rust-ini = "^0.17.0"
|
||||
smart-default = "0.6.0"
|
||||
textwrap = "^0.14.0"
|
||||
tokio = { version = "1.2", default_features = false, features = ["macros", "rt-multi-thread", "net", "sync", "time"] }
|
||||
tokio = { version = "1.2", default_features = false, features = ["macros", "rt-multi-thread", "net", "sync", "time", "process"] }
|
||||
unic-langid = "^0.9"
|
||||
zeroize = "^1.3.0"
|
||||
|
||||
|
@ -33,6 +33,8 @@ Options:
|
||||
command to run to shut down the system
|
||||
--power-reboot 'CMD [ARGS]...'
|
||||
command to run to reboot the system
|
||||
--power-no-setsid
|
||||
do not prefix power commands with setsid
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
@ -8,7 +8,7 @@ action_command = Change command
|
||||
action_session = Choose session
|
||||
action_power = Power
|
||||
|
||||
date = %a, %d %h %Y - %H:%M
|
||||
date = %a, %d %h %Y - %H:%M:%S
|
||||
|
||||
username = Username:
|
||||
wait = Please wait...
|
||||
|
@ -13,6 +13,7 @@ use getopts::{Matches, Options};
|
||||
use i18n_embed::DesktopLanguageRequester;
|
||||
use tokio::{
|
||||
net::UnixStream,
|
||||
process::Command,
|
||||
sync::{
|
||||
oneshot::{Receiver, Sender},
|
||||
RwLock, RwLockWriteGuard,
|
||||
@ -43,7 +44,7 @@ impl Display for AuthStatus {
|
||||
|
||||
impl Error for AuthStatus {}
|
||||
|
||||
#[derive(SmartDefault, Copy, Clone, PartialEq)]
|
||||
#[derive(SmartDefault, Debug, Copy, Clone, PartialEq)]
|
||||
pub enum Mode {
|
||||
#[default]
|
||||
Username,
|
||||
@ -51,6 +52,7 @@ pub enum Mode {
|
||||
Command,
|
||||
Sessions,
|
||||
Power,
|
||||
Processing,
|
||||
}
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
@ -87,6 +89,8 @@ pub struct Greeter {
|
||||
pub message: Option<String>,
|
||||
|
||||
pub power_commands: HashMap<PowerOption, String>,
|
||||
pub power_command: Option<Command>,
|
||||
pub power_setsid: bool,
|
||||
|
||||
pub working: bool,
|
||||
pub done: bool,
|
||||
@ -249,6 +253,7 @@ impl Greeter {
|
||||
|
||||
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]...'");
|
||||
opts.optflag("", "power-no-setsid", "do not prefix power commands with setsid");
|
||||
|
||||
self.config = match opts.parse(&env::args().collect::<Vec<String>>()) {
|
||||
Ok(matches) => Some(matches),
|
||||
@ -312,6 +317,8 @@ impl Greeter {
|
||||
self.power_commands.insert(PowerOption::Reboot, command);
|
||||
}
|
||||
|
||||
self.power_setsid = !self.config().opt_present("power-no-setsid");
|
||||
|
||||
self.connect().await;
|
||||
}
|
||||
|
||||
|
@ -149,15 +149,13 @@ pub async fn handle(greeter: Arc<RwLock<Greeter>>, events: &mut Events, ipc: Ipc
|
||||
|
||||
Mode::Power => {
|
||||
if let Some((option, _)) = POWER_OPTIONS.get(greeter.selected_power_option) {
|
||||
match power(&greeter, *option) {
|
||||
Ok(status) if status.success() => {}
|
||||
Ok(status) => greeter.message = Some(format!("Command exited with {}", status)),
|
||||
Err(err) => greeter.message = Some(format!("Command failed: {}", err)),
|
||||
}
|
||||
power(&mut greeter, *option);
|
||||
}
|
||||
|
||||
greeter.mode = greeter.previous_mode;
|
||||
}
|
||||
|
||||
Mode::Processing => {}
|
||||
},
|
||||
|
||||
Key::Char(c) => insert_key(&mut greeter, c).await,
|
||||
@ -190,7 +188,7 @@ async fn insert_key(greeter: &mut Greeter, c: char) {
|
||||
Mode::Username => greeter.username.clone(),
|
||||
Mode::Password => greeter.answer.clone(),
|
||||
Mode::Command => greeter.new_command.clone(),
|
||||
Mode::Sessions | Mode::Power => return,
|
||||
Mode::Sessions | Mode::Power | Mode::Processing => return,
|
||||
};
|
||||
|
||||
let index = (value.chars().count() as i16 + greeter.cursor_offset) as usize;
|
||||
@ -213,7 +211,7 @@ async fn delete_key(greeter: &mut Greeter, key: Key) {
|
||||
Mode::Username => greeter.username.clone(),
|
||||
Mode::Password => greeter.answer.clone(),
|
||||
Mode::Command => greeter.new_command.clone(),
|
||||
Mode::Sessions | Mode::Power => return,
|
||||
Mode::Sessions | Mode::Power | Mode::Processing => return,
|
||||
};
|
||||
|
||||
let index = match key {
|
||||
@ -232,7 +230,7 @@ async fn delete_key(greeter: &mut Greeter, key: Key) {
|
||||
Mode::Username => greeter.username = value,
|
||||
Mode::Password => greeter.answer = value,
|
||||
Mode::Command => greeter.new_command = value,
|
||||
Mode::Sessions | Mode::Power => return,
|
||||
Mode::Sessions | Mode::Power | Mode::Processing => return,
|
||||
};
|
||||
|
||||
if let Key::Delete = key {
|
||||
|
31
src/main.rs
31
src/main.rs
@ -62,6 +62,37 @@ async fn run() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
});
|
||||
|
||||
tokio::task::spawn({
|
||||
let greeter = greeter.clone();
|
||||
|
||||
async move {
|
||||
loop {
|
||||
let command = greeter.write().await.power_command.take();
|
||||
|
||||
if let Some(mut command) = command {
|
||||
greeter.write().await.mode = Mode::Processing;
|
||||
|
||||
let message = match tokio::spawn(async move { command.status().await }).await {
|
||||
Ok(result) => match result {
|
||||
Ok(status) if status.success() => None,
|
||||
Ok(status) => Some(format!("Command exited with {}", status)),
|
||||
Err(err) => Some(format!("Command failed: {}", err)),
|
||||
},
|
||||
|
||||
Err(_) => Some("Command failed".to_string()),
|
||||
};
|
||||
|
||||
let mode = greeter.read().await.previous_mode;
|
||||
|
||||
let mut greeter = greeter.write().await;
|
||||
|
||||
greeter.mode = mode;
|
||||
greeter.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
loop {
|
||||
if let Some(ref mut rx) = greeter.write().await.exit_rx {
|
||||
if let Ok(status) = rx.try_recv() {
|
||||
|
36
src/power.rs
36
src/power.rs
@ -1,7 +1,6 @@
|
||||
use std::{
|
||||
io,
|
||||
process::{Command, ExitStatus, Stdio},
|
||||
};
|
||||
use std::process::Stdio;
|
||||
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::Greeter;
|
||||
|
||||
@ -11,8 +10,8 @@ pub enum PowerOption {
|
||||
Reboot,
|
||||
}
|
||||
|
||||
pub fn power(greeter: &Greeter, option: PowerOption) -> Result<ExitStatus, io::Error> {
|
||||
let mut command = match greeter.power_commands.get(&option) {
|
||||
pub fn power(greeter: &mut Greeter, option: PowerOption) {
|
||||
let command = match greeter.power_commands.get(&option) {
|
||||
None => {
|
||||
let mut command = Command::new("shutdown");
|
||||
|
||||
@ -25,15 +24,30 @@ pub fn power(greeter: &Greeter, option: PowerOption) -> Result<ExitStatus, io::E
|
||||
command
|
||||
}
|
||||
|
||||
Some(command) => {
|
||||
let mut args: Vec<&str> = command.split(' ').collect();
|
||||
let exe = args.remove(0);
|
||||
Some(args) => {
|
||||
let mut command = match greeter.power_setsid {
|
||||
true => {
|
||||
let mut command = Command::new("setsid");
|
||||
command.args(args.split(' '));
|
||||
command
|
||||
}
|
||||
|
||||
let mut command = Command::new(exe);
|
||||
false => {
|
||||
let mut args = args.split(' ');
|
||||
|
||||
let mut command = Command::new(args.next().unwrap_or_default());
|
||||
command.args(args);
|
||||
command
|
||||
}
|
||||
};
|
||||
|
||||
command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null()).env_clear().status()
|
||||
command.stdin(Stdio::null());
|
||||
command.stdout(Stdio::null());
|
||||
command.stderr(Stdio::null());
|
||||
|
||||
command
|
||||
}
|
||||
};
|
||||
|
||||
greeter.power_command = Some(command);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
mod command;
|
||||
mod i18n;
|
||||
mod power;
|
||||
mod processing;
|
||||
mod prompt;
|
||||
mod sessions;
|
||||
mod util;
|
||||
@ -111,6 +112,7 @@ pub async fn draw(greeter: Arc<RwLock<Greeter>>, terminal: &mut Term) -> Result<
|
||||
Mode::Command => self::command::draw(&mut greeter, &mut f).ok(),
|
||||
Mode::Sessions => self::sessions::draw(&mut greeter, &mut f).ok(),
|
||||
Mode::Power => self::power::draw(&mut greeter, &mut f).ok(),
|
||||
Mode::Processing => self::processing::draw(&mut greeter, &mut f).ok(),
|
||||
_ => self::prompt::draw(&mut greeter, &mut f).ok(),
|
||||
};
|
||||
|
||||
|
39
src/ui/processing.rs
Normal file
39
src/ui/processing.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use std::{error::Error, io};
|
||||
|
||||
use termion::raw::RawTerminal;
|
||||
use tui::{
|
||||
backend::TermionBackend,
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
text::Span,
|
||||
widgets::{Block, BorderType, Borders, Paragraph},
|
||||
Frame,
|
||||
};
|
||||
|
||||
use super::util::*;
|
||||
use crate::Greeter;
|
||||
|
||||
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: u16 = get_height(greeter) + 1;
|
||||
let x = (size.width - width) / 2;
|
||||
let y = (size.height - height) / 2;
|
||||
|
||||
let container = Rect::new(x, y, width, height);
|
||||
let container_padding = greeter.container_padding();
|
||||
let frame = Rect::new(x + container_padding, y + container_padding, width - (2 * container_padding), height - (2 * container_padding));
|
||||
|
||||
let block = Block::default().borders(Borders::ALL).border_type(BorderType::Plain);
|
||||
|
||||
let constraints = [Constraint::Length(1)];
|
||||
|
||||
let chunks = Layout::default().direction(Direction::Vertical).constraints(constraints.as_ref()).split(frame);
|
||||
let text = Span::from(fl!("wait"));
|
||||
let paragraph = Paragraph::new(text).alignment(Alignment::Center);
|
||||
|
||||
f.render_widget(paragraph, chunks[0]);
|
||||
f.render_widget(block, container);
|
||||
|
||||
Ok((1, 1))
|
||||
}
|
@ -5,7 +5,7 @@ pub fn titleize(message: &str) -> String {
|
||||
}
|
||||
|
||||
pub fn should_hide_cursor(greeter: &Greeter) -> bool {
|
||||
greeter.working || (greeter.mode == Mode::Password && greeter.prompt.is_none()) || greeter.mode == Mode::Sessions || greeter.mode == Mode::Power
|
||||
greeter.working || (greeter.mode == Mode::Password && greeter.prompt.is_none()) || greeter.mode == Mode::Sessions || greeter.mode == Mode::Power || greeter.mode == Mode::Processing
|
||||
}
|
||||
|
||||
pub fn get_height(greeter: &Greeter) -> u16 {
|
||||
@ -19,11 +19,11 @@ pub fn get_height(greeter: &Greeter) -> u16 {
|
||||
Some(_) => (2 * container_padding) + prompt_padding + 2,
|
||||
None => (2 * container_padding) + 1,
|
||||
},
|
||||
Mode::Sessions | Mode::Power => (2 * container_padding),
|
||||
Mode::Sessions | Mode::Power | Mode::Processing => (2 * container_padding),
|
||||
};
|
||||
|
||||
match greeter.mode {
|
||||
Mode::Command | Mode::Sessions | Mode::Power => initial,
|
||||
Mode::Command | Mode::Sessions | Mode::Power | Mode::Processing => initial,
|
||||
_ => initial + greeting_height,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user