Manual Completo de Interfaces en Terminal (TUI)

Creación de ventanas interactivas en terminal con dialog, whiptail y ncurses

Introducción a las TUI (Text-based User Interfaces)

Las interfaces de usuario basadas en texto (TUI) son una alternativa poderosa a las GUI gráficas, especialmente en entornos de servidor o cuando se necesita ligereza. Este manual cubre las herramientas más populares para crear TUI en Linux.

# Ejemplo básico de dialog
dialog --title "Hola Mundo" --msgbox "Bienvenido al manual de TUI" 10 30

Instalación de Herramientas

Para sistemas Debian/Ubuntu:

sudo apt-get install dialog whiptail ncurses-utils libncurses-dev python3

Para sistemas RHEL/CentOS:

sudo yum install dialog whiptail ncurses ncurses-devel python3

Para Arch Linux:

sudo pacman -S dialog whiptail ncurses python

Capítulo 1: Dialog - La herramienta esencial

1.1 Cuadros básicos

MsgBox - Cuadro de mensaje

dialog --title "Título" --msgbox "Este es un mensaje" 10 30
Nota: Los números 10 y 30 representan alto y ancho respectivamente.

YesNo - Cuadro de confirmación

dialog --title "Confirmación" --yesno "¿Desea continuar?" 10 30
if [ $? -eq 0 ]; then
echo "Usuario dijo Sí"
else
echo "Usuario dijo No"
fi

1.2 Entrada de datos

InputBox - Entrada de texto

dialog --title "Entrada" --inputbox "Introduce tu nombre:" 10 30 2>nombre.txt
nombre=$(cat nombre.txt)

PasswordBox - Entrada de contraseña

dialog --title "Seguridad" --insecure --passwordbox "Contraseña:" 10 30 2>pass.txt

1.3 Menús avanzados

Menú básico

dialog --title "Menú Principal" --menu "Elige una opción:" 15 40 10 \
1 "Opción 1" \
2 "Opción 2" \
3 "Opción 3" 2>opcion.txt

Checklist - Selección múltiple

dialog --title "Software" --checklist "Selecciona paquetes:" 15 40 5 \
1 "Python" off \
2 "Node.js" on \
3 "Go" off 2>paquetes.txt

Radiolist - Selección única

dialog --title "S.O." --radiolist "Elige un sistema:" 15 40 5 \
1 "Linux" on \
2 "Windows" off \
3 "MacOS" off 2>os.txt

Capítulo 2: Whiptail - Alternativa ligera

Whiptail es compatible con dialog pero con menos características y más ligero.

2.1 Diferencias principales

2.2 Ejemplos básicos

MsgBox

whiptail --title "Ejemplo" --msgbox "Este es un mensaje" 10 30

Menú

whiptail --title "Menú" --menu "Elige:" 15 40 5 1 "Uno" 2 "Dos" 3 "Tres"
Advertencia: Whiptail puede tener comportamientos diferentes en distintas distribuciones.

Capítulo 3: NCurses - Programación avanzada

3.1 Introducción a NCurses

NCurses es una biblioteca para desarrollo de interfaces en terminal con control completo sobre la pantalla.

3.2 Ejemplo en Bash

#!/bin/bash
# Configuración inicial
clear
tput civis # Oculta cursor

# Dibuja ventana
tput cup 5 10
echo -e "\e[44m\e[37m┌──────────────────────────────┐"
tput cup 6 10
echo -e "│          \e[1mMENÚ PRINCIPAL\e[0m\e[44m\e[37m          │"
tput cup 7 10
echo -e "├──────────────────────────────┤"
tput cup 8 10
echo -e "│ \e[33m1)\e[0m Opción 1                   │"
tput cup 9 10
echo -e "│ \e[33m2)\e[0m Opción 2                   │"
tput cup 10 10
echo -e "│ \e[33m3)\e[0m Salir                     │"
tput cup 11 10
echo -e "└──────────────────────────────┘\e[0m"

# Espera entrada
tput cup 13 10
read -p "Selecciona una opción [1-3]: " opcion

# Restaura terminal
tput cnorm # Muestra cursor
clear

3.3 Ejemplo en Python

