Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@

### Fixed

- hush: TUI now reports a clear "no TTY" error with subcommand guidance instead of crossterm's cryptic "Device not configured" failure (#57)
- remediate merged-#35 CodeRabbit findings (#36) [#35]
- post-merge remediation for #33 (validator hardening, packaging hygiene, ask-codex metadata) (#34) [#33]
- complete issue #10 port hardening (#33) [#10]
Expand Down
17 changes: 15 additions & 2 deletions bws-tui/src/tui/terminal.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use super::{events, App};
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use crossterm::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, Terminal};
use std::io::{self, Write};
use std::io::{self, IsTerminal, Write};

#[derive(Default)]
struct TerminalGuard {
Expand Down Expand Up @@ -50,6 +50,13 @@ impl Drop for TerminalGuard {
}

pub(super) fn run(app: &mut App) -> Result<()> {
if !is_interactive(io::stdin().is_terminal(), io::stdout().is_terminal()) {
bail!(
"hush's interactive TUI needs a terminal session (no TTY found). \
Run `hush` from an interactive terminal, or use the script-friendly \
subcommands: `hush list`, `hush get --key <KEY>`, `hush exec --key <KEY> -- <cmd>`"
);
}
let mut guard = TerminalGuard::default();
guard.enable_raw()?;
let mut output = io::stdout();
Expand All @@ -60,6 +67,12 @@ pub(super) fn run(app: &mut App) -> Result<()> {
finish_terminal(event, raw, screen)
}

/// The TUI needs either side attached to a terminal; crossterm opens /dev/tty
/// directly for input when stdin is piped, so stdout alone is enough.
pub(super) fn is_interactive(stdin_tty: bool, stdout_tty: bool) -> bool {
stdin_tty || stdout_tty
}

pub(super) fn finish_terminal(
event: Result<()>,
raw: Result<()>,
Expand Down
9 changes: 9 additions & 0 deletions bws-tui/src/tui/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use crate::tui::terminal::is_interactive;
use anyhow::anyhow;

#[test]
Expand Down Expand Up @@ -52,6 +53,14 @@ fn edit_fields_cycle_without_invalid_numeric_states() {
assert_eq!(EditField::Key.previous(), EditField::Note);
}

#[test]
fn tui_gate_accepts_a_tty_on_either_side() {
assert!(is_interactive(true, false));
assert!(is_interactive(false, true));
assert!(is_interactive(true, true));
assert!(!is_interactive(false, false));
}

#[test]
fn terminal_cleanup_reports_every_failure() {
let error = terminal::finish_terminal(
Expand Down
Loading