https://gitlab.synchro.net/main/sbbs/-/issues/1223#note_10155
Correction: the root cause I posted above is wrong, and the fix that was committed for it does not fix anything. It has been reverted.
Deuce was right to push back on the ESC ESC framing. That repro was a distraction, and the ncurses read-ahead theory does not explain the reported symptom. Here is what the symptom actually is, with the evidence.
## The ESC never reaches the descriptor
strace of the real scfg running inside GNU screen. ESC was written to the terminal at 21:55:02.107. The process polled fd 0 every ~1 ms and got
Timeout continuously for three seconds:
```
21:55:02.100049 pselect6(1, [0], NULL, NULL, {tv_sec=0, tv_nsec=0}, NULL) = 0 (Timeout)
... ~3000 more of the same ...
21:55:05.110272 read(0, "\33", 1) = 1 <-- only when the NEXT key arrived 21:55:05.110316 read(0, "\33", 1) = 1
21:55:05.112085 read(0, "[", 1) = 1
21:55:05.112122 read(0, "B", 1) = 1
```
The second key (a Down-arrow) was sent at 21:55:05.109. Both the stranded ESC and the arrow's own bytes surfaced together at that moment. So the ESC byte
was not sitting in an ncurses queue - it was not on the file descriptor at
all. Nothing curses-side could have retrieved it.
## It is GNU screen, and the trigger is mouse reporting
A raw stdin reader, no curses linked in at all, timestamping every read():
| DECSET modes enabled | lone ESC |
| --- | --- |
| none | delivered after 300 ms (screen's maptimeout) |
| 1000 | HELD |
| 1002 | HELD |
| 1003 | HELD |
| 1006 | delivered after 300 ms |
| 1000,1006 | HELD |
| 1003,1006 | HELD |
So the moment the application turns on X10/normal mouse reporting, screen
stops applying its maptimeout to a trailing ESC and holds it until the next input byte arrives. The application then runs exactly one keystroke behind, permanently - which is the reported symptom.
The hold is unbounded, not a long timeout. Waiting 10 seconds after a lone
ESC, the inner process had read nothing; the next keypress delivered
`b'\x1bq'` in one read.
tmux does not do this: with 1000 enabled, ESC is delivered immediately.
A bare ncurses probe using the exact init sequence from curs_initciolib() (newterm, cbreak, noecho, nonl, keypad, halfdelay(1), raw, timeout(10), ESCDELAY=25) handles a lone ESC correctly inside screen. Adding mousemask()
to that same probe, and changing nothing else, makes it go one key behind and stay there.
## Why it hits us
curs_initciolib() calls mousemask(), so every ciolib curses-mode program
picks this up - scfg, uedit, echocfg, umonitor, and the terminal server's
local console - and only when run inside GNU screen. Outside screen there is
no lag, which is why this took so long to characterize.
Reproducing it needs nothing from Synchronet:
```
screen -S t python3 -c '
import os,sys,time,tty,termios
fd=sys.stdin.fileno(); termios.tcgetattr(fd); tty.setraw(fd)
os.write(1, b"\x1b[?1000h") # comment this out and ESC arrives t0=time.time()
while True:
d=os.read(fd,4096)
open("/tmp/rawin.log","a").write("%8.3f %r\n"%(time.time()-t0,d))
if b"q" in d: break
'
```
Press ESC, wait, then press another key, and look at /tmp/rawin.log. With
1000 enabled the ESC is timestamped with the second key, not on its own.
I have not formed a view on what we should do about it, since the defect is
in screen rather than here. Options seem to be to stop enabling mouse
reporting when running under screen (STY is set for its children), or to
treat it as a screen bug and leave it, or both.
-- *Authored by Claude (Claude Code), on behalf of @rswindell*
--- SBBSecho 3.37-Linux
* Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)