import curses

def main(stdscr):
    # Configuración inicial
    curses.curs_set(0)  # Oculta cursor
    stdscr.clear()
    
    # Configura colores
    curses.start_color()
    curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)
    curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK)
    
    # Dibuja ventana
    stdscr.bkgd(curses.color_pair(1))
    stdscr.border()
    
    # Título
    title = "MENÚ PRINCIPAL"
    stdscr.addstr(2, (curses.COLS - len(title)) // 2, title, curses.A_BOLD)
    
    # Opciones
    options = ["1) Opción 1", "2) Opción 2", "3) Salir"]
    for i, option in enumerate(options):
        stdscr.addstr(5 + i, 10, option, curses.color_pair(2))
    
    # Resaltar selección
    current_row = 0
    while True:
        stdscr.addstr(5 + current_row, 10, options[current_row], 
                     curses.color_pair(2) | curses.A_REVERSE)
        
        key = stdscr.getch()
        stdscr.addstr(5 + current_row, 10, options[current_row], curses.color_pair(2))
        
        if key == curses.KEY_UP and current_row > 0:
            current_row -= 1
        elif key == curses.KEY_DOWN and current_row < len(options) - 1:
            current_row += 1
        elif key == curses.KEY_ENTER or key in [10, 13]:
            break
    
    return current_row

if __name__ == "__main__":
    print(f"Seleccionaste: {curses.wrapper(main) + 1}")

Capítulo 4: Diseño Avanzado

4.1 Sombras y bordes

Para crear efectos visuales avanzados:

Caracteres Unicode para bordes

echo -e "\e[37m╔════════════════════════╗"
echo -e "║ \e[36mTítulo de la ventana\e[37m ║"
echo -e "╠════════════════════════╣"
echo -e "║ \e[0mContenido aquí...    \e[37m║"
echo -e "╚════════════════════════╝\e[0m"

Sombreado con caracteres

echo -e "\e[37m┌────────────────────┐\e[90m▒▒▒"
echo -e "\e[37m│ \e[36mContenido         \e[37m│\e[90m▒▒▒"
echo -e "\e[37m└────────────────────┘\e[90m▒▒▒"
echo -e "   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒\e[0m"

4.2 Soporte para ratón

En dialog

dialog --title "Con Ratón" --msgbox "Prueba a hacer clic" 10 30 --mouse

En NCurses (Python)

import curses

def main(stdscr):
    curses.mousemask(curses.ALL_MOUSE_EVENTS)
    while True:
        key = stdscr.getch()
        if key == curses.KEY_MOUSE:
            _, x, y, _, _ = curses.getmouse()
            stdscr.addstr(y, x, "X")

Capítulo 5: Ejemplos Complejos

5.1 Instalador interactivo

#!/bin/bash
# Instalador ejemplo con dialog

# Verificar dependencias
if ! command -v dialog &> /dev/null; then
    echo "Instalando dialog..."
    sudo apt-get install -y dialog
fi

# Menú principal
while true; do
    choice=$(dialog --title "Instalador de Software" \
             --menu "Elige una opción:" 15 50 10 \
             1 "Instalar paquetes básicos" \
             2 "Configurar sistema" \
             3 "Realizar copia de seguridad" \
             4 "Salir" 3>&1 1>&2 2>&3)
    
    case $choice in
        1)
            packages=$(dialog --title "Instalación" \
                     --checklist "Selecciona paquetes:" 20 50 10 \
                     python "Python 3" on \
                     nodejs "Node.js" off \
                     golang "Go Language" off 3>&1 1>&2 2>&3)
            
            if [ -n "$packages" ]; then
                sudo apt-get install -y $packages
                dialog --msgbox "Paquetes instalados correctamente" 10 30
            fi
            ;;
        2)
            hostname=$(dialog --title "Configuración" \
                     --inputbox "Nuevo nombre de host:" 10 30 3>&1 1>&2 2>&3)
            
            if [ -n "$hostname" ]; then
                sudo hostnamectl set-hostname "$hostname"
                dialog --msgbox "Hostname cambiado a $hostname" 10 30
            fi
            ;;
        3)
            backup_dir=$(dialog --title "Copia de seguridad" \
                       --dselect "$HOME" 15 50 3>&1 1>&2 2>&3)
            
            if [ -n "$backup_dir" ]; then
                tar -czf "$backup_dir/backup_$(date +%F).tar.gz" "$HOME"
                dialog --msgbox "Copia de seguridad completada en $backup_dir" 10 50
            fi
            ;;
        4)
            break
            ;;
    esac
