Finally works, now the commands it's launched
parent
e76881005c
commit
31d13507fe
|
|
@ -1231,6 +1231,7 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||||
name = "sshmngrs"
|
name = "sshmngrs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"libc",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"serde",
|
"serde",
|
||||||
"toml",
|
"toml",
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,6 @@ ratatui = "0.30.2"
|
||||||
serde = { version = "1.0.229", features = ["derive"] }
|
serde = { version = "1.0.229", features = ["derive"] }
|
||||||
toml = "1.1.3"
|
toml = "1.1.3"
|
||||||
tui-input = "0.15.3"
|
tui-input = "0.15.3"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
libc = "0.2.189"
|
||||||
|
|
|
||||||
74
src/main.rs
74
src/main.rs
|
|
@ -1,7 +1,13 @@
|
||||||
|
mod misc;
|
||||||
|
|
||||||
|
use misc::{
|
||||||
|
centered_rect, ignore_interrupt_signals, reset_child_interrupt_signals,
|
||||||
|
restore_interrupt_signals, wait_for_enter,
|
||||||
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
DefaultTerminal, Frame,
|
DefaultTerminal, Frame,
|
||||||
crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
|
crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
|
||||||
layout::{Constraint, Flex, Layout, Rect},
|
layout::{Constraint, Layout, Rect},
|
||||||
style::{Color, Style},
|
style::{Color, Style},
|
||||||
widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph},
|
widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph},
|
||||||
};
|
};
|
||||||
|
|
@ -126,6 +132,7 @@ struct App {
|
||||||
config: Config,
|
config: Config,
|
||||||
selected: usize,
|
selected: usize,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
|
pending_command: Option<String>,
|
||||||
should_quit: bool,
|
should_quit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,6 +144,7 @@ impl App {
|
||||||
config,
|
config,
|
||||||
selected: 0,
|
selected: 0,
|
||||||
mode: Mode::Normal,
|
mode: Mode::Normal,
|
||||||
|
pending_command: None,
|
||||||
should_quit: false,
|
should_quit: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -170,6 +178,7 @@ impl App {
|
||||||
KeyCode::Char('a') => self.mode = Mode::Editing(CommandForm::new()),
|
KeyCode::Char('a') => self.mode = Mode::Editing(CommandForm::new()),
|
||||||
KeyCode::Char('e') => self.start_editing(),
|
KeyCode::Char('e') => self.start_editing(),
|
||||||
KeyCode::Char('d') => self.delete_selected(),
|
KeyCode::Char('d') => self.delete_selected(),
|
||||||
|
KeyCode::Enter => self.connect_selected(),
|
||||||
KeyCode::Up | KeyCode::Char('k') => self.select_previous(),
|
KeyCode::Up | KeyCode::Char('k') => self.select_previous(),
|
||||||
KeyCode::Down | KeyCode::Char('j') => self.select_next(),
|
KeyCode::Down | KeyCode::Char('j') => self.select_next(),
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -192,6 +201,12 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn connect_selected(&mut self) {
|
||||||
|
if let Some(command) = self.config.commands.get(self.selected) {
|
||||||
|
self.pending_command = Some(command.command.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_key_editing(&mut self, key: KeyEvent) {
|
fn on_key_editing(&mut self, key: KeyEvent) {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Esc => self.mode = Mode::Normal,
|
KeyCode::Esc => self.mode = Mode::Normal,
|
||||||
|
|
@ -253,10 +268,59 @@ fn run(mut terminal: DefaultTerminal) -> std::io::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(command) = app.pending_command.take() {
|
||||||
|
launch_command(&mut terminal, &command)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn launch_command(terminal: &mut DefaultTerminal, command: &str) -> std::io::Result<()> {
|
||||||
|
ratatui::restore();
|
||||||
|
|
||||||
|
println!("Running: {command}\n");
|
||||||
|
|
||||||
|
// In Unix, while the child runs the command is in cooked mode, so Ctrl+C sends
|
||||||
|
// SIGINT to the whole foreground process group (sshmngrs included). Ignore it in
|
||||||
|
// the parent so interrupting the child, for example, at an ssh password prompt,
|
||||||
|
// returns to sshmngrs instead of killing it. The child resets its own handlers to
|
||||||
|
// default below so it can still be interrupted.
|
||||||
|
ignore_interrupt_signals();
|
||||||
|
let mut shell = shell_command(command);
|
||||||
|
reset_child_interrupt_signals(&mut shell);
|
||||||
|
|
||||||
|
let status = shell.status();
|
||||||
|
|
||||||
|
match &status {
|
||||||
|
Ok(code) if !code.success() => eprintln!("\nCommand exited with {code}."),
|
||||||
|
Ok(code) => println!("\nCommand exited with {code}."),
|
||||||
|
Err(error) => eprintln!("\nCould not run command: {error}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_enter("Press Enter to restore sshmngrs...");
|
||||||
|
|
||||||
|
restore_interrupt_signals();
|
||||||
|
|
||||||
|
*terminal = ratatui::init();
|
||||||
|
terminal.clear()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shell_command(command: &str) -> std::process::Command {
|
||||||
|
let mut shell = if cfg!(target_os = "windows") {
|
||||||
|
let mut shell = std::process::Command::new("cmd");
|
||||||
|
shell.arg("/C");
|
||||||
|
shell
|
||||||
|
} else {
|
||||||
|
let mut shell = std::process::Command::new("sh");
|
||||||
|
shell.arg("-c");
|
||||||
|
shell
|
||||||
|
};
|
||||||
|
shell.arg(command);
|
||||||
|
shell
|
||||||
|
}
|
||||||
|
|
||||||
fn draw(frame: &mut Frame, app: &App) {
|
fn draw(frame: &mut Frame, app: &App) {
|
||||||
draw_main(frame, app, frame.area());
|
draw_main(frame, app, frame.area());
|
||||||
|
|
||||||
|
|
@ -342,11 +406,3 @@ fn draw_field(frame: &mut Frame, area: Rect, title: &str, input: &Input, focused
|
||||||
frame.set_cursor_position((cursor_x, cursor_y));
|
frame.set_cursor_position((cursor_x, cursor_y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn centered_rect(area: Rect, width: u16, height: u16) -> Rect {
|
|
||||||
let horizontal = Layout::horizontal([Constraint::Length(width)]).flex(Flex::Center);
|
|
||||||
let vertical = Layout::vertical([Constraint::Length(height)]).flex(Flex::Center);
|
|
||||||
let [area] = vertical.areas(area);
|
|
||||||
let [area] = horizontal.areas(area);
|
|
||||||
area
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
||||||
|
|
||||||
|
pub fn wait_for_enter(prompt: &str) {
|
||||||
|
println!("{prompt}");
|
||||||
|
let mut discard = String::new();
|
||||||
|
let _ = std::io::stdin().read_line(&mut discard);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn centered_rect(area: Rect, width: u16, height: u16) -> Rect {
|
||||||
|
let horizontal = Layout::horizontal([Constraint::Length(width)]).flex(Flex::Center);
|
||||||
|
let vertical = Layout::vertical([Constraint::Length(height)]).flex(Flex::Center);
|
||||||
|
let [area] = vertical.areas(area);
|
||||||
|
let [area] = horizontal.areas(area);
|
||||||
|
area
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn ignore_interrupt_signals() {
|
||||||
|
unsafe {
|
||||||
|
libc::signal(libc::SIGINT, libc::SIG_IGN);
|
||||||
|
libc::signal(libc::SIGQUIT, libc::SIG_IGN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn restore_interrupt_signals() {
|
||||||
|
unsafe {
|
||||||
|
libc::signal(libc::SIGINT, libc::SIG_DFL);
|
||||||
|
libc::signal(libc::SIGQUIT, libc::SIG_DFL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn reset_child_interrupt_signals(command: &mut std::process::Command) {
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
// Safety: the closure only resets signal dispositions to their default,
|
||||||
|
// which is async-signal-safe and valid between fork and exec.
|
||||||
|
unsafe {
|
||||||
|
command.pre_exec(|| {
|
||||||
|
libc::signal(libc::SIGINT, libc::SIG_DFL);
|
||||||
|
libc::signal(libc::SIGQUIT, libc::SIG_DFL);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn ignore_interrupt_signals() {}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn restore_interrupt_signals() {}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn reset_child_interrupt_signals(_command: &mut std::process::Command) {}
|
||||||
Loading…
Reference in New Issue