Version: crossterm 0.29, still present on master (src/terminal/sys/unix.rs:255). Unix.
Summary
supports_keyboard_enhancement() / query_keyboard_enhancement_flags() blocks forever if the terminal replies to CSI ? u but never replies to Primary Device Attributes (CSI c).
The first reply is bounded by poll_internal(Some(Duration::from_millis(2000)), …), but once the keyboard-flags reply arrives the DA1 flush is an unbounded read:
Ok(InternalEvent::KeyboardEnhancementFlags(current_flags)) => {
// Flush the PrimaryDeviceAttributes out of the event queue.
internal::read(&PrimaryDeviceAttributesFilter).ok();
return Ok(Some(current_flags));
}Because this usually runs before an app draws anything and with raw mode enabled, the user sees a blank terminal that ignores every key including Ctrl-C.
Real-world case
Kelpi 0.2.12 answers CSI ? u with CSI ? 0 u but does not answer CSI c (benfriebe/kelpi#349). tuicr calls supports_keyboard_enhancement() at startup and hangs indefinitely there (agavra/tuicr#751). The terminal is at fault for skipping DA1, but a capability probe shouldn't be able to hang the app.
Repro
Run any binary that calls supports_keyboard_enhancement() in a pty that replies only to the keyboard query (shown here with tuicr):
import os, pty, select, time
pid, fd = pty.fork()
if pid == 0:
os.execvp("tuicr", ["tuicr", "--no-update-check"])
out, sent, t0 = b"", False, time.time()
while time.time() - t0 < 5:
if select.select([fd], [], [], 0.1)[0]:
out += os.read(fd, 65536)
if not sent and b"\x1b[c" in out:
os.write(fd, b"\x1b[?0u"); sent = True # no DA1 reply
print(len(out), repr(out[:80]))
os.kill(pid, 9)| Terminal replies | Result |
|---|---|
CSI ? 0 u only | blocks forever (still blocked after 5 s) |
CSI ? 0 u + CSI ? 62 ; 22 c | returns immediately |
| nothing | returns Err after the 2 s timeout |
Suggested fix
Bound the DA1 flush the same way as the first read, e.g. poll_internal(Some(Duration::from_millis(2000)), &PrimaryDeviceAttributesFilter) and only read if it returns Ok(true). Returning Ok(Some(current_flags)) either way keeps the current result when DA1 is missing, just without the hang.
Activity
0No activity yet.