[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