Fix ESCAPE key processing with web_driver under Windows
Closes gh-5969.
Summary
gh-5969 reports that running ESC key presses are not processed immediately when running textual in the web under Windows. This is due to the InputReader under Windows performing a blocking read without honouring the timeout. This PR fixes the InputReader, which in turn fixes gh-5969.
Checklist
- [ ] ~Docstrings on all new or modified functions / classes~
- [ ] ~Updated documentation~
- [ ] ~Updated CHANGELOG.md (where appropriate)~
Remarks
An InputReader is used by the web driver of the launched app to read user input from stdin, which is redirected to an anonymous pipe under Windows. Unlike Linux, anonymous pipes under Windows support just a limited set of operations: (blocking) read, (non-blocking) peek, and close. In particular, the OS provides no way to select/wait an anonymous pipe, perform asynchronous read, or interrupt/cancel a blocking read.
Therefore, to support read with timeout, this PR creates a background thread to read from the pipe and pass on the data through a Queue to the thread where the InputReader is iterated. Timeout is achieved via Queue.get().
While this workaround is straightforward, it does have a semantic difference from the InputReader under Linux: the background thread reads data in advance of being iterated. This is not a problem for Textual because only one InputReader instance is created and it is used throughout the lifetime of the app. However, the implementation is not suitable for general use.
An alternative (not pursued by this PR) could be to redirect stdin to a named pipe under Windows, which supports timeout and asynchronous operations. That requires making changes in the textual-serve repo in addition to this one.
I made a new commit that properly implements cancellation for blocking read of anonymous pipe under Windows. The code follows the approach of DotNET, where the key is to use the CancelSynchronuousIo function. This seems to be the only way to support asynchronous operation on anonymous pipe properly. (An alternative is busy-polling using PeekNamedPipe, which wastes CPU time.)