From 116a33a876b685ea810563a4bd3cd7924093075d Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Sun, 6 Sep 2026 17:37:59 +0200 Subject: [PATCH] fix(tui): bind whiptail dialogs to controlling terminal Interactive stage selection did not receive any keyboard input when the setup was started through a pipe (curl ... | bash), because the dialogs inherited the pipe as stdin/stdout and ENTER/SPACE went to the shell. - lib/utils.sh: add has_tty, ensure_term, get_term_size and a run_whiptail wrapper that binds dialog I/O to /dev/tty and returns results via --output-fd 3 - lib/tui.sh: run all dialogs through run_whiptail, add tui_is_available and tui_calc_geometry to clamp dialog geometry to the terminal size - setup.sh: re-attach stdin/stdout to /dev/tty when piped, normalize TERM and use has_tty for the interactive check - install.sh: reconnect stdout as well and normalize TERM before running setup.sh Co-authored-by: Junie --- install.sh | 19 ++++++-- lib/tui.sh | 130 +++++++++++++++++++++++++++++---------------------- lib/utils.sh | 66 ++++++++++++++++++++++++-- setup.sh | 16 ++++++- 4 files changed, 167 insertions(+), 64 deletions(-) diff --git a/install.sh b/install.sh index 152d95f..58ec6c7 100755 --- a/install.sh +++ b/install.sh @@ -130,11 +130,24 @@ main() { fi setup_cmd+=(./setup.sh "$@") - # Reconnect stdin to /dev/tty before executing setup.sh if piped (e.g. curl ... | bash) - if [[ ! -t 0 ]] && [[ -e /dev/tty ]] && ( : < /dev/tty ) 2>/dev/null; then - exec < /dev/tty + # Reconnect stdin/stdout to /dev/tty before executing setup.sh if piped + # (e.g. curl ... | bash), otherwise the interactive TUI receives no keystrokes + if [[ -c /dev/tty ]] && ( : < /dev/tty ) 2>/dev/null; then + if [[ ! -t 0 ]]; then + exec < /dev/tty + fi + if [[ ! -t 1 ]]; then + exec > /dev/tty + fi fi + # Ensure a terminal type whiptail/newt is able to render + case "${TERM:-}" in + ""|dumb|unknown) + export TERM="xterm-256color" + ;; + esac + "${setup_cmd[@]}" } diff --git a/lib/tui.sh b/lib/tui.sh index 993bb02..fe9066f 100755 --- a/lib/tui.sh +++ b/lib/tui.sh @@ -5,33 +5,78 @@ set -euo pipefail +# All whiptail dialogs are executed via 'run_whiptail' (lib/utils.sh), which +# binds stdin and stdout to the controlling terminal (/dev/tty). Without this, +# keyboard input (ENTER, SPACE, arrow keys) is consumed by the shell instead of +# the dialog whenever the setup is started through a pipe (curl ... | bash). + # Check if whiptail is available tui_check_whiptail() { if ! command_exists whiptail; then log_warn "whiptail is not installed. Attempting to install whiptail..." DEBIAN_FRONTEND=noninteractive apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y whiptail fi + ensure_term +} + +# Determine whether an interactive TUI can be displayed at all +tui_is_available() { + command_exists whiptail || return 1 + has_tty || return 1 + return 0 +} + +# Compute dialog geometry into TUI_HEIGHT / TUI_WIDTH / TUI_LIST_HEIGHT +# Usage: tui_calc_geometry [desired_list_height] +tui_calc_geometry() { + local want_height="$1" + local want_width="$2" + local want_list="${3:-0}" + + local term_size term_lines term_cols + term_size="$(get_term_size)" + term_lines="${term_size%% *}" + term_cols="${term_size##* }" + + local height="$want_height" + local width="$want_width" + + if (( term_lines - 2 < height )); then + height=$(( term_lines - 2 )) + fi + if (( height < 8 )); then + height=8 + fi + + if (( term_cols - 4 < width )); then + width=$(( term_cols - 4 )) + fi + if (( width < 40 )); then + width=40 + fi + + local list_height="$want_list" + if (( want_list > 0 )); then + if (( height - 8 < list_height )); then + list_height=$(( height - 8 )) + fi + if (( list_height < 3 )); then + list_height=3 + fi + fi + + TUI_HEIGHT="$height" + TUI_WIDTH="$width" + TUI_LIST_HEIGHT="$list_height" } # Display welcome message tui_welcome() { - local term_lines - term_lines=$(tput lines 2>/dev/null || echo 24) - local term_cols - term_cols=$(tput cols 2>/dev/null || echo 80) - - local height=14 - local width=70 - if (( term_lines < 16 )); then - height=$(( term_lines - 2 > 8 ? term_lines - 2 : 8 )) - fi - if (( term_cols < 72 )); then - width=$(( term_cols - 4 > 40 ? term_cols - 4 : 40 )) - fi + tui_calc_geometry 14 70 - whiptail --title "Debian Unstable Setup & Hyprland Installer" \ + run_whiptail --title "Debian Unstable Setup & Hyprland Installer" \ --msgbox "Willkommen beim Debian Unstable & Desktop Setup!\n\nDieses Skript führt dich durch die modulare Einrichtung deines Debian Sid Systems mit XanMod-Kernel, Hyprland und Optimierungen.\n\nDrücke ENTER um zur Modulauswahl zu gelangen." \ - "$height" "$width" || true + "$TUI_HEIGHT" "$TUI_WIDTH" || true } # Present stage checklist to user @@ -40,26 +85,11 @@ tui_select_stages() { local tmp_file tmp_file="$(mktemp)" - local term_lines - term_lines=$(tput lines 2>/dev/null || echo 24) - local term_cols - term_cols=$(tput cols 2>/dev/null || echo 80) - - local height=20 - local width=74 - local list_height=10 - - if (( term_lines < 22 )); then - height=$(( term_lines - 2 > 12 ? term_lines - 2 : 12 )) - list_height=$(( height - 8 > 4 ? height - 8 : 4 )) - fi - if (( term_cols < 76 )); then - width=$(( term_cols - 4 > 40 ? term_cols - 4 : 40 )) - fi + tui_calc_geometry 20 74 10 - if whiptail --output-fd 3 --title "Modulauswahl / Stages" \ - --checklist "Wähle die gewünschten Installations-Phasen mit der LEERTASTE aus:" \ - "$height" "$width" "$list_height" \ + if run_whiptail --output-fd 3 --title "Modulauswahl / Stages" \ + --checklist "Wähle die gewünschten Installations-Phasen mit der LEERTASTE aus (ENTER = OK):" \ + "$TUI_HEIGHT" "$TUI_WIDTH" "$TUI_LIST_HEIGHT" \ "01" "Debian Unstable (Deb822, Pinning, Upgrade)" ON \ "02" "Kernel & Hardware (XanMod, P-State, Microcode)" ON \ "03" "Bootloader & Theme (GRUB, Vimix, Plymouth)" ON \ @@ -103,11 +133,12 @@ tui_prompt_target_user() { fi if [[ -n "$default_user" && "$default_user" != "root" ]]; then - if whiptail --title "Ziel-Benutzer bestätigen" \ + tui_calc_geometry 10 65 + if run_whiptail --title "Ziel-Benutzer bestätigen" \ --yes-button "Verwenden" \ --no-button "Anderer Benutzer" \ --yesno "Erkannter Ziel-Benutzer: $default_user\n\nMöchtest du diesen Benutzer für die Konfiguration (Dotfiles, Gruppen, Hyprland) verwenden?" \ - 10 65; then + "$TUI_HEIGHT" "$TUI_WIDTH"; then chosen="$default_user" fi fi @@ -116,19 +147,20 @@ tui_prompt_target_user() { tmp_file="$(mktemp)" while [[ -z "$chosen" ]]; do - if whiptail --output-fd 3 --title "Ziel-Benutzer eingeben" \ + tui_calc_geometry 10 60 + if run_whiptail --output-fd 3 --title "Ziel-Benutzer eingeben" \ --inputbox "Bitte gib den Benutzernamen des Desktop-Benutzers ein:" \ - 10 60 "$default_user" \ + "$TUI_HEIGHT" "$TUI_WIDTH" "$default_user" \ 3> "$tmp_file"; then local input_user input_user="$(tr -d '[:space:]' < "$tmp_file")" if [[ -z "$input_user" || "$input_user" == "root" ]]; then - whiptail --title "Ungültiger Benutzer" --msgbox "Ungültiger Benutzername (darf nicht leer oder root sein)." 8 55 + run_whiptail --title "Ungültiger Benutzer" --msgbox "Ungültiger Benutzername (darf nicht leer oder root sein)." 8 55 || true continue fi if ! id "$input_user" >/dev/null 2>&1; then - whiptail --title "Benutzer nicht gefunden" --msgbox "Benutzer '$input_user' existiert nicht im System." 8 55 + run_whiptail --title "Benutzer nicht gefunden" --msgbox "Benutzer '$input_user' existiert nicht im System." 8 55 || true continue fi chosen="$input_user" @@ -148,21 +180,9 @@ tui_confirm_execution() { local selected_stages="$1" local target_user="$2" - local term_lines - term_lines=$(tput lines 2>/dev/null || echo 24) - local term_cols - term_cols=$(tput cols 2>/dev/null || echo 80) - - local height=14 - local width=70 - if (( term_lines < 16 )); then - height=$(( term_lines - 2 > 10 ? term_lines - 2 : 10 )) - fi - if (( term_cols < 72 )); then - width=$(( term_cols - 4 > 40 ? term_cols - 4 : 40 )) - fi + tui_calc_geometry 14 70 - whiptail --title "Installation starten" \ + run_whiptail --title "Installation starten" \ --yesno "Folgende Konfiguration wird ausgeführt:\n\n- Ziel-Benutzer: $target_user\n- Ausgewählte Stages: $selected_stages\n\nMöchtest du die Installation jetzt starten?" \ - "$height" "$width" + "$TUI_HEIGHT" "$TUI_WIDTH" } diff --git a/lib/utils.sh b/lib/utils.sh index 266f3e0..d98628d 100755 --- a/lib/utils.sh +++ b/lib/utils.sh @@ -82,6 +82,59 @@ command_exists() { command -v "$1" >/dev/null 2>&1 } +# ------------------------------------------------------------------------------ +# Terminal / TTY helpers (required for interactive whiptail dialogs) +# ------------------------------------------------------------------------------ + +# Check whether a usable controlling terminal is available +has_tty() { + [[ -c /dev/tty ]] || return 1 + { : < /dev/tty; } 2>/dev/null || return 1 + return 0 +} + +# Ensure TERM is set to a value newt/whiptail is able to render +ensure_term() { + case "${TERM:-}" in + ""|dumb|unknown) + if [[ -f /usr/share/terminfo/x/xterm-256color || -f /lib/terminfo/x/xterm-256color ]]; then + export TERM="xterm-256color" + else + export TERM="linux" + fi + ;; + esac +} + +# Report the current terminal size as " " +get_term_size() { + local size="" + if has_tty; then + size="$(stty size < /dev/tty 2>/dev/null || true)" + fi + if [[ -z "$size" ]]; then + size="$(stty size 2>/dev/null || true)" + fi + if [[ ! "$size" =~ ^[0-9]+[[:space:]]+[0-9]+$ ]]; then + size="${LINES:-24} ${COLUMNS:-80}" + fi + echo "$size" +} + +# Run whiptail with stdin and stdout bound to the controlling terminal. +# This is required because the setup may be started through a pipe +# (e.g. 'curl ... | bash'), where stdin is not the terminal and all +# keystrokes would otherwise be swallowed by the shell instead of the dialog. +# Dialog results must be requested via '--output-fd 3'. +run_whiptail() { + ensure_term + if has_tty; then + whiptail "$@" < /dev/tty > /dev/tty + else + whiptail "$@" + fi +} + # Determine and prompt for the non-root target user prompt_target_user() { if [[ -n "${TARGET_USER:-}" ]]; then @@ -203,19 +256,22 @@ prompt_ollama_models_path() { fi # If whiptail is available and running on an active terminal, use whiptail dialog - if command_exists whiptail && [[ -t 1 || -c /dev/tty ]]; then - local tui_input - if tui_input=$(whiptail --title "Ollama KI-Modelle Speicherort" \ + if command_exists whiptail && has_tty; then + local tui_tmp + tui_tmp="$(mktemp)" + if run_whiptail --output-fd 3 --title "Ollama KI-Modelle Speicherort" \ --inputbox "Bitte gib den Speicherort für die Ollama KI-Modelle an:" \ 10 70 "$default_path" \ - 3>&1 1>&2 2>&3); then - tui_input="$(echo "$tui_input" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + 3> "$tui_tmp"; then + local tui_input + tui_input="$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' < "$tui_tmp")" if [[ -n "$tui_input" ]]; then chosen="$tui_input" else chosen="$default_path" fi fi + rm -f "$tui_tmp" fi # CLI fallback if whiptail was not used or failed diff --git a/setup.sh b/setup.sh index 84fb365..1ca0e89 100755 --- a/setup.sh +++ b/setup.sh @@ -21,6 +21,19 @@ fi setup_err_trap +# Reconnect stdin/stdout to the controlling terminal when the script was +# started through a pipe (e.g. 'curl ... | bash'). Otherwise keystrokes are +# consumed by the shell and never reach the interactive whiptail dialogs. +if has_tty; then + if [[ ! -t 0 ]]; then + exec < /dev/tty + fi + if [[ ! -t 1 ]]; then + exec > /dev/tty + fi +fi +ensure_term + # Show help text show_help() { cat <