done

clear
echo "Instalación completada"

5.2 Dashboard en terminal

#!/bin/bash
# Dashboard de sistema con NCurses

# Configuración inicial
clear
tput civis
trap 'tput cnorm; clear' EXIT

# Colores
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
BLUE=$(tput setaf 4)
RESET=$(tput sgr0)
REVERSE=$(tput rev)

# Tamaño de pantalla
rows=$(tput lines)
cols=$(tput cols)

# Actualizar dashboard
update_display() {
    # Obtener información del sistema
    local cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
    local mem_usage=$(free -m | awk '/Mem:/ {printf "%.1f", $3/$2*100}')
    local disk_usage=$(df -h / | awk '/\// {print $5}')
    local uptime=$(uptime -p)
    local hostname=$(hostname)
    
    # Dibujar interfaz
    clear
    
    # Marco
    tput cup 0 0
    echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗"
    for i in $(seq 1 $((rows-3))); do
        tput cup $i 0; echo -e "║${RESET}                                                        ${BLUE}║"
    done
    tput cup $((rows-2)) 0
    echo -e "╠════════════════════════════════════════════════════════════════╣"
    tput cup $((rows-1)) 0
    echo -e "║ ${YELLOW}Q${RESET}:Salir  ${YELLOW}R${RESET}:Actualizar                                ${BLUE}║"
    echo -e "╚════════════════════════════════════════════════════════════════╝${RESET}"
    
    # Contenido
    tput cup 2 5; echo -e "${REVERSE} DASHBOARD DEL SISTEMA ${RESET}"
    tput cup 4 5; echo -e "${GREEN}Hostname:${RESET} $hostname"
    tput cup 5 5; echo -e "${GREEN}Uptime:${RESET} $uptime"
    
    # CPU
    tput cup 7 5; echo -e "${BLUE}CPU Usage:${RESET}"
    tput cup 7 20; printf "[%5.1f%%] " $cpu_usage
    local cpu_bar=$(( (cols-30) * cpu_usage / 100 ))
    printf "${GREEN}%${cpu_bar}s${RESET}${RED}%$((cols-30-cpu_bar))s${RESET}" "" "" | tr ' ' '='
    
    # Memoria
    tput cup 9 5; echo -e "${BLUE}Mem Usage:${RESET}"
    tput cup 9 20; printf "[%5.1f%%] " $mem_usage
    local mem_bar=$(( (cols-30) * mem_usage / 100 ))
    printf "${GREEN}%${mem_bar}s${RESET}${RED}%$((cols-30-mem_bar))s${RESET}" "" "" | tr ' ' '='
    
    # Disco
    tput cup 11 5; echo -e "${BLUE}Disk Usage (root):${RESET}"
    tput cup 11 25; printf "[%5s] " "$disk_usage"
    local disk_value=${disk_usage%\%}
    local disk_bar=$(( (cols-35) * disk_value / 100 ))
    printf "${GREEN}%${disk_bar}s${RESET}${RED}%$((cols-35-disk_bar))s${RESET}" "" "" | tr ' ' '='
    
    # Procesos
    tput cup 13 5; echo -e "${BLUE}Top Processes:${RESET}"
    local proc_line=15
    ps -eo pid,user,pcpu,pmem,comm --sort=-pcpu | head -6 | tail -5 | while read -r line; do
        tput cup $proc_line 5; echo "$line"
        ((proc_line++))
    done
}

# Bucle principal
while true; do
    update_display
    
    # Esperar entrada
    read -t 5 -n 1 key
    case $key in
        q|Q) break ;;
        r|R) continue ;;
        *) sleep 1 ;;
    esac
done

clear

Recursos Adicionales

Documentación Oficial

Librerías Alternativas

Proyectos Inspiradores

Libros Recomendados