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",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-multimap"
|
name = "ordered-multimap"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -786,6 +792,15 @@ dependencies = [
|
|||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
@ -904,7 +919,9 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
"mio",
|
"mio",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
|
"once_cell",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
@ -19,7 +19,7 @@ rust-embed = "^5.9.0"
|
|||||||
rust-ini = "^0.17.0"
|
rust-ini = "^0.17.0"
|
||||||
smart-default = "0.6.0"
|
smart-default = "0.6.0"
|
||||||
textwrap = "^0.14.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"
|
unic-langid = "^0.9"
|
||||||
zeroize = "^1.3.0"
|
zeroize = "^1.3.0"
|
||||||
|
|
||||||
|
@ -33,6 +33,8 @@ Options:
|
|||||||
command to run to shut down the system
|
command to run to shut down the system
|
||||||
--power-reboot 'CMD [ARGS]...'
|
--power-reboot 'CMD [ARGS]...'
|
||||||
command to run to reboot the system
|
command to run to reboot the system
|
||||||
|
--power-no-setsid
|
||||||
|
do not prefix power commands with setsid
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
@ -8,7 +8,7 @@ action_command = Change command
|
|||||||
action_session = Choose session
|
action_session = Choose session
|
||||||
action_power = Power
|
action_power = Power
|
||||||
|
|
||||||
date = %a, %d %h %Y - %H:%M
|
date = %a, %d %h %Y - %H:%M:%S
|
||||||
|
|
||||||
username = Username:
|
username = Username:
|
||||||
wait = Please wait...
|
wait = Please wait...
|
||||||
|
@ -13,6 +13,7 @@ use getopts::{Matches, Options};
|
|||||||
use i18n_embed::DesktopLanguageRequester;
|
use i18n_embed::DesktopLanguageRequester;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
net::UnixStream,
|
net::UnixStream,
|
||||||
|
process::Command,
|
||||||
sync::{
|
sync::{
|
||||||
oneshot::{Receiver, Sender},
|
oneshot::{Receiver, Sender},
|
||||||
RwLock, RwLockWriteGuard,
|
RwLock, RwLockWriteGuard,
|
||||||
@ -43,7 +44,7 @@ impl Display for AuthStatus {
|
|||||||
|
|
||||||
impl Error for AuthStatus {}
|
impl Error for AuthStatus {}
|
||||||
|
|
||||||
#[derive(SmartDefault, Copy, Clone, PartialEq)]
|
#[derive(SmartDefault, Debug, Copy, Clone, PartialEq)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
#[default]
|
#[default]
|
||||||
Username,
|
Username,
|
||||||
@ -51,6 +52,7 @@ pub enum Mode {
|
|||||||
Command,
|
Command,
|
||||||
Sessions,
|
Sessions,
|
||||||
Power,
|
Power,
|
||||||
|
Processing,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(SmartDefault)]
|
#[derive(SmartDefault)]
|
||||||
@ -87,6 +89,8 @@ pub struct Greeter {
|
|||||||
pub message: Option<String>,
|
pub message: Option<String>,
|
||||||
|
|
||||||
pub power_commands: HashMap<PowerOption, String>,
|
pub power_commands: HashMap<PowerOption, String>,
|
||||||
|
pub power_command: Option<Command>,
|
||||||
|
pub power_setsid: bool,
|
||||||
|
|
||||||
pub working: bool,
|
pub working: bool,
|
||||||
pub done: 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-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.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>>()) {
|
self.config = match opts.parse(&env::args().collect::<Vec<String>>()) {
|
||||||
Ok(matches) => Some(matches),
|
Ok(matches) => Some(matches),
|
||||||
@ -312,6 +317,8 @@ impl Greeter {
|
|||||||
self.power_commands.insert(PowerOption::Reboot, command);
|
self.power_commands.insert(PowerOption::Reboot, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.power_setsid = !self.config().opt_present("power-no-setsid");
|
||||||
|
|
||||||
self.connect().await;
|
self.connect().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,15 +149,13 @@ pub async fn handle(greeter: Arc<RwLock<Greeter>>, events: &mut Events, ipc: Ipc
|
|||||||
|
|
||||||
Mode::Power => {
|
Mode::Power => {
|
||||||
if let Some((option, _)) = POWER_OPTIONS.get(greeter.selected_power_option) {
|
if let Some((option, _)) = POWER_OPTIONS.get(greeter.selected_power_option) {
|
||||||
match power(&greeter, *option) {
|
power(&mut 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)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
greeter.mode = greeter.previous_mode;
|
greeter.mode = greeter.previous_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mode::Processing => {}
|
||||||
},
|
},
|
||||||
|
|
||||||
Key::Char(c) => insert_key(&mut greeter, c).await,
|
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::Username => greeter.username.clone(),
|
||||||
Mode::Password => greeter.answer.clone(),
|
Mode::Password => greeter.answer.clone(),
|
||||||
Mode::Command => greeter.new_command.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;
|
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::Username => greeter.username.clone(),
|
||||||
Mode::Password => greeter.answer.clone(),
|
Mode::Password => greeter.answer.clone(),
|
||||||
Mode::Command => greeter.new_command.clone(),
|
Mode::Command => greeter.new_command.clone(),
|
||||||
Mode::Sessions | Mode::Power => return,
|
Mode::Sessions | Mode::Power | Mode::Processing => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let index = match key {
|
let index = match key {
|
||||||
@ -232,7 +230,7 @@ async fn delete_key(greeter: &mut Greeter, key: Key) {
|
|||||||
Mode::Username => greeter.username = value,
|
Mode::Username => greeter.username = value,
|
||||||
Mode::Password => greeter.answer = value,
|
Mode::Password => greeter.answer = value,
|
||||||
Mode::Command => greeter.new_command = value,
|
Mode::Command => greeter.new_command = value,
|
||||||
Mode::Sessions | Mode::Power => return,
|
Mode::Sessions | Mode::Power | Mode::Processing => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Key::Delete = key {
|
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 {
|
loop {
|
||||||
if let Some(ref mut rx) = greeter.write().await.exit_rx {
|
if let Some(ref mut rx) = greeter.write().await.exit_rx {
|
||||||
if let Ok(status) = rx.try_recv() {
|
if let Ok(status) = rx.try_recv() {
|
||||||
|
38
src/power.rs
38
src/power.rs
@ -1,7 +1,6 @@
|
|||||||
use std::{
|
use std::process::Stdio;
|
||||||
io,
|
|
||||||
process::{Command, ExitStatus, Stdio},
|
use tokio::process::Command;
|
||||||
};
|
|
||||||
|
|
||||||
use crate::Greeter;
|
use crate::Greeter;
|
||||||
|
|
||||||
@ -11,8 +10,8 @@ pub enum PowerOption {
|
|||||||
Reboot,
|
Reboot,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn power(greeter: &Greeter, option: PowerOption) -> Result<ExitStatus, io::Error> {
|
pub fn power(greeter: &mut Greeter, option: PowerOption) {
|
||||||
let mut command = match greeter.power_commands.get(&option) {
|
let command = match greeter.power_commands.get(&option) {
|
||||||
None => {
|
None => {
|
||||||
let mut command = Command::new("shutdown");
|
let mut command = Command::new("shutdown");
|
||||||
|
|
||||||
@ -25,15 +24,30 @@ pub fn power(greeter: &Greeter, option: PowerOption) -> Result<ExitStatus, io::E
|
|||||||
command
|
command
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(command) => {
|
Some(args) => {
|
||||||
let mut args: Vec<&str> = command.split(' ').collect();
|
let mut command = match greeter.power_setsid {
|
||||||
let exe = args.remove(0);
|
true => {
|
||||||
|
let mut command = Command::new("setsid");
|
||||||
|
command.args(args.split(' '));
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
|
false => {
|
||||||
|
let mut args = args.split(' ');
|
||||||
|
|
||||||
|
let mut command = Command::new(args.next().unwrap_or_default());
|
||||||
|
command.args(args);
|
||||||
|
command
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
command.stdin(Stdio::null());
|
||||||
|
command.stdout(Stdio::null());
|
||||||
|
command.stderr(Stdio::null());
|
||||||
|
|
||||||
let mut command = Command::new(exe);
|
|
||||||
command.args(args);
|
|
||||||
command
|
command
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null()).env_clear().status()
|
greeter.power_command = Some(command);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
mod command;
|
mod command;
|
||||||
mod i18n;
|
mod i18n;
|
||||||
mod power;
|
mod power;
|
||||||
|
mod processing;
|
||||||
mod prompt;
|
mod prompt;
|
||||||
mod sessions;
|
mod sessions;
|
||||||
mod util;
|
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::Command => self::command::draw(&mut greeter, &mut f).ok(),
|
||||||
Mode::Sessions => self::sessions::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::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(),
|
_ => 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 {
|
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 {
|
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,
|
Some(_) => (2 * container_padding) + prompt_padding + 2,
|
||||||
None => (2 * container_padding) + 1,
|
None => (2 * container_padding) + 1,
|
||||||
},
|
},
|
||||||
Mode::Sessions | Mode::Power => (2 * container_padding),
|
Mode::Sessions | Mode::Power | Mode::Processing => (2 * container_padding),
|
||||||
};
|
};
|
||||||
|
|
||||||
match greeter.mode {
|
match greeter.mode {
|
||||||
Mode::Command | Mode::Sessions | Mode::Power => initial,
|
Mode::Command | Mode::Sessions | Mode::Power | Mode::Processing => initial,
|
||||||
_ => initial + greeting_height,
|
_ => initial + greeting_height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user