tty.cc

//*************************************************************************
//  MODULE : TTY - Device Driver to emulate a simple teletype emulator	  *
//  AUTHOR : Ron Chernich                                                 *
//  PURPOSE: This class used by RCOS to instantiate devices for the	  *
//	     "Operator's console" and the (two) TTY "user terminals".	  *
//  HISTORY:                                                              *
//   05-APR-93	Second Implementation following previous flawed design	  *
//   08-APR-93	ANSI terminal driver and Error Signals 41, 42 added	  *
//   12-MAY-93	Bugs in WriteBlk and ANSI gotoxy squashed		  *
//   18-MAY-93	Memory leaks plugged (always kill arrays with delete[] !) *
//   27-OCT-93  Limit cursor hiding to "screen" region                    *
//   01-APR-94  Allow TTY mode to be changed via an IoCtrl message	  *
//   20-APR-94  YAM - YetAnotherMemoryleak plugged in GetKey/PutKey	  *
//*************************************************************************

  #include "tty.hh"
  #include "kernel.hh"


  #define XM	    3	    // Offset, screen left edge to first character
  #define BUFLEN    32	    // Size for input ring buffer
  #define FATAL     TRUE    // Define fatal and simple error conditions
  #define WARN	    FALSE


//////////////////////////////////////////////////////////////////////////
// Class constructor must run base class constructor first.  Passed params
// specify display size and location (in pixels) in a rect struct, and the
// colors to use for the foreground and background.  The "screen" size in
// chars is determined from the pixel dimensions and a char array allocated
// and cleared to all spaces.
//
tty::tty (UINT16 id, UINT16 cls, Knl *pK, rect &pR, UINT16 nFg, UINT16 nBg)
    :port(id, cls, pK), RngBuf(BUFLEN), nFgnd(nFg), nBgnd(nBg)
{
  x = pR.ul.x, y = pR.ul.y;
  dx = pR.lr.x, dy = pR.lr.y;
  nRows = dy / GfxTextHeight();
  nCols = (dx - 2) / GfxTextExtent("w");
  ddy = (dy - (GfxTextHeight() * nRows)) >> 1;
  pVmem = new char[(nRows * nCols)+1];
  if (pVmem) {
    nMode = TTY_Silent;
    Reset(TRUE);
  }
  else {
    nMode = TTY_Failed;
    DevError(ET_InitFail, FATAL);
  }
}

////////////////////
// Destructor must release "video" memory
//
tty::~tty (void)
{
  if (pVmem)
    DELETE_ARRAY pVmem;
}

//////////////////////
// Receive and action a message. Like a good driver, TTY opens, closes,
// can be read, written (char and vlock modes) and responds to specific
// IO control requests..
//
void tty::RxPort (PMSG pM)
{
  switch (pM->wMsgType & ~MM_Sync) {
    case KM_Open :
      if (uDest)
	DevError(ET_StillOpen, WARN);
      else {
	Reset(TRUE);
	uDest = pM->wSender;
      }
      break;
    case KM_Close :
      if (uDest)
	uDest = ID_NULL;
      else
	DevError(ET_NotOpen, WARN);
      break;
    case KM_Read :
      if (uDest == pM->wSender)
	GetKey(pM);
      else
	DevError(ET_InvalidReq, WARN);
      break;
    case KM_KeyPress :
      if (nMode & TTY_UConly)
	pM->wParam = (UINT16)toupper(pM->wParam);
      PutKey((char)pM->wParam);
      break;
    case KM_Write :
      Display((char*)&pM->wParam, 1);
      break;
    case KM_WriteBlk :
      Display((char*)pM->pBody, pM->wParam);
      break;
    case KM_IoCtrl :
      if (uDest) {
	UINT16 wStat = 0xffff;
	switch (pM->wParam) {
	  case DM_Reset:
	    Reset((pM->wSender == ID_Kernel) ? TRUE : FALSE);
	    break;
	  case DM_KeyHit:
	    wStat = (UINT16)RngBuf.RngStat();
	    break;
	  case DM_GetSize:
	    wStat = (UINT16)((nRows << 8) | nCols);
	    break;
	  case DM_GetPos:
	    wStat = (UINT16)(((idx / nCols) << 8) | (idx % nCols));
	    break;
 	  case DM_GetMode:
 	    wStat = (UINT16)nMode;
	    break;
	  case DM_SetMode:
	    nMode = *(INT16*)pM->pBody;
 	    wStat = (UINT16)nMode;
	    break;
	default:
	  DevError(ET_NoSupport, WARN);
	}
	if (IS_SYNCH(pM->wMsgType))
	  pM->wParam = wStat;
	else {
	  PMSG pM = new message(uID, KM_IoCtrl, wStat);
	  pTx->PostMsg(pM->wSender, pM);
	}
      }
      break;
  default:
    DevError(ET_NoSupport, WARN);
  }
}

