[BRLTTY] Linux console hacking (was: Re: Footsteps towards better accessibility in Linux)
Nicolas Pitre
nico at fluxnic.net
Mon Oct 5 00:50:58 UTC 2026
On Sun, 4 Oct 2026, Samuel Thibault wrote:
> I have had a look at enter_alt_screen, it uses csi_J(CSI_J_FULL) to
> clear the screen. I see in csi_J that it first calls flush_scrollback
> and then only calls scr_memsetw. Thus flush_scrollback happens to move
> the cursor, before the screen is cleared. I guess that what happens is
> that this cursor move makes brltty have the time to read the top of the
> screen before it is cleared.
That can't be it. do_con_write() holds the console lock for the whole
write() buffer, so the full \e[?1049h -> enter_alt_screen() -> csi_J()
sequence is atomic with respect to vcs_read(), which needs the same
lock. And flush_scrollback() doesn't actually move the cursor: it
recomputes vc_pos from the same x/y, and no VT_UPDATE notification is
sent until the end of do_con_write().
The actual problem _could_ be on the BRLTTY side. A screen snapshot is
made of three separate system calls:
1. ioctl(VT_GETCONSIZECSRPOS) for the size and cursor position
2. pread() on /dev/vcsa for glyphs and attributes
3. pread() on /dev/vcsu for the Unicode text
On top of that, vcs_read() drops the console lock between page-sized
chunks, so even a single read of a large screen isn't atomic.
When a curses program exits, it typically moves the cursor to the bottom
row and then sends \e[?1049l, which restores the main screen and the
cursor position saved on entry. If that lands between steps 1 and 2,
BRLTTY gets the cursor from the alternate screen and the text from the
restored main screen. Autospeak follows the cursor, so it speaks the
bottom line of the main screen, i.e. stale output from before the
program was started. The next refresh corrects things right away, which
is why nothing shows up on the braille display. But speech can't be
taken back, and since the corrected cursor line is usually blank,
autospeak has nothing new to say that would interrupt it.
The kernel does raise POLLPRI on /dev/vcsa for that update, and BRLTTY
does refresh again, but only after the core has already processed the
inconsistent snapshot.
The potential fix is in BRLTTY's Linux screen driver:
- read the vcsa content first: each vcsa read resets its pending
update indicator, so everything read afterwards is covered (the
size from the ioctl is only used to validate what was read);
- after the snapshot, poll /dev/vcsa with a zero timeout, and retake
the snapshot (up to 3 attempts) if an update is pending, which
also covers a resize in the middle.
That way an inconsistent snapshot never reaches the core. It's here:
https://github.com/npitre/brltty/tree/linux-screen-snapshot
Sébastien, could you give it a try? Running with -l scrdrv logs "screen
updated during cache refresh - retrying" each time the race is caught,
which would confirm the diagnosis.
Nicolas
More information about the BRLTTY
mailing list