Terminal IScreen best approach

+1 vote
asked Apr 21 by rexh (200 points)
edited Apr 22 by rexh

We are trying to implement our own display of the terminal screen using the IScreen interface and this is the skeleton of the code:

class RebexScreen : IScreen
{
   RebexClient m_TnClient;
   public RebexScreen (RebexClient cc)
   {
      m_TnClient = cc;
   }
   public void Clear(int left, int top, int width, int height)
   {
      // clear screen data
   }
   public void Copy(int sourceLeft, int sourceTop, int targetLeft, int targetTop, int width, int height)
   {
      // copy screen data
   }
   public void Resize(int width, int height)
   {
   }
   public void Scroll(int rows)
   {
     // move screen data up
   }
   public void SetColor(int foreground, int background)
   {
   }
   public void SetCursorPosition(int x, int y, bool visible)
   {
      if (visible)
      {
         // update cursor pos
      }
   }
   public void Write(int left, int top, string text)
   {
      TerminalCell cell = m_TnClient.m_rxScript.Terminal.Screen.GetCell(left, top);
      // my drawing code:
   }
}

class RebexClient 
{
   public async Task<bool> ConnectAsync(ConnectionSettings cs)
   {
      m_rxTelnet = new Telnet(cs.HostNameIP, cs.Port, cs.SupportSSL ? SslMode.Explicit : SslMode.None);
      m_rxTelnet.Timeout = cs.TimeOutSec * 1000;
      TerminalOptions options = new TerminalOptions() {
         TerminalType = TerminalType.Ansi,
         Columns = cs.CustomCols,
         Rows = cs.CustomRows,
      };
      options.ColorScheme = Rebex.TerminalEmulation.ColorScheme.Custom;
      options.SetColorIndex(SchemeColorName.Foreground, 2);
      options.SetColorIndex(SchemeColorName.Background, 6);
      try
      {
         m_rxScript = null;
         m_rxScript = await m_rxTelnet.StartScriptingAsync(options).ConfigureAwait(false);
         RebexScreen my = new RebexScreen(this);
         TerminalExtensions.SetCustomScreen(m_rxScript.Terminal, my);
         m_rxScript.Terminal.Disconnected += Terminal_Disconnected;
         m_rxScript.Terminal.DataReceived += Terminal_DataReceived;
      }
      catch (NetworkSessionException ex)
      {
         switch (ex.Status)
         {
            case NetworkSessionExceptionStatus.UnclassifiableError:
               break;
            case NetworkSessionExceptionStatus.OperationFailure:
               break;
            case NetworkSessionExceptionStatus.ConnectFailure:
               break;
            case NetworkSessionExceptionStatus.ConnectionClosed:
               break;
            case NetworkSessionExceptionStatus.SocketError:
               break;
            case NetworkSessionExceptionStatus.NameResolutionFailure:
               break;
            case NetworkSessionExceptionStatus.ProtocolError:
               break;
            case NetworkSessionExceptionStatus.OperationAborted:
               break;
            case NetworkSessionExceptionStatus.ServerProtocolViolation:
               break;
            case NetworkSessionExceptionStatus.Timeout:
               break;
            case NetworkSessionExceptionStatus.AsyncError:
               break;
            default:
               break;
         }
         ReportError(ex.Message);
      }
      catch (Exception e)
      {
         ReportError(e.Message);
      }
      Task.Run(() =>
      {
         try
         {
            while (m_rxScript.Process(int.MaxValue) != TerminalState.Disconnected) ;
         }
         catch {}
      }).Forget();
      return true;
   }
   private void Terminal_DataReceived(object sender, DataReceivedEventArgs e)
   {
   }
   private void Terminal_Disconnected(object sender, EventArgs e)
   {
   }
   public async Task DisconnectAsync()
   {
      await Task.Run(() =>
      {
         try
         {
            m_rxScript.Terminal.Dispose();
            m_rxScript.Close();
         }
         catch {}
      }).ConfigureAwait(false);
   }
   public async Task<bool> SendAsync(byte[] acData, int nOffset, int nLen)
   {
      await Task.Run(() => m_rxScript.SendData(acData, nOffset, nLen)).ConfigureAwait(false);
      return true;
   }
}

