turbo-vision 1.0.5

A Rust implementation of the classic Borland Turbo Vision text-mode UI framework
Documentation
//! SSH TUI Server Example
//!
//! This example demonstrates how to serve a turbo-vision application over SSH.
//!
//! # Running
//!
//! ```bash
//! cargo run --example ssh_server --features ssh
//! ```
//!
//! # Connecting
//!
//! ```bash
//! ssh -p 2222 user@localhost
//! ```
//!
//! Any password will work (demo only - implement real auth for production!).

use std::time::Duration;

use turbo_vision::prelude::*;
use turbo_vision::terminal::{Backend, Terminal};
use turbo_vision::views::{
    button::Button,
    dialog::Dialog,
    static_text::StaticText,
};
use turbo_vision::ssh::{SshServer, SshServerConfig};

/// Run the TUI application with the provided backend.
fn run_tui_app(backend: Box<dyn Backend>) {
    let terminal = match Terminal::with_backend(backend) {
        Ok(t) => t,
        Err(_) => return,
    };

    run_tui_inner(terminal);
}

fn run_tui_inner(mut terminal: Terminal) {
    // Get terminal size
    let (width, height) = terminal.size();

    // Create a dialog
    let dialog_width: i16 = 50;
    let dialog_height: i16 = 12;
    let dialog_x = (width - dialog_width) / 2;
    let dialog_y = (height - dialog_height) / 2;

    let mut dialog = Dialog::new_modal(
        Rect::new(dialog_x, dialog_y, dialog_x + dialog_width, dialog_y + dialog_height),
        "SSH Admin Console"
    );

    // Add welcome text
    let text = StaticText::new(
        Rect::new(2, 2, dialog_width - 4, 4),
        "Welcome to the turbo-vision SSH TUI Demo!\n\nThis interface is served over SSH."
    );
    dialog.add(Box::new(text));

    // Add quit button
    let button = Button::new(
        Rect::new((dialog_width - 12) / 2, dialog_height - 4, (dialog_width + 12) / 2, dialog_height - 2),
        "Quit",
        CM_QUIT,
        true
    );
    dialog.add(Box::new(button));

    // Set initial focus to the first focusable child (the button)
    // This is critical for keyboard events to work!
    dialog.set_initial_focus();

    // Event loop
    let mut running = true;
    while running {
        // Draw
        terminal.clear();
        dialog.draw(&mut terminal);
        if terminal.flush().is_err() {
            break;
        }

        // Handle events
        if let Ok(Some(mut event)) = terminal.poll_event(Duration::from_millis(50)) {
            dialog.handle_event(&mut event);

            // Check if dialog ended via button press
            // Dialog::handle_event() intercepts commands and calls end_modal(),
            // so we check get_end_state() instead of the event directly
            if dialog.get_end_state() != 0 {
                running = false;
            }

            // Also quit on Escape (if not already handled by dialog)
            if event.what == EventType::Keyboard && event.key_code == turbo_vision::core::event::KB_ESC {
                running = false;
            }
        }
    }

    let _ = terminal.shutdown();
}

#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    // Initialize file logging
    let log_file = std::fs::File::create("ssh_server.log")?;
    let _ = simplelog::WriteLogger::init(
        simplelog::LevelFilter::Debug,
        simplelog::Config::default(),
        log_file,
    );

    let config = SshServerConfig::new()
        .bind_addr("0.0.0.0:2222")
        .load_or_generate_key("ssh_host_key");

    println!("=== SSH TUI Server ===");
    println!();
    println!("Server listening on port 2222");
    println!();
    println!("Connect with:");
    println!("  ssh -p 2222 user@localhost");
    println!();
    println!("Any password will work (demo only!)");
    println!();
    println!("Press Ctrl+C to stop the server");
    println!();

    let server = SshServer::new(config, || {
        Box::new(|backend| {
            run_tui_app(backend);
        })
    });

    server.run().await?;

    Ok(())
}