2

I maintain an extension for the Bash environment called Basta. Basta provides a scroll-protected status line at the bottom of your ANSI/VT100 terminal.

When Basta sets itself up, the effective number of lines, as known by the termios stuff in the kernel and the shell LINES variable decreases by 1. Resizing the terminal is handled nicely. Almost. There is trap for the SIGWINCH signal to call the update routine, which is also called after every command, prior to returning to the prompt.

However, from time to time I see a situation in which the scrolling region is lost. The prompt is being painted over the bottom line, where the user is editing commands and where command output appears, resulting in a mess.

I have a hypothesis on one how it can occur, which has reliable repro steps.

  1. We run a program that waits for input, while Bash is in the background, such as cat.

  2. While this program is running, we resize the window such that we shrink it by one line.

  3. Terminate the cat program.

If the terminal is resized while cat is running, Bash does not get the SIGWINCH (because, I think, SIGWINCH is only sent to the processes in the terminal sessions foreground process group, and Bash is in the Background at that moment). Our trap does not execute.

Basta's update function also relies on comparing the previous terminal size to the current to detect a size change.

Here is the problem: in this situation where a terminal with a scroll-protected status like has shrunk by one line, the number of lines appears not to have changed.

E.g. 40 line screen with 39 line scrolling region: LINES=39. Resize terminal to be 39 lines long. Scrolling region is gone. LINES=39 again. To the software, it looks like the same size, so it concludes that there has not been a resize. The status line is now being painted over top of the bottom row, over top of the user's input, because Basta wants to put it on row 40, which doesn't exist any more, so the cursor clamps to 39.

If Basta knows what a window size change occurred, it will not do the size comparison; in that situation it takes the slow path whereby it queries the terminal itself to determine the size. (It would be undesirable to do that for each update, because sending queries to the terminal is dodgy. If the user is typing rapidly, the terminal response can get mixed up with their keystrokes, and there can be a noticeable delay in getting a response from the terminal over laggy remote connections.)

Is there any clever way to know that the terminal has changed (or at least suspect it with a reasonably low false positive rate), in the absence of having received the WINCH trap, due to having been in the background, without talking to the terminal?

16
  • Have you considered reading the terminal's line count? It is a simple ioctl() call. Commented Feb 8 at 0:00
  • @DavidG. Didn't you get the memo? POSIX standardized this with tcgetattr in <termios.h>. We are a shell script, so our interface to the line count in the kernel is stty -a which dumps out info like rows 40 cols 80. In the problem situation, this doesn't help; the value matches what's in $LINES and also jibes with the previously known value that the script knows about. I.e. the size appears to be the same, though the terminal is smaller and we lost the scroll-protected line. Commented Feb 8 at 1:14
  • @DavidG. E.g. before we went to the background due to the execution of a command, we had a 42 line window, of which 1 line was protected from scrolling. stty -a would report rows 41, and LINES was 41. While we are backgrounded due to some program running, the window is resized to 41 lines. When the program terminates, we have rows 41 and LINES=41. But that's the full size of the window; the scroll-protected line is gone! We didn't get a SIGWINCH, and the sizes look the same. Oops! Commented Feb 8 at 1:17
  • 1) That tcgetattr() is the TCGETS ioctl call. 2) it will report the unreduced number of lines. E.g. before you went into background, you had LINES=41 and stty rows 42. After background you had LINES=41 and stty rows 41. 3) This change you can detect, without needing any signal. Commented Feb 8 at 12:00
  • Oh... you are changing THAT too. Then maybe that won't help. Commented Feb 8 at 12:06

1 Answer 1

2

Is there a reason basta.query_terminal_lines doesn't work?

Or, for those who haven't read the "basta" code:

Is there any reason you can't: save the cursor position in the terminal (ESC 7), move the cursor to the bottom row (ESC [ 9 9 9 9 ; 9 9 9 9 H), query the cursor position (ESC [ 6 n), restore the cursor position (ESC 8), and then read the escape sequence sent by the query?

This turns out to be a costly by effective way to get the current screen size when one has changed the internally stored values. Part of why this works is that the explicit cursor movement ignores the scrolling region on ansi/xterm/... style terminals. (I think I've used a different terminal that didn't.)

Note that the number 9999 needs to larger than the screen could be. With a new 7680 × 4320 display, and the "nil2" font, the window could be 2160 lines and 7680 columns. (And that doesn't count Xinerama, or windows too large to display.) 9999 may not be large enough for long.

2
  • Indseed, what I oversaw is that the code already some part of this logic in basta.check_cursor which is executed on every prompt: it queries the cursor position to determine whether it has gone out of bounds into the status line area. That function avoids executing the logic if there is pending TTY input. So Basta already accepts querying the terminal; extra steps of querying the size can be added there. I've merged some patches for this, including ones that add a holdoff time in addition to checking for TTY input. Commented Feb 11 at 2:51
  • So in the end, there probably is no solution without querying the terminal, but we can avoid hammering the terminal with queries, or interjecting them when there is pending user input that we would have to discard to get to the terminal's response. There is a small race condition there, but ah well. Commented Feb 11 at 2:54

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.