/////////////////
// Clear the video RAM and, if the display is active, clear the screen
// and home the cursor.  If bool param is set, we must also reinitialize
// the the keyboard buffer.
//
void tty::Reset (BOOL bFlush)
{

  if (bFlush) {
    while (RngBuf.RngStat())
      RngBuf.RngGet();
    uDest = ID_NULL, uCnt = 0;
  }
  idx = jdx = nEscape = 0;
  ycurr = y + ddy, xcurr = x + XM;
  memset(pVmem, ' ', (nRows * nCols));
  if (nMode & TTY_Active) {
    GfxRect(x, y, x+dx, y+dy, GFX_Fill, nBgnd);
    TtyCursor(ON);
  }
}

//////////
// Refresh the display from the input buffer.  For optimum speed,
// trailing blanks are truncated - the whole line even, maybe..
//
void tty::ReFresh (BOOL bClear)
{
  char *pBuf = new char[nCols+1];

  if (NULL == pBuf)
    DevError(41, FATAL);
  else {
    char *cp;
    INT16  i, j;
    Mickey.AutoPointer(x, y, x+dx, y+dy);
    if (bClear)
      GfxRect(x, y, x+dx, y+dy, GFX_Fill, nBgnd);
    GfxSetClip(x, y, x+dx, y+dy);
    GfxTextColor(nFgnd);
    for (i = 0, cp = pVmem; i < nRows; i++, cp += nCols) {
      strncpy(pBuf, cp, nCols);
      j = nCols - 1;
      while ((pBuf[j] == ' ') && (j >= 0))
	--j;
      pBuf[j+1] = '\0';
      if (*pBuf)
	GfxText(x+XM, y+ddy+(i*GfxTextHeight()), pBuf, GFX_Transparent);
    }
    GfxClrClip();
    TtyCursor(ON);
    Mickey.ShowPointer();
    DELETE_ARRAY pBuf;
  }
}

//////////////////////
// Provided the thing is not stuffed, change its mode.
// RETURNS: previous mode
//
INT16 tty::SetMode (INT16 nNew)
{
  INT16 nOld = nMode;

  if (!(nMode & TTY_Failed))
    nMode = nNew;
  return nOld;
}

