Aykana
Aykana

Reputation: 43

Terminal state not restored using termion

I am trying to get the user input after a certain duration by using two threads. A thread duration and thread for editing. When the thread duration completes,and that the thread for editing has not completed,the terminal state is not restored thus breaking the terminal. This happens when the user did not press "q" before the time duration

The only way of restoring the state of the terminal is to press"q" which will break the loop in the first thread calling droop on the termion raw terminal

use std::io;
use std::io::Write;

use crossbeam_channel::{select, unbounded};
use std::thread;
use std::time;
use std::time::Duration;

use termion;
use termion::input::TermRead;
use termion::raw::IntoRawMode;

fn test() -> String {
    let (s1, r1) = unbounded();
    let (s2, r2) = unbounded();

    let terminal = io::stdout().into_raw_mode();
    let mut stdout = terminal.unwrap();
    let mut stdin = termion::async_stdin().keys();
    thread::spawn(move || {
        // Use asynchronous stdin
        let mut s = String::new();

        loop {
            // Read input (if any)
            let input = stdin.next();

            // If a key was pressed
            if let Some(Ok(key)) = input {
                match key {
                    // Exit if 'q' is pressed
                    termion::event::Key::Char('q') => {
                        s1.send('q');
                        break;
                    }
                    // Else print the pressed key
                    _ => {
                        if let termion::event::Key::Char(k) = key {
                            s1.send(k);
                        }

                        stdout.lock().flush().unwrap();
                    }
                }
            }
            thread::sleep(time::Duration::from_millis(50));
        }
    });
    thread::spawn(move || {
        thread::sleep(Duration::from_millis(3000));
        s2.send(20).unwrap();
    });

    // None of the two operations will become ready within 100 milliseconds.
    let mut val: String = String::new();

    loop {
        select! {
            recv(r1) -> msg => val.push(msg.unwrap()),
            recv(r2) -> _msg => break,
            default(Duration::from_millis(3000)) => println!("timed out"),
        };
    }
    return val;
}

fn main() {
    println!("result {}", test());
}

Upvotes: 2

Views: 607

Answers (1)

Aplet123
Aplet123

Reputation: 35560

In Rust, forcefully exiting a thread (such as by ending the main thread before the child threads run) is almost never a good idea, for reasons you've seen here. Their destructors don't get run, which means things could get messed up. The cleanest way is probably to keep an Arc<Mutex<bool>> that becomes true when threads should exit, and the threads can read it on their own accord and exit gracefully. Then, you should join the threads at the end of the function to ensure they finish all the way through. I've documented my changes in the comments:

use std::io;
use std::io::Write;

use crossbeam_channel::{select, unbounded};
use std::thread;
use std::time;
use std::time::Duration;

// import Arc and Mutex
use std::sync::{Arc, Mutex};

use termion;
use termion::input::TermRead;
use termion::raw::IntoRawMode;

fn test() -> String {
    let (s1, r1) = unbounded();
    let (s2, r2) = unbounded();

    let terminal = io::stdout().into_raw_mode();
    let stdout = terminal.unwrap();
    let mut stdin = termion::async_stdin().keys();

    // keep a boolean flag of if we should exit
    let should_exit = Arc::new(Mutex::new(false));
    // clone the Arc for moving into the first thread
    let should_exit_t1 = Arc::clone(&should_exit);
    // keep a vec of handles for joining
    let mut handles = vec![];
    // push the handle onto the vec
    handles.push(thread::spawn(move || {
        loop {
            // if the flag is true then we should gracefully exit
            if *should_exit_t1.lock().unwrap() {
                break;
            }

            // Read input (if any)
            let input = stdin.next();

            // If a key was pressed
            if let Some(Ok(key)) = input {
                match key {
                    // Exit if 'q' is pressed
                    termion::event::Key::Char('q') => {
                        s1.send('q').unwrap();
                        break;
                    }
                    // Else print the pressed key
                    _ => {
                        if let termion::event::Key::Char(k) = key {
                            s1.send(k).unwrap();
                        }

                        stdout.lock().flush().unwrap();
                    }
                }
            }
            thread::sleep(time::Duration::from_millis(50));
        }
    }));
    // also push the handle onto the vec
    handles.push(thread::spawn(move || {
        thread::sleep(Duration::from_millis(3000));
        s2.send(20).unwrap();
    }));

    // None of the two operations will become ready within 100 milliseconds.
    let mut val: String = String::new();

    loop {
        select! {
            recv(r1) -> msg => val.push(msg.unwrap()),
            recv(r2) -> _msg => break,
            default(Duration::from_millis(3000)) => println!("timed out"),
        };
    }

    // before exiting, set the exit flag to true
    *should_exit.lock().unwrap() = true;
    // join all the threads so their destructors are run
    for handle in handles {
        handle.join().unwrap();
    }
    return val;
}

fn main() {
    println!("result {}", test());
}

Upvotes: 4

Related Questions