burn -t forwards keystrokes on every platform except one combination: Windows
with stdin redirected from a pipe or a file. There, nothing typed is ever sent
to the board, and nothing says so.
Why
read_available_keys() dispatches on platform before it looks at anything else:
def read_available_keys() -> bytes:
if sys.platform == "win32":
return _read_windows()
return _read_posix()
_read_windows() only ever asks the console:
while len(out) < _MAX_BYTES_PER_POLL and msvcrt.kbhit():
msvcrt.kbhit() reports console keyboard input. With stdin redirected it is
always false, so the loop never runs and the function returns b"" forever.
_read_posix() would handle a pipe fine — it selects on the descriptor — but
it is unreachable on Windows, and deliberately so: tests/test_terminal_keyboard.py
skips TestPosixReading there with the note that "Windows select() accepts
only sockets, and this path never runs there".
POSIX is unaffected: select() on a pipe works, and raw_terminal() is a no-op
when there is no terminal to configure.
Reproducing
On Windows, against any board parked at a U-Boot prompt:
echo printenv | uv run python -m defib burn -c <chip> -p COM15 -t
The banner appears and serial output streams, but printenv never reaches the
board. The same command on Linux or macOS works.
Suggested shape
Keep the invariant the current implementation was built around — no thread, no
executor, nothing that can stall the loop while it is also holding the serial
link to the board. So not a blocking stdin.read() handed to asyncio.to_thread.
PeekNamedPipe gives the available byte count on a Windows pipe without
blocking, which fits the existing poll-between-serial-reads structure:
def _read_windows() -> bytes:
if sys.stdin.isatty():
return _read_windows_console() # what exists today
return _read_windows_stream() # PeekNamedPipe, then os.read that many
via msvcrt.get_osfhandle(sys.stdin.fileno()) and ctypes.windll.kernel32.
A redirected regular file needs no peek — a read returns immediately — so the
count can come from the peek for pipes and a plain bounded os.read otherwise.
Whatever the mechanism, it should stay bounded by _MAX_BYTES_PER_POLL for the
reason already documented there: a producer that never pauses must not starve
the board's output or the stop flag.
Credit
Found while reviewing #131, which had this case covered before #136 shipped —
its third branch handled any non-tty stream on either platform. That PR is
closed as superseded by #136, but this part of it was not wrong, and #136 did
not carry it across. #131 solved it with a blocking read in a thread, which is
the approach #136 deliberately avoided, so this wants a fresh implementation
rather than a straight port.
burn -tforwards keystrokes on every platform except one combination: Windowswith stdin redirected from a pipe or a file. There, nothing typed is ever sent
to the board, and nothing says so.
Why
read_available_keys()dispatches on platform before it looks at anything else:_read_windows()only ever asks the console:msvcrt.kbhit()reports console keyboard input. With stdin redirected it isalways false, so the loop never runs and the function returns
b""forever._read_posix()would handle a pipe fine — it selects on the descriptor — butit is unreachable on Windows, and deliberately so:
tests/test_terminal_keyboard.pyskips
TestPosixReadingthere with the note that "Windowsselect()acceptsonly sockets, and this path never runs there".
POSIX is unaffected:
select()on a pipe works, andraw_terminal()is a no-opwhen there is no terminal to configure.
Reproducing
On Windows, against any board parked at a U-Boot prompt:
The banner appears and serial output streams, but
printenvnever reaches theboard. The same command on Linux or macOS works.
Suggested shape
Keep the invariant the current implementation was built around — no thread, no
executor, nothing that can stall the loop while it is also holding the serial
link to the board. So not a blocking
stdin.read()handed toasyncio.to_thread.PeekNamedPipegives the available byte count on a Windows pipe withoutblocking, which fits the existing poll-between-serial-reads structure:
via
msvcrt.get_osfhandle(sys.stdin.fileno())andctypes.windll.kernel32.A redirected regular file needs no peek — a read returns immediately — so the
count can come from the peek for pipes and a plain bounded
os.readotherwise.Whatever the mechanism, it should stay bounded by
_MAX_BYTES_PER_POLLfor thereason already documented there: a producer that never pauses must not starve
the board's output or the stop flag.
Credit
Found while reviewing #131, which had this case covered before #136 shipped —
its third branch handled any non-tty stream on either platform. That PR is
closed as superseded by #136, but this part of it was not wrong, and #136 did
not carry it across. #131 solved it with a blocking read in a thread, which is
the approach #136 deliberately avoided, so this wants a fresh implementation
rather than a straight port.