/////////////
// The ANSI Terminal Driver.  This is a subset of the ISO 6429 standard in:
// 1.  Sequences 'h', 'l', 'p' are NOT implemented.
// 2.  The 'm' (graphics) sequence implement Fore/Background colors only -
//     ie, Attributes (Blink etc) are ignored.
// The tty class default if line-wrap on. Since 'l' is not implemented,
// there is no way to change this at present.
//
INT16 tty::AnsiDriver (char ch)
{
  static short aColor[8] = {0, 4, 2, 14, 1, 5, 3, 15};

  if (nEscape == 1) {
    n1 = n2 = 0;
    return ((ch == '[') ? 2 : 0);
  }
  else {
    INT16 i, j;
    switch (ch) {
      case 'A':     // <--------------------------------- Cursor Up and Down
      case 'B':
	if (nEscape == 3) {
	  j = ((ch == 'A') ? MIN((idx/nCols),n1) : MIN((idx/nCols),n1));
	  if (j) {
	    i = nCols * j;
	    idx += (ch == 'A') ? -i : i;
	    i = GfxTextHeight() * j;
	    ycurr += (ch == 'A') ? -i : i;
	    ch = pVmem[idx], pVmem[idx] = '\0';
	    i = idx - (idx % nCols);
	    xcurr = x + XM + GfxTextExtent(pVmem + i) * j;
	    pVmem[idx] = ch;
	  }
	}
	break;
      case 'C':
      case 'D':     // <------------------------------ Cursor Right and Left
	if (nEscape == 3) {
	  j = ((ch == 'C') ?
	    MIN((idx-((idx%nCols)+1)),n1) : MIN(((idx % nCols)+1),n1));
	  if (j) {
	    idx += (ch == 'C') ? j : -j;
	    i = idx - (idx % nCols);
	    ch = pVmem[idx], pVmem[idx] = '\0';
	    xcurr = x + XM + GfxTextExtent(pVmem + i);
	    pVmem[idx] = ch;
	  }
	}
	break;
      case 'f':
      case 'H':     // <------------------------------------ Position Cursor
	if (nEscape >= 3) {
	  n1 = MIN(n1,(nRows-1)), n2 = MIN(n2,(nCols-1));
	  idx = (nCols * n1) + n2;
	  ycurr = y + ddy + (GfxTextHeight() * n1);
	  ch = pVmem[idx], pVmem[idx] = '\0';
	  xcurr = x + XM + GfxTextExtent(pVmem + (n1 * nCols));
	  pVmem[idx] = ch;
	}
	break;
      case 'J':     // <----------------------- Clear screen and home cursor
	if (((char)n1 == '2') && (nEscape == 3))
	  Reset(FALSE);
	break;
      case 'K':	    // <------------------------------- Erase to end of line
	for(i = idx, j = idx + nCols - (idx % nCols); i < j; i++)
	  pVmem[i] = ' ';
	GfxRect(xcurr,ycurr,x+dx,ycurr+GfxTextHeight(),GFX_Fill,nBgnd);
	break;
      case 'm':     // <------------------- Set Graphics Mode (Fg/Bg Colors)
	if (nEscape >= 4)
	  while (n1 >= 30) {
	    if ((j = n1 % 10) <= 7) {
	      if (3 == (n1 / 10))
		nFgnd = aColor[j];
	      if (4 == (n1 / 10))
		nBgnd = aColor[j];
	    }
	    n1 = n2, n2 = 0;
	  }
	break;
      case 's':     // <----------------------	save current cursor position
	jdx = idx;
	break;
      case 'u':	    // <---------------------------- restore cursor position
	idx = jdx;
	ycurr = y + ddy + (GfxTextHeight() * idx / nCols);
	ch = pVmem[idx], pVmem[idx] = '\0';
	xcurr = x + XM + GfxTextExtent(pVmem + idx - (idx % nCols));
	pVmem[idx] = ch;
	break;
      case ';':     // <------------------------- process argument separator
	return (nEscape + 1);
    default:
      (nEscape > 2) ? (n2 = (INT16)ch) : (n1 = (INT16)ch);
      return (nEscape + 1);
    }
  }
  return 0;
}