1) I am assuming the IScreen.Write is the place to implement the drawing of text to my screen and the way to get the color and underline attributes is by calling the GetCell method there. Is that correct?

2) Setting the default foreground and background using SetColorIndex does not seem to work. I tried it with the Terminal Control and it works there but not here with the VirtualTerminal. Am I missing something?

3) Should I be using the Terminal_DataReceived event? But with that, there is no information about position of text.

4) Are there attributes for reverse video, double width/double height? If not, any plans of providing that in the near future?

5) Any way to disable 8-bit control characters?

Thank you.

1 Answer

0 votes
answered Apr 23 by Lukas Matyska (46,810 points)

Please check the sample implementation which uses System.Console.

The IScreen is very similar to System.Console. If you want to write a text to specific position, you have to set the CursorLeft and 'CursorTopfirst. If you want to write a text with specific color, you have to setForegroundColor` and so on. This will help to understand my answers to your questions:

1) IScreen.Write() is the place to implement the drawing of text. If a color is changed by the server, the IScreen.SetColor() is called first, then the IScreen.Write() follows.
There is no support for additional cell attributes such as Underline, Italic, etc. To get these values, you have to use ITerminal.Screen.GetCell().

2) The TerminalOptions.ColorScheme applies only to TerminalControl. VirtualTerminal holds the original data received from server. To handle colors, use IScreen.SetColor() method.

3) No, please see sample project.

4) There are no attributes for reverse video nor double width/height. Reverse video is applied immediately in, so the fore and back colors of VirtualTerminal are swapped. This is not applied in IScreen. Double width/height are not supported and we have no plans to add support for this.

5) What do you mean by disabling 8-bit control characters exactly?

commented Apr 29 by rexh (200 points)
Thank you Lukas for the answers and the sample project which is very helpful.

4) I understand that it will require quite a bit of changes to display the double width/double height on the terminal but would it be possible to just provide those information as properties of the TerminalCell?

5) I believe some VT hosts are 7-bit and data sent from the client has to be in 7-bit so 8-bit data has to be escaped, such as CSI as ESC [.

While testing, I also discovered an issue with Rebex telnet implementation, it looks like it does not handle FF F2 (Data Mark) or FF F1 (NOP) or FF F9 and they are treated as data.
commented Apr 30 by Lukas Matyska (46,810 points)
4) we will consider to add suport for this and let you within day or two.

5) all escape sequences are sent as 7-bit always. 8-bit is sent only for user input (if non-ASCII characters are to be sent)

FF F1 to FF F9 are ignored, but they are not treated as data. Actaully, in our automated tests we encounter weird behavior of a Telnet server, which sometimes send only F2 without preceeding FF. In this case, F2 is treated as regular data naturally.

If you want to dignose the issue, please send us Verbose log of the communication to support@rebex.net (it can be done like this https://www.rebex.net/kb/logging).
commented Apr 30 by rexh (200 points)
4) Thank you!
5) I don't understand VT too much but will some host always expect 8-bit escape sequences?

I have emailed the log file to support.
commented May 3 by Lukas Matyska (46,810 points)
edited Jun 29 by Lukas Matyska
4) We decided to add support for double width/height characters (DECDWL/DECDHL control function) using terminal.ActionRequested event.
The reason is that both control function applies to whole line (not single cell).

5) I am not aware of any host accepting 8-bit escape sequences only.

Just to inform others.
The communication log showed that there is bug when processing 0x0D 0xFF sequence. In this case (<CR> followed by a Telnet option), the Telnet option is reported as data (it is not processed as Telnet option).

UPDATE:
Both, the fix and new feature were released in version 2018 R2.
commented May 3 by rexh (200 points)
Excellent! Thank you very much!
commented Jun 8 by rexh (200 points)
Hi Lukas,
Do you have any idea when the DECDWL/DECDHL support will become available?
Thanks,
commented Jun 11 by Lukas Matyska (46,810 points)
The feature is already implemented. It is waiting for revision. I hope it will be included in next public release (2018 R2). However, if you want to receive BETA version now, please let me know.
commented Jun 11 by rexh (200 points)
Yes, I would like to have beta version. thank you.
commented Jun 11 by Lukas Matyska (46,810 points)
I have sent you link for the latest BETA to your email.
commented Jun 11 by rexh (200 points)
Got it. Thank you!
...