Recent

Author Topic: [SOLVED] Horrible console output  (Read 894 times)

lebao3105

  • New Member
  • *
  • Posts: 26
[SOLVED] Horrible console output
« on: April 02, 2026, 01:51:33 pm »
I was constantly trying to modify set of stdout+stderr attributes to my liking:
1. Map NL to CR-NL, as LineEnding on Linux and many other OSes is only NL
2. Disable mapping CR to NL
3. And maybe more if needed

termios attributes and flags can be found here: https://www.man7.org/linux/man-pages/man3/termios.3.html

The code is here:
Code: Pascal  [Select][+][-]
  1. var
  2.     { Where to write stuff into. }
  3.     OutputFile: Text;
  4.  
  5.     { Handle to where to write stuff into. }
  6.     OutputHandle: cint = StdOutputHandle;
  7.  
  8. { Check if Handle is a terminal. }
  9. fn isATerminal(Handle: cint): bool;
  10.  
  11. retn screenClear;
  12. retn setOutputStream(toStdErr: bool);
  13. fn ANSIEscapeSequence(seq: string): string;
  14.  
  15. // other unrelevant parts of the unit are not included
  16.  
  17. implementation
  18.  
  19. uses ...
  20.  
  21. var
  22.     originalOuts: array[0..1] of termios;
  23.     // 0st index: stdout
  24.     // 1st index: stderr
  25.  
  26. fn isATerminal(Handle: cint): bool;
  27. begin
  28.     return(isATTY(Handle) = 1);
  29. end;
  30.  
  31. retn setOutputStream(toStdErr: bool);
  32. begin
  33.     if toStdErr then begin
  34.         OutputFile := stderr;
  35.         OutputHandle := StdErrorHandle;
  36.     end
  37.     else begin
  38.         OutputFile := stdout;
  39.         OutputHandle := StdOutputHandle;
  40.     end;
  41. end;
  42.  
  43. retn modifyOutputHandle_Internal(for_stderr: bool);
  44. var
  45.     indx: int;
  46.     h: cint;
  47.     modi: termios;
  48. {$push} {$warn 5057 off} // Local variable seems to be not initialized
  49. begin
  50.     if for_stderr then begin
  51.         indx := 1;
  52.         h := StdErrorHandle;
  53.     end
  54.     else begin
  55.         indx := 0;
  56.         h := StdOutputHandle;
  57.     end;
  58.  
  59.     if not isATerminal(h) then
  60.         return;
  61.  
  62.     if tcgetattr(h, originalOuts[indx]) = -1 then
  63.         return; // TODO: Do something
  64.  
  65.     modi := originalOuts[indx];
  66.     modi.c_oflag := modi.c_oflag or ONLCR;
  67.     modi.c_oflag := modi.c_oflag or ONLRET;
  68.     modi.c_oflag := modi.c_oflag or ONOCR;
  69.     modi.c_oflag := modi.c_oflag and not OCRNL;
  70.  
  71.     if tcsetattr(h, TCSANOW, modi) = -1 then
  72.         return; // TODO: Do something
  73. end;
  74. {$pop}
  75.  
  76. retn restoreStdOut_Internal(for_stderr: bool);
  77. var
  78.     indx: int;
  79.     h: cint;
  80. begin
  81.     if for_stderr then begin
  82.         indx := 1;
  83.         h := StdErrorHandle;
  84.     end
  85.     else begin
  86.         indx := 0;
  87.         h := StdOutputHandle;
  88.     end;
  89.  
  90.     if not isATerminal(h) then
  91.         return;
  92.    
  93.     if tcsetattr(h, TCSANOW, originalOuts[indx]) = -1 then
  94.         return; // TODO: Do something
  95. end;
  96.  
  97. retn screenClear;
  98. begin
  99.     if not isATerminal(OutputHandle) then
  100.         return;
  101.  
  102.     write(OutputFile, ESC_KEY+'[2J');
  103.     write(OutputFile, ESC_KEY+'[H'); // move the cursor to the top-left corner
  104. end;
  105.  
  106. fn ANSIEscapeSequence(seq: string): string; inline;
  107. begin
  108.     if isATerminal(OutputHandle) then
  109.         return(seq);
  110.     return('');
  111. end;
  112.  
  113. initialization
  114.  
  115. InitKeyboard;
  116.  
  117. modifyOutputHandle_Internal({ for stderr }true);
  118. modifyOutputHandle_Internal(false);
  119.  
  120. finalization
  121.  
  122. DoneKeyboard;
  123.  
  124. Debug('Restoring default console attributes...', []);
  125. if modifiedStdIn and not disableRawStdIn then
  126.         Error('Failed to restore standard input attributes.', []);
  127.  
  128. restoreStdOut_Internal({ for stderr }true);
  129. restoreStdOut_Internal(false);
  130.  
  131. end.
  132.  