/////////////////
// Copy the passed data into the screen buffer, interpreting any special
// characters as we go.	If the display is "visible", echo the changes to
// the screen - I'd like a better Bell routine, though.  The unusual amount
// of code in the routines that set the horizontal pixel var  is to
// accomodate proportional fonts (Microsoft - looks good, by costs!)
//
// CAUTION! The Auto Line-feed mode relies on the ACR case falling
//	    through to the ALF case.. don't change the order!
//
void tty::Display (char *cp, INT16 n)
{

  if (nMode & TTY_Active) {
    Mickey.AutoPointer(x, y, x+dx, y+dy);
    TtyCursor(OFF);
    GfxSetClip(x, y, x+dx, y+dy);
  }
  while (n--)
    if (nEscape) {
      nEscape = AnsiDriver(*cp++);
      continue;
    }
    else {
      switch (*cp) {
	case ABEL:
	  fputc('\a', stdout);
	  break;
	case BKSP:
	  if (idx) {
	    --idx;
	    char cTemp = pVmem[idx];
	    pVmem[idx] = '\0';
	    if (((idx % nCols) == (nCols - 1)) && (ycurr > y + ddy))
	      ycurr -= GfxTextHeight();
	    xcurr = x + GfxTextExtent(pVmem + idx - (idx % nCols)) + XM;
	    pVmem[idx] = cTemp;
	  }
	  break;
	case AFF :
	  Reset(FALSE);
	  break;
	case ACR:
	  xcurr = x+XM;
	  idx -= idx % nCols;
	  if (0 == (nMode & TTY_AutoLF))
	    break;
	case ALF:
	  if (idx >= nCols * (nRows - 1))
	    Scroll();
	  else {
	    idx += nCols;
	    ycurr += GfxTextHeight();
	  }
	  break;
	case ESC:
	  if (nMode & TTY_UseANSI)
  	    nEscape = 1;
	  break;
      default:
	if ((*cp >= ' ') && (*cp < DEL)) {
	  pVmem[idx++] = *cp;
	  char cTemp = pVmem[idx];
	  pVmem[idx] = '\0';
	  if (nMode & TTY_Active) {
	    GfxTextColor(nFgnd);
	    GfxText(xcurr, ycurr, pVmem+idx-1, GFX_Transparent);
	  }
	  if (idx % nCols) {
	    xcurr += GfxTextExtent(pVmem+idx-1);
	    pVmem[idx] = cTemp;
	  }
	  else {
	    xcurr = x + XM;
	    if (idx / nCols < nRows) {
	      ycurr += GfxTextHeight();
	      pVmem[idx] = cTemp;
	    }
	    else {
	      Scroll();
	      idx = nCols * (nRows - 1);
	    }
	  }
	}
      } 			    // end switch
      ++cp;
    }				    // end while
  if (nMode & TTY_Active) {
    TtyCursor(ON);
    GfxClrClip();
    Mickey.ShowPointer();
  }
}

////////////////////
// Scroll the video RAM, filling the last line with blanks and scroll the
// visible screen too, if it is active.  Again, scrolling would probably
// run faster if the "video RAM" buffer used a start-of-scan pointer, but
// the arrays should be small and I'm running out of time...
//
// YABBWA .. Yet another Borland Bug Work-Around!
//   The Borland farmalloc/farfree functions eventually fail, resulting
//   in no scroll.  Since this is un-acceptable, we'll do a character
//   based scroll if (when) IMALLOC returns NULL.  Needless to say, the
//   Microsoft C/C++ 7.00 runs with no problem.  jeeze.
//   ** News Flash: this probably due to Borland's delete not operating
//   ** like Microsoft's .. could be fixed now - need testing (18-MAY-93).
//
void tty::Scroll (void)
{
  INT16 nLastLine = nCols * (nRows - 1), x2 = x+dx, y0 = y+ddy;

  memcpy(pVmem, pVmem+nCols, nLastLine);
  memset(pVmem+nLastLine, ' ', nCols);
  if (nMode & TTY_Active) {
    PIMBUF pImg = GfxGetImage(x, y0+GfxTextHeight(), x2, y+dy);
    if (pImg) {
      GfxPutImage(x, y0, pImg);
      GfxFreeImage(pImg);
    }
    else {
      char *cp, *pBuf = new char[nCols+1];
      if (NULL == pBuf)
        DevError(42, FATAL);
      else {
        for (INT16 i = 0, j = nCols; i < nRows; i++, j += nCols) {
          GfxRect(x, y0, x2, y0+GfxTextHeight(), GFX_Fill, nBgnd);
          strncpy(pBuf, pVmem+j, nCols);
          cp = pBuf + nCols - 1;
          while ((*cp == ' ') && (cp >= pBuf))
            --cp;
          *(cp+1) = '\0';
          if (*pBuf)
            GfxText(x+XM, y0, pBuf, GFX_Transparent);
          y0 += GfxTextHeight();
        }
	DELETE_ARRAY pBuf;
      }
    }
    GfxRect(x, y0+(GfxTextHeight()*(nRows-1)), x2, y+dy, GFX_Fill, nBgnd);
  }
}

