Recent

Author Topic: Detect changes in terminal size  (Read 3157 times)

CyberFilth

  • Jr. Member
  • **
  • Posts: 88
    • My github account
Detect changes in terminal size
« on: January 19, 2022, 12:18:01 pm »
I have a game that runs in the terminal, using the Video unit.
When the program first opens it calls a procedure that checks the terminal size by using the ScreenWidth constant. It then arranges the UI depending on how big the terminal is.

Is there a way to poll the terminal size every so often, to see if the terminal has been resized. So that the UI can be rearranged accordingly? Possibly using SIGWINCH?

My main issue is that the Video unit and the CRT unit don't mix well, so I can run ScreenWidth at the start of the program before Video is initialised, and at the beginning of each game loop. But it can't be running constantly to listen out for signals.

Does the Video unit have some way to tell if the terminal has been resized? Currently ScreenWidth can only tell me what the width was when the program was first run.
Running Windows 10 & Xubuntu 20.04 | Lazarus 2.0.12 | FPC 3.2.0

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: Detect changes in terminal size
« Reply #1 on: January 24, 2022, 10:02:36 pm »
I guess you use the CRT unit for capturing key events and writing to different position in different colors. If that is the case, you might take a look at the LazTermUtils package I created because of the limitiations of the CRT unit, you had the pleasure of experiencing yourself.

Quite intereting for you might be the example ColorTest.lpr, which draws colors to the whole terminal that change every frame:
Code: Pascal  [Select][+][-]
  1.     while True do
  2.     begin
  3.       //...
  4.       Term.Output.CursorGoto(0, 1);
  5.       v := (v + 1) mod 1001;
  6.       sz := Term.Size; // get the current size of the console
  7.       for i := 0 to (sz.Rows-1)*sz.Columns - 1 do //  iterate through all cells (rows * -1 because the first line is used to print fps, -1 at the end because this is 0 based)
  8.       begin
  9.         Term.Output.WriteModified(' ', [HSVtoRGB(i/((sz.Rows-1)*sz.Columns), 1, Abs(v-500)/500)]); // draw each cell individually
  10.       end;  
  11.       Term.Output.FlushBuffer; // as a buffering layer is used, to not have to call multiple writes to the console for each char, this will draw the whole screen at once
  12.       // ...
  13.     end;

It also provides functionality to read key presses non blocking (even key combinations with shift or ctrl), as in the example NonBlockingReadTest.lpr, which will loop and print the current time until escape was pressed:
Code: Pascal  [Select][+][-]
  1.     while true do
  2.     begin
  3.       Term.CursorGoto(0, 0);
  4.       DateTimeToString(s, 'dd.mm.yyyy hh:nn:ss', Now);
  5.       Term.Output.WriteLn(s);
  6.       Term.Output.WriteLn('To exit press escape');
  7.       if Term.Input.ReadKeyNonBlocking(key) then
  8.         if Key.SpecialKey and (Key.SpecialKeyCode = skEscape) then
  9.           Break;
  10.       Sleep(100);
  11.     end;
So if you want to build a game, you could combine these two and have a loop that first checks if a key was pressed, if so the internal state gets updated, then the size is checked and then it is computed if something needs to be redrawn.

Also unlike crt, which only supports 16 colors, LazTermUtils supports 24 bit true color. But only works on "modern" xterm compatible Terminals, so pretty much all Linux and MacOS Terminals and Windows 10 (no prior Windows versions). But the Windows terminal is really slow.
« Last Edit: January 24, 2022, 10:11:19 pm by Warfley »

MarkMLl

  • Hero Member
  • *****
  • Posts: 6646
Re: Detect changes in terminal size
« Reply #2 on: January 24, 2022, 10:17:40 pm »
Is there a way to poll the terminal size every so often, to see if the terminal has been resized. So that the UI can be rearranged accordingly? Possibly using SIGWINCH?

Assuming that you're talking about a character/text session:

Depends on the OS. On unix, the /kernel/ sends /you/ (note the direction here) SIGWINCH when the console size changes... there's example code in the console handler of the Lazarus IDE but I might also be able to dig something out if you want more.

