For context, I am using the libraries bevy and print_typewriter.

I noticed that before the program even starts, I am able to type in characters. This is bad, since when I ask for the user’s input, the previous characters are included inside of it.

How do I make sure that only the user’s input after the program starts, and after I print a question gets in?

  • Vector@lemmy.world
    link
    fedilink
    English
    arrow-up
    14
    ·
    edit-2
    4 months ago

    In general allowing content to be supplied in advance on stdin is desirable behavior, because it allows a developer to (for example) write applications that work as pipes that can have content queued on the input stream ready for processing.

    If that behaviour doesn’t suit your use case and you need to only accept input after a certain point, you could read() and simply ignore/discard the current content of stdin before you write your question/accept the answer from the user.

    • BinetteOP
      link
      fedilink
      English
      arrow-up
      2
      ·
      4 months ago

      Thanks! So far it kinda works, but since I’m using print_typewriter, the characters that I’m printing are printed one by one, and user input can slip in between them. I’m not sure how to prevent them from showing up in the first place, and not make them appear in stdin.

      Or maybe in this case I shouldn’t use the terminal, right?

      • ssokolow
        link
        fedilink
        English
        arrow-up
        5
        ·
        edit-2
        4 months ago

        For an interactive terminal program with the characteristics you want, you need to do two things:

        1. Flush stdin before using it, similar to what things like sudo do. (Basically, once your program is all started up and ready to go, read everything that’s already in there and throw it away.)
        2. Disable the terminal’s default echoing behaviour which traces back to when the terminal was a completely separate device possibly half-way around the world from the machine you were logged into on the other side of a slow modem and you didn’t want the latency from waiting for the machine on the far end to handle echo. (See this if you want more context.)

        Windows and POSIX (Linux, the BSDs, macOS, and basically everything else of note) have different APIs for it. On the Linux side, you want something that wraps the curses library, which can put your terminal in “raw mode” or some other configuration that operates unbuffered and lacking terminal-side echo. On the windows side, it can either be done by wrapping the Windows APIs directly or by using the pdcurses library.

        Something like termwiz should do for both… though you’ll probably need to reimplement print_typewriter but that should be trivial from what I see of its README.

        • BinetteOP
          link
          fedilink
          English
          arrow-up
          1
          ·
          4 months ago

          Took me a while to get it to work because of bevy stuff, but it works a lot better! Is there a way to flush stdin without requiring the user to press a key?

          Thanks a lot!

          • ssokolow
            link
            fedilink
            English
            arrow-up
            2
            ·
            4 months ago

            The answer depends on what’s actually going on. I’ve yet to do this sort of thing in Rust but, when I was doing it in Python and initializing curses for a TUI (i.e. like a GUI but made using text), I remember the curses wrapper’s initialization stuff handling that for me.

            Because of the way terminal emulation works, there are actually two buffers: the one inside the fake terminal hardware that lets the user accumulate and backspace characters before sending the data over the fake wire when you press Enter and the one on the receiving end of the wire.

            Paste your initialization code for your curses wrapper and I’ll take a look at it after I’ve had breakfast.

            • BinetteOP
              link
              fedilink
              English
              arrow-up
              1
              ·
              edit-2
              4 months ago

              Here’s the main function

              fn main() {
                  let mut main_terminal = terminal::new_terminal(caps::Capabilities::new_from_env().unwrap()).unwrap();
              
                  terminal::Terminal::set_raw_mode(&mut main_terminal);
              
                  App::new()
                      .insert_non_send_resource(main_terminal)
                      .init_resource::<TextDuration>()
                      .add_systems(Startup, enter_name)
                      .run();
              }
              

              And here are the function enter name and flush_sdin

              fn enter_name(duration: Res<TextDuration>, main_terminal: NonSendMut<terminal::SystemTerminal>){
                  print_text(&duration, Speeds::Default, String::from("Please enter your name: "));
                  flush_stdin();
                  terminal::Terminal::set_cooked_mode(main_terminal.into_inner());
                  let mut name = String::new();
                  io::stdin().read_line(&mut name);
                  print_text(&duration, Speeds::Default, String::from(format!("Hello, {}!\n", name.trim().green())));
              
              }
              
              fn flush_stdin(){
                  let mut clean_buffer = [0; 4];
                  let _ =io::stdin().read(&mut clean_buffer[..]);
              }
              

              When I use flush_stdin, I have to press a key before submitting the actual input

              Edit: I forgot to mention, the print_text function is just something I used to make print_typed! use different speeds and stuff. I haven’t finished the different speeds for now, but that’s not important

              
              fn print_text(duration: &Res<TextDuration>, speed: Speeds, text: String){
                  match speed {
                      Speeds::Default => print_typed!((duration.default), "{}", text),
                      
                  }
              }
              
              
              • ssokolow
                link
                fedilink
                English
                arrow-up
                1
                ·
                edit-2
                4 months ago

                What you’re running into is that read() does blocking I/O by default and, while you can change that, both approaches (checking for pending data before reading or setting stdin into non-blocking mode so it’ll return immediately either way) require different code for Windows and for POSIX, so it’s best to let your platform abstraction (i.e. termwiz) handle that.

                I have no experience with Bevy or Termwiz, but see if this does what you want once you’ve fixed any “I wrote this but never tried to compile it” bugs:

                use std::time::Duration;
                
                fn flush_stdin(main_terminal: terminal::SystemTerminal) {
                    while let Ok(Some(_)) = main_terminal.poll_input(Some(Duration::ZERO)) { }
                }
                

                If I’ve understood the termwiz docs correctly, that’ll pull and discard keypresses off the input buffer until none are left and then return.

                Note that I’m not sure how it’ll behave if you call enter_name a second time and you’re still in cooked mode from a previous enter_name. My experience is with raw mode. (And it’s generally bad form to have a function change global state as a hidden side-effect and not restore what it was before.)

                • BinetteOP
                  link
                  fedilink
                  arrow-up
                  1
                  ·
                  4 months ago

                  Thanks a lot man! After debuggin for a while it worked!

                  I was also wondering, where do you learn that kind of stuff? I’m currently learning and would like to be as resourceful as possible.

      • Vector@lemmy.world
        link
        fedilink
        English
        arrow-up
        2
        ·
        4 months ago

        I’m sure there are ways to suppress output from stdin presenting in the terminal but I couldn’t tell you how to do it without looking it up myself.

        The easiest entry point to this problem that I can think of off the top of my head is password input masking (e.g., when you run sudo and type your password, it prevents character output even though the characters are read by the application).

        There is almost certainly a much better and more appropriate mechanism to prevent stdin characters from printing directly to the terminal (perhaps some kind of special character? A TTY control option?) but I don’t know it off-hand.