//////////////////////
// Routine controls processing of characters read from keyboard.
// If there an outstanding request for data, service it immediatly
// Otherwise add the key to the ring buffer.  The char is written
// to video RAM (and perhaps displayed) if terminal is set to Simplex.
//
void tty::PutKey (char ch)
{

  if (nMode & TTY_Simplex)
    Display(&ch, 1);
  RngBuf.RngPut(ch);
  if (uCnt) {
    --uCnt;
    MSG msg(uID, KM_Read, (UINT16)RngBuf.RngGet());
    pTx->PostMsg(uDest, &msg);
  }
}

//////////////////////
// Service a request for key data.  Several things should never happen,
// but let's be defensive..  First, we should be "open" and the request
// must have come from whoever has us open.  Next, characters can only
// be sent if we happen to have any, otherwise we increment a request-for-
// data count and exit (this count will be inspected when data finally
// arrives).  If we have data we either place it in the requesting message
// struct if the transfer was synchronous, or post a message if the request
// was async.
//
// ASIDE: I'm not all that keen on this one character per message scheme
// It will probably bog down - a more flexible arrangement like a Block
// mode request may ultimately be a better way to fly.. END OF ASIDE.
//
void tty::GetKey (PMSG pM)
{

  if (RngBuf.RngStat() == FALSE)
    ++uCnt;
  else {
    char ch = RngBuf.RngGet();
    if (IS_SYNCH(pM->wMsgType))
      pM->wParam = (UINT16)ch;
    else {
      MSG msg(uID, KM_Read, (UINT16)ch);
      pTx->PostMsg(uDest, &msg);
    }
  }
}

/////////////
// Draw or hide the cursor at the current X/Y location in a style
// determined by the top two bits of the mode word..
//
void tty::TtyCursor (UINT16 state)
{
  char   st2[2];
  INT16  dy = ycurr + GfxTextHeight();
  UINT16 nFhue = ((state == ON) ? nFgnd : nBgnd);
  UINT16 nBhue = ((state == ON) ? nBgnd : nFgnd);

  switch (nMode & TTY_CsrMask) {
    case TTY_IBar:
      GfxTextColor(nFhue);
      GfxMoveTo(xcurr-2, ycurr), GfxLineTo(xcurr-1, ycurr);
      GfxMoveTo(xcurr+2, ycurr), GfxLineTo(xcurr+1, ycurr);
      GfxMoveTo(xcurr-2, dy),	 GfxLineTo(xcurr-1, dy);
      GfxMoveTo(xcurr+2, dy),	 GfxLineTo(xcurr+1, dy);
      GfxMoveTo(xcurr, ycurr+1), GfxLineTo(xcurr, dy-1);
      break;
    case TTY_InvBlk:
      st2[0] = pVmem[idx], st2[1] = '\0';
      GfxTextColor(nFhue);
      GfxTextColorBg(nBhue);
      GfxText(xcurr, ycurr, st2, GFX_Replace);
      //GfxRect(xcurr, ycurr, xcurr+GfxTextExtent(st2), dy, GFX_Fill, hue);
      GfxTextColorBg(nFhue);
      GfxTextColor(nBhue);
      break;
    case TTY_Uscore:
      GfxTextColor(nFhue);
      st2[0] = pVmem[idx], st2[1] = '\0';
      GfxMoveTo(xcurr, dy - GFX_UlnOffset);
      GfxLineTo(xcurr+GfxTextExtent(st2), dy-GFX_UlnOffset);
      GfxTextColor(nBhue);
      break;
    case TTY_CursorOff:
      break;
  }
}

/********************************** eof **********************************/