Broken output can be seen using program's --help option.
My program uses an array of TOption, which is:
Code: Pascal  [Select][+][-]
  1. type TOption = record
  2.         Long    : String;
  3.         Kind    : EOptKind;
  4.         Short   : Char;
  5.         Help    : string;
  6.  
  7.         { Write the full help message of the option, which includes:
  8.           * Short and long variants;
  9.           * Value parameter name - if any and the option needs a value;
  10.           * Default option value - if any and the option needs a value;
  11.           * The option's help message.
  12.         }
  13.         fn WriteFullHelpMessage(defaultVal: string = ''; valParam: string = 'VALUE'): string;
  14.     end;
  15.  
  16. fn TOption.WriteFullHelpMessage(defaultVal: string = ''; valParam: string = 'VALUE'): string;
  17. begin
  18.     if Short = #0 then
  19.         return;
  20.  
  21.     Result += ANSI_CODE_BOLD;
  22.  
  23.     if Long <> '' then
  24.         Result += ('--' + Long + '  ');
  25.    
  26.     // TOption.Short must not be empty as it's used for command-
  27.     // line parsing task. However ARGAs are terminated with 0ed
  28.     // entry, which of course has its Short a #0. Already been
  29.     // handled above.
  30.     Result += ('-' + Short + ANSI_CODE_RESET);
  31.  
  32.     if Kind <> EOptKind.FLAG then
  33.     begin
  34.         if Long <> '' then Result += '  ';
  35.         Result += valParam;
  36.  
  37.         if defaultVal <> '' then
  38.             Result += (' = ' + defaultVal);
  39.  
  40.     end;
  41.  
  42.     Result += (LineEnding#9 + Help + LineEnding);
  43. end;
  44.  

All TOption fields are not empty.

The problem is that not only NL is still not CRNL (as LineEnding is a constant), but also garbage datas get printed and help messages get (re)printed for unknown reason (I think it's not related to this topic):
Code: [Select]
Asks for user input.
                    M=��RD�3=�4+@�AD�3=�4+@--message  -m  VALUE
Custom message to show instead of Press any key/Enter to continue

M=��RD�3=�4+@�AD�3=�4+@--message  -m  VALUE
Custom message to show instead of Press any key/Enter to continue
--hide-input  -t
Hide inputs from the user

M=��RD�3=�4+@�AD�3=�4+@--message  -m  VALUE
Custom message to show instead of Press any key/Enter to continue
--hide-input  -t
Hide inputs from the user
--require-enter
               M=��RD�3=�4+@�AD�3=�4+@--message  -m  VALUE
Custom message to show instead of Press any key/Enter to continue
--hide-input  -t
Hide inputs from the user
--require-enter
               M=��RD�3=�4+@�AD�3=�4+@--message  -m  VALUE
Custom message to show instead of Press any key/Enter to continue
--hide-input  -t
Hide inputs from the user
--require-enter
               M=��RD�3=�4+@�AD�3=�4+@--message  -m  VALUE
Custom message to show instead of Press any key/Enter to continue
--hide-input  -t
Hide inputs from the user
--require-enter
               M=��RD�3=�4+@�AD�3=�4+@--message  -m  VALUE
Custom message to show instead of Press any key/Enter to continue
--hide-input  -t
Hide inputs from the user
--require-enter
               M=��RD�3=�4+@�AD�3=�4+@--message  -m  VALUE
Custom message to show instead of Press any key/Enter to continue
--hide-input  -t
Hide inputs from the user
--require-enter
               M=��RD�3=�4+@�AD�3=�4+@--message  -m  VALUE
Custom message to show instead of Press any key/Enter to continue
--hide-input  -t
Hide inputs from the user
--require-enter
               M=��RD�3=�4+@�AD�3=�4+@--message  -m  VALUE
Custom message to show instead of Press any key/Enter to continue
--hide-input  -t
Hide inputs from the user
--require-enter
               -o will show characters from -k inside squared brackets, e.g [ynYn].
If no arguments are provided, the program will simply ask for a key press, quits with exit code 0.
Otherwise will quit with the exit code set to the ASCII value of the input character.
                                                                                     fatal: unrecognized option: -o
                                                                                                                   debug: Restoring default console attributes...

Removing stdout and stderr modifications on application start (modifyOutputHandle_Internal calls) failed to work.

Are there any stdout/stderr modification from the RTL / C runtime that I do not know to happen on program start? Are my modifications actually correct? Should I manually override RTL's LineEnding to CRNL?

Hours later I may produce a small reproduction.

By the way, some compiler flags are used:
Code: [Select]
-dretn:=procedure
-dfn:=function
-dlong:=longint
-dulong:=longword
-dint:=integer
-dbool:=boolean
-dreturn:=exit

Fix: To fix, use SetTextLineEnding:
Code: Pascal  [Select][+][-]
  1. SetTextLineEnding(stderr, #13#10);
  2. SetTextLineEnding(stdout, #13#10);
  3.  

However this does NOT work if you use literal NL / UNIX LineEnding.
« Last Edit: April 03, 2026, 04:41:18 pm by lebao3105 »
My GitHub+GitLab username: lebao3105.

lebao3105

  • New Member
  • *
  • Posts: 26
Re: Horrible console output
« Reply #1 on: April 02, 2026, 05:40:45 pm »
As promised here is the reproduce program.
Oddly there are no garbage data being printed.
No additional compiler flags needed.
« Last Edit: April 02, 2026, 05:42:58 pm by lebao3105 »
My GitHub+GitLab username: lebao3105.

lebao3105

  • New Member
  • *
  • Posts: 26
Re: Horrible console output
« Reply #2 on: April 02, 2026, 05:50:58 pm »
Here is the modified test.pp in order to actually require no other compiler flags.
My GitHub+GitLab username: lebao3105.

Thaddy

  • Hero Member
  • *****
  • Posts: 19472
  • Glad to be alive.
Re: Horrible console output
« Reply #3 on: April 02, 2026, 06:30:59 pm »
That code is neither freepascal, nor delphi.
IGN what dialect are you using?
Any "programmer" that knows only one programming language is not a programmer

lebao3105

  • New Member
  • *
  • Posts: 26
Re: Horrible console output
« Reply #4 on: April 02, 2026, 06:44:05 pm »
That code is neither freepascal, nor delphi.
IGN what dialect are you using?

Free Pascal?
For the original code, I just put some compiler definitions. The lastest reproduce code however does not need any of them.
My GitHub+GitLab username: lebao3105.

lebao3105

  • New Member
  • *
  • Posts: 26
Re: Horrible console output
« Reply #5 on: April 02, 2026, 06:51:35 pm »
I figured out the cause of broken output indentations. It was... InitKeyboard. Oh dear...
It literally set the console to use RAW mode.
« Last Edit: April 02, 2026, 06:54:45 pm by lebao3105 »
My GitHub+GitLab username: lebao3105.

 

TinyPortal © 2005-2018