textual icon indicating copy to clipboard operation
textual copied to clipboard

Fix ESCAPE key processing with web_driver under Windows

Open fancidev opened this issue 4 months ago • 1 comments

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.

fancidev avatar Aug 07 '25 22:08 fancidev

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.)

fancidev avatar Aug 16 '25 06:08 fancidev