To the best of my knowledge- and believe me I've looked over the years- there's nothing comparable in any OS of the Windows or enhanced-DOS lineage.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

CyberFilth

  • Jr. Member
  • **
  • Posts: 88
    • My github account
Re: Detect changes in terminal size
« Reply #3 on: January 25, 2022, 10:43:38 am »
Quote from: Warfley
I guess you use the CRT unit for capturing key events and writing to different position in different colors.
Actually I'm using the keyboard unit with the video unit for this game. I just use a crt function once before the game proper starts, then all key events are captured with keyboard.
I did come across LazTermUtils already whilst browsing though and I'll probably check it out properly at some point.

Quote from: MarkMLI
To the best of my knowledge- and believe me I've looked over the years- there's nothing comparable in any OS of the Windows or enhanced-DOS lineage.
Thanks for confirming that, I was looking for something cross-platform but Windows always seems to be the 'special case'
Running Windows 10 & Xubuntu 20.04 | Lazarus 2.0.12 | FPC 3.2.0

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: Detect changes in terminal size
« Reply #4 on: January 25, 2022, 11:07:32 am »
Actually I'm using the keyboard unit with the video unit for this game. I just use a crt function once before the game proper starts, then all key events are captured with keyboard.
You should be careful with the CRT unit. It is not a unit that simply provides functions. Just by including it in the uses it will massively change the behavior of the terminal. This is the reason why it does not work with other terminal units.
So maybe the easiest solution for you would be to just replace that one call to the CRT unit with something else. May I ask what is it what you do using CRT unit?

MarkMLl

  • Hero Member
  • *****
  • Posts: 6646
Re: Detect changes in terminal size
« Reply #5 on: January 25, 2022, 11:10:10 am »
Thanks for confirming that, I was looking for something cross-platform but Windows always seems to be the 'special case'

Not just Windows. I was an early-adopter of desktop multitasking OSes but slow to migrate from embedded+DOS to GUI programming, so while something like Desqview might have an equivalent to SIGWINCH I don't believe that anything from OS/2 onwards does.

In the Lazarus sources see debugger/test/watchconsolesize.pas which was derived from this older code:

Code: Pascal  [Select][+][-]
  1. var     sigNo, sigErr, sigCode, sigPid, winRows, winCols: integer; (* For debugging   *)
  2.  
  3.  
  4. (* We don't ever want to screw the MCP by responding to a casual ^C. We do,
  5.   however, want to do our best to shut down in good order if we get an urgent
  6.   signal such as SIGTERM, since it might indicate an incipient power failure.
  7. *)
  8. procedure termHandler(sig: longint; info: PSigInfo; context: PSigContext); cdecl;
  9.  
  10. var
  11.   winSize: TWinSize;
  12.  
  13. begin
  14.   with info^ do begin                   (* For debugging                        *)
  15.     sigNo := si_signo;
  16.     sigErr := si_errno;
  17.     sigCode := si_code;
  18.     sigPid := _sifields._kill._pid
  19.   end;
  20.   case sig of
  21.     SIGWINCH: begin
  22.                 FillChar(winSize, sizeof(winSize), 0);
  23.                 if IsaTty(StdInputHandle) = 1 then
  24.                   fpioctl(StdInputHandle, TIOCGWINSZ, @winSize);
  25.                 with WinSize do         (* For debugging                        *)
  26.                   if ws_row <> 0 then begin
  27.                     winRows := ws_row;
  28.                     WinCols := ws_col
  29.                   end
  30.               end;
  31. ...
  32.  

and

Code: Pascal  [Select][+][-]
  1.   procedure catchsignals;
  2.  
  3.   var     action: SigActionRec;
  4.  
  5.   begin
  6.     sigNo := 0;
  7.     FillChar(action, SizeOf(action), 0);
  8.     action.Sa_Handler := @termHandler;
  9.     action.Sa_Flags := SA_SIGINFO;
  10. ...
  11.     if fpSigAction(SIGWINCH, @action, nil) <> 0 then
  12.       wc('Warning: SIGWINCH not hooked, error ' + IntToStr(fpGetErrNo), true);
  13. ...
  14.  

