1

I am making a terminal program that is able to run any executable (please ignore safety concerns). I need to detect when the child process is waiting for the user input (from stdin). I start the child process using:

process = subprocess.Popen(command, close_fds=False, shell=True, **file_descriptors)

I can think of 2 ways of detecting if the child process is waiting for stdin:

  • Writing a character then backspace and checking if the child has processed those 2 bytes. But here it says that "CMD does support the backspace key". So I need to find a character that when printed to the screen will delete what ever is in the stdin buffer in the command prompt.
  • The second method is to use the pywin32 library and use the WaitForInputIdle function as described here. I looked at the source code for the subprocess library and found that it uses pywin32 and it keeps a reference to the process handle. So I tried this:
win32event.WaitForInputIdle(proc._handle, 100)

But I got this error:

(1471, 'WaitForInputIdle', 'Unable to finish the requested operation because the specified process is not a GUI process.')

Also in the windows api documentation here it says: "WaitForInputIdle waits only once for a process to become idle; subsequent WaitForInputIdle calls return immediately, whether the process is idle or busy.". I think that means that I can't use the function for its purpose more than once which wouldn't solve my problem

Edit: This only needs to work on Windows but later I might try to make my program computable with Linux as well. Also I am using pipes for the stdin/stdout/stderr.

Why I need to know if the child is waiting for stdin:

Currently, when the user presses the enter key, I send all of the data, that they have written so far, to stdin and disable the user from changing it. The problem is when the child process is sleeping/calculating and the user writes some input and wants to change it before the process starts reading from stdin again.

Basically lets take this program:

sleep(10)
input("Enter value:")

and lets say that I enter in "abc\n". When using cmd it will allow me to press backspace and delete the input if the child is still sleeping. Currently my program will mark all of the text as read only when it detects the "\n" and send it to stdin.

TheLizzard
  • 7,248
  • 2
  • 11
  • 31
  • Does this only need to work on Windows? – Joseph Sible-Reinstate Monica Feb 26 '21 at 01:03
  • I have found something similar what you want. Does it answer your questions? https://stackoverflow.com/a/49603436/11502612 – milanbalazs Feb 26 '21 at 06:25
  • @milanbalazs I am using pipes not files and I don't want to change the child process' input. That is why I tried writing `"g\b"` to `stdin`. The `\b` character was supposed to remove the other character but it just gave me `"g"` and a white box - like the one for unknown characters. – TheLizzard Feb 26 '21 at 15:43

2 Answers2

0
class STDINHandle:
    def __init__(self, read_handle, write_handle):
        self.handled_write = False
        self.working = Lock()
        self.write_handle = write_handle
        self.read_handle = read_handle

    def check_child_reading(self):
        with self.working:
            # Reset the flag
            self.handled_write = True
            # Write a character that cmd will ignore
            self.write_handle.write("\r")
            thread = Thread(target=self.try_read)
            thread.start()
            sleep(0.1)
            # We need to stop the other thread by giving it data to read
            if self.handled_write:
                # Writing only 1 "\r" fails for some reason.
                # For good measure we write 10 "\r"s
                self.write_handle.write("\r"*10)
                return True
            return False

    def try_read(self):
        data = self.read_handle.read(1)
        self.handled_write = False

    def write(self, text):
        self.write_handle.write(text)

I did a bit of testing and I think cmd ignores "\r" characters. I couldn't find a case where cmd will interpret it as an actual character (like what happened when I did "\b"). Sending a "\r" character and testing if it stays in the pipe. If it does stay in the pipe that means that the child hasn't processed it. If we can't read it from the pipe that means that the child has processed it. But we have a problem - we need to stop the read if we can't read from stdin otherwise it will mess with the next write to stdin. To do that we write more "\r"s to the pipe.

Note: I might have to change the timing on the sleep(0.1) line.

TheLizzard
  • 7,248
  • 2
  • 11
  • 31
  • For anyone reading this: check [this](https://stackoverflow.com/a/66768375/11106801) out. It is a better way of doing it. – TheLizzard Mar 23 '21 at 17:36
-1

I am not sure this is a good solution but you can give it a try if interested. I just assumed that we execute the child process for its output given 2 inputs data and TIMEOUT.

process = subprocess.Popen(command, close_fds=False, shell=True, **file_descriptors)

try:
    output, _ = process.communicate(data, TIMEOUT)
except subprocess.TimeoutExpired:
    print("Timeout expires while waiting for a child process.")
    # Do whatever you want here

    return None

cmd_output = output.decode()

You can find more examples for TimeoutExpired here.

Thang Pham
  • 1,006
  • 2
  • 9
  • 18
  • I don't get how this is supposed to tell me if the child process is listening for user input on stdin. Isn't that just sending the input data and reading stdout/stderr for the results (with a timeout)? I already can read the stdout/stderr and write in the tkinter window. The problem is that I can't detect if the child process is waiting for the input. I will update my question with more details in 2 min. – TheLizzard Feb 27 '21 at 23:54
  • How can you get the output from the child process while it is waiting for user input? This is still unclear to me. – Thang Pham Feb 28 '21 at 00:00
  • I have 3 thread running - 1 to get stdout, 1 to get stderr, 1 (with tkinter) that handles the terminal window/stdin. – TheLizzard Feb 28 '21 at 00:04