So it looks as though the way I found that worked was (a) wait for notification from the kernel and (b) query the kernel's idea of the current terminal (xterm etc.) size.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

CyberFilth

  • Jr. Member
  • **
  • Posts: 88
    • My github account
Re: Detect changes in terminal size
« Reply #6 on: January 25, 2022, 11:22:25 am »
Quote from: Warfley
May I ask what is it what you do using CRT unit?
The code is at https://github.com/cyberfilth/ASCII-axe
The compiled binaries don't have the latest updates, but the source code has 2 units, one at resolution.pas which runs before the video unit is initialised. It gets the screen width and sets some global variables.

When you quit out of the game, and video is shut down, this procedure clears the screen and displays the random seed for debugging purposes.
Running Windows 10 & Xubuntu 20.04 | Lazarus 2.0.12 | FPC 3.2.0

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: Detect changes in terminal size
« Reply #7 on: January 25, 2022, 11:52:50 am »
Well, then I would suggest to either not clear the screen, and just add the new test to a new line below, or implement sceen clearing yourself.

For example, you could just use a few functions from my LazTermUtils compatibility unit to do so:
Code: Pascal  [Select][+][-]
  1. procedure ClearScreen;
  2. var
  3.   tmp: Cardinal;
  4. begin
  5.   tmp := InitOutputConsole(StandardOut);
  6.   try
  7.     Write(#27'[2J');
  8.   finally
  9.     ResetConsole(StandardOut, tmp);
  10.   end;
  11. end;
This should do.

Or if you don't mind adding another dependency, with LazTermUtils:
Code: Pascal  [Select][+][-]
  1. procedure ClearScreen;
  2. begin
  3.   With TTerminal.Create do
  4.   try
  5.     Output.Clear;
  6.   finally
  7.     Free;
  8.   end;
  9. end;

Thaddy

  • Hero Member
  • *****
  • Posts: 14158
  • Probably until I exterminate Putin.
Re: Detect changes in terminal size
« Reply #8 on: January 25, 2022, 01:24:43 pm »
But the point is that should be somewhere in the rtl, not somewhere specific to Lazarus.
Specialize a type, not a var.

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: Detect changes in terminal size
« Reply #9 on: January 25, 2022, 02:40:29 pm »
Laztermutils has nothing really to do with Lazarus, it's just a package I named this way  because it contains an LPK.

The code above can also be used by simply copying the used functions from the compatibility unit into your own code. After all the code is nothing more than:
Code: Pascal  [Select][+][-]
  1. procedure ClearScreen;
  2. {$IFDEF Windows}
  3. var
  4.   State: Cardinal;
  5.   SOut: THandle;
  6. {$Endif}
  7. begin
  8. {$IfDef WINDOWS}
  9.   SOut := GetStdHandle(STD_OUTPUT_HANDLE);
  10.   GetConsoleMode(SOut, State);
  11.   SetConsoleMode(SOut, State Or ENABLE_VIRTUAL_TERMINAL_INPUT);
  12.   try
  13. {$EndIf}
  14.   Write(#27'[2J');
  15. {$IfDef WINDOWS}
  16.   finally
  17.     SetConsoleMode(SOut, State);
  18.   end;
  19. {$EndIf}
  20. end;

And I completely disagree that this should be part of the RTL. Handling the Console is highly OS and TTY specific, and different systems have different capabilities. The RTL needs to provide the same functionality to all systems, which means that you will end up with things like the CRT unit, which is massively reduced in functionality to what the target could do (e.g. with restricting myself to xterm compatibility LazTermutils can have 24 bit colors, while the CRT can only 4 bit colors, because of compatibility to systems like DOS no one really uses anymore) and also provide basically an emulation layer in between to simulate functionality on some platforms (this is why many other console functions do not work with CRT, because it hijacks all the I/O to the console for it's emulation layer).

Pretty much no other cross plattform language I know of has the console functionality as part of their standard library. C/C++, Python, Java, etc. all rely on external libraries that might only work on certain systems because this is something that is very hard to unify over all the different platforms.

 

TinyPortal © 2005-2018