Logo Search packages:      
Sourcecode: vdr-plugin-console version File versions

engine.c

/*
 * engine.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: engine.c,v 1.1 2004/10/24 12:57:09 chelli-guest Exp $
 */


#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stropts.h>

#include <vdr/config.h>
#include <vdr/remote.h>

#include "engine.h"
#include "i18n.h"





// ----- cConsoleTerminalEmulation ----------------------------------------------------------

cTerminalEmulation::cTerminalEmulation() {

  _w = _h = 0;
  _curX = _curY = 0;

  _modeCurVisible = true;  // TODO load default values
  _modeOrigin = false;
  _modeWrapAround = true;
  _modeInsert = false;
  _modeNewLine = false;

  _curPosSaved = NULL;

  _scrollRegionTop = 0; _scrollRegionBottom = Setup.OSDheight-2-1;

  _state = eCssNormal;

  _changed = false;
  _bell = false;

  _wantRefreshEvent = false;

  _escapeParams = NULL;
  _escapeParamsCount = 0;

  // initialize all possibilities
  for ( int i = 0; i < CONSOLE_MAXROWS; ++i ) {
    _screen[ i ] = 0;
  }

  // initialize tabulators
  int tab = 0;
  for ( int i = 0; i < CONSOLE_MAXCOLS; ++i ) {
    _tabs[ i ] = tab+=8;
  }

  // No idea if this is the right size but it is
  // a good value to start.
  // The real size will be setted from the OSD menu.
  setSize( Setup.OSDwidth, Setup.OSDheight-2 );

  SelectCharSet( 0, 'B' );
}



cTerminalEmulation::~cTerminalEmulation() {

  // No thread should use this object if it will be destroyed.
  cMutexLock l( &_mutex );

  // releasing all allocated buffers
  for ( int i = 0; i < _h; ++i ) {
    delete _screen[ i ];
  }

  while ( _curPosSaved ) {
    sConScreenCursorPos* prev = _curPosSaved->prev;
    delete( _curPosSaved );
    _curPosSaved = prev;
  }
}



void cTerminalEmulation::SelectCharSet( int g, char set ) {

  //fprintf( stderr, "New CharSet selected: %i, %c\n", g, set );

  // First clear all
  for ( int i = 0; i <= 255; ++i )
    _charSet[ i ] = i;

  if ( set == 'A' || set == 'B' ) {
    ; // Already setted

  } else if ( g == 1 && set == '0' ) {

    // Map the 7-bit special chars to the 8-bit ASCII chars
    _charSet[  96 ] =  32;
    _charSet[  97 ] = 177;
    _charSet[  98 ] =  32;
    _charSet[  99 ] =  32;
    _charSet[ 100 ] =  32;
    _charSet[ 101 ] =  32;
    _charSet[ 102 ] = 248;
    _charSet[ 103 ] = 241;
    _charSet[ 104 ] =  32;
    _charSet[ 105 ] =  32;
    _charSet[ 106 ] = 217;
    _charSet[ 107 ] = 191;
    _charSet[ 108 ] = 218;
    _charSet[ 109 ] = 192;
    _charSet[ 110 ] = 197;
    _charSet[ 111 ] = 196;
    _charSet[ 112 ] = 196;
    _charSet[ 113 ] = 196;
    _charSet[ 114 ] = 196;
    _charSet[ 115 ] = 196;
    _charSet[ 116 ] = 195;
    _charSet[ 117 ] = 180;
    _charSet[ 118 ] = 193;
    _charSet[ 119 ] = 194;
    _charSet[ 120 ] = 179;
    _charSet[ 121 ] = 243;
    _charSet[ 122 ] = 242;
    _charSet[ 123 ] = 227;
    _charSet[ 124 ] =  32;
    _charSet[ 125 ] = 156;
    _charSet[ 126 ] = 249;
  }

}



void cTerminalEmulation::Changed() {
  // Must not be locked here, becaus it can
  // only be called from a method that is
  // already locked.
  if ( ! _changed && _wantRefreshEvent ) {
    _changed = true;
    cRemote::Put( (eKeys)kRefresh );
  }
  _changed = true;
}



void cTerminalEmulation::Refreshed() {
  _changed = false;
}



void cTerminalEmulation::BellSeen() {
  _bell = false;
}



bool cTerminalEmulation::setSize( int w, int h ) {

  cMutexLock l( &_mutex );

  // if nothing is to do then exit
  if ( w == _w && h == _h )
    return false;

// fprintf( stderr, "Area resized: old=%i, %i; new=%i, %i\n", _w, _h, w, h );

  // if the width is the same as before when we don't need to
  // realloc rows that exist already and should exist afterwards. 
  int RowsNoChangeNeeded = min( h, _h );

  if ( w != _w ) {
    
    for ( int row = 0; row < RowsNoChangeNeeded; ++row ) {
      
      sConScreenChar* &screenRow = _screen[ row ];
      screenRow = (sConScreenChar*) realloc( (void*)screenRow, w * sizeof( sConScreenChar ) );
      
      // initialize new cols if any
      for( int col = _w; col < w; ++col ) {
        screenRow[ col ] = _defaultChar;
      }
    }
  }

  // allocating new rows if any
  for ( int row = RowsNoChangeNeeded; row < h; ++row ) {
    
    sConScreenChar* &screenRow = _screen[ row ];
    screenRow = (sConScreenChar*) realloc( (void*)screenRow, w * sizeof( sConScreenChar ) );
    
    // initialize new cols
    for ( int col = 0; col < w; ++col ) {
      screenRow[ col ] = _defaultChar;
    }
  }


  // releasing old rows if any
  for ( int row = h; row < _h; ++row ) {
    sConScreenChar* &screenRow = _screen[ row ];
    delete( screenRow );
    screenRow = 0;
  }

  // change scroll region
  if ( _scrollRegionTop == 0 && _scrollRegionBottom == _h - 1 )
    _scrollRegionBottom = h - 1;

  // save new size
  _w = w; _h = h;

  Changed();

  return true;
}



// Clears the hole screen
void cTerminalEmulation::Clear( int fromX, int fromY, int toX, int toY ) {

  // check the range
  if ( fromX < 0 )
    fromX = 0;
  else if ( fromX >= _w )
    fromX = _w - 1;
  
  if ( fromY < 0 )
    fromY = 0;
  else if ( fromY >= _h )
    fromY = _h - 1;

  if ( toX < 0 || toX >= _w )
    toX = _w - 1;

  if ( toY < 0 || toY >= _h )
    toY = _h - 1;


  for ( int row = fromY; row <= toY; ++row ) {

    sConScreenChar* screenRow = _screen[ row ];

    for ( int col = fromX; col <= toX; ++col ) {
      screenRow[ col ] = _defaultChar;
    }
  }

  Changed();
}



void cTerminalEmulation::ScrollUp( int count, int FromLine ) {

  // scroll only if we are in the scrolling range
  if ( _curY >= _scrollRegionTop && _curY <= _scrollRegionBottom ) {

    for ( int i = 0; i < count; ++i ) {

      delete( _screen[ FromLine ] );

      for ( int row = FromLine; row <= _scrollRegionBottom - 1; ++row ) {
        _screen[ row ] = _screen[ row + 1 ];
      }

      sConScreenChar* &screenRow = _screen[ _scrollRegionBottom ];
      screenRow = (sConScreenChar*) malloc( _w * sizeof( sConScreenChar ) );
  
      for ( int col = 0; col < _w; ++col ) {
        screenRow[ col ] = _defaultChar;
      }
    }

    Changed();
  }
}



void cTerminalEmulation::ScrollDown( int count, int FromLine ) {

  // scroll only if we are in the scrolling range
  if ( _curY >= _scrollRegionTop && _curY <= _scrollRegionBottom ) {

    for ( int i = 0; i < count; ++i ) {

      delete( _screen[ _scrollRegionBottom ] );

      for ( int row = _scrollRegionBottom - 1; row >= FromLine; --row ) {
        _screen[ row + 1 ] = _screen[ row ];
      }

      sConScreenChar* &screenRow = _screen[ FromLine ];
      screenRow = (sConScreenChar*) malloc( _w * sizeof( sConScreenChar ) );
  
      for ( int col = 0; col < _w; ++col ) {
        screenRow[ col ] = _defaultChar;
      }
    }

    Changed();
  }
}



void cTerminalEmulation::Write( const unsigned char* stream ) {

  cMutexLock l( &_mutex );

  if ( *stream ) {

    while ( *stream ) {
      Write( _charSet[ *stream ] );
      ++stream;
    }

  } else {
    // Signal, that the client has terminated
    if ( _wantRefreshEvent )
      cRemote::Put( (eKeys)kRefresh );
  }
}



void cTerminalEmulation::Write( unsigned char ch ) {

#if 0

  if ( ch == '\n' )
    fprintf(stderr,"{\\n}");
  else if ( ch == '\r' )
    fprintf(stderr,"{\\r}");
  else if ( ch == '\b' )
    fprintf(stderr,"{\\b}");
  else if ( ch == '\t' )
    fprintf(stderr,"{\\t}");
  else if ( ch == 27 )
    fprintf(stderr,"{ESC}");
  else if ( ch < 32 )
    fprintf(stderr,"{\\%i}", ch);
  else if ( ch >= 32 )
    fprintf(stderr,"%c", ch );

#endif

  if ( ch == SI )
    SelectCharSet( 0, '0' );
  if ( ch == SO )
    SelectCharSet( 1, '0' );


  // search for escape sequences
  switch ( _state ) {

  case eCssNormal:

    switch ( ch ) {

    // interpret control chars
    case ESC:   _state = eCssEscape;  break; // begin of escape sequence

    case CR:    keyCarriageReturn();  break;
    case LF:
    case FF:
    case VT:    keyLineFeed( true );  break;

    case HT:    keyTab();             break;
    case BS:    keyBackspace();       break;
    case '\a':  keyBell();            break; // 7
    case 127:   keyBackspace();       break;

    // display all other chars
    default:    keyInsert( ch );
    }
    break;

  case eCssEscape:

    if ( ch == '[' ) {

      // ok, the escape sequence begins here
      _state = eCssEscapeParameter;

    } else if ( ch == '#' ) {
      _state = eCssEscapeSingleCode;

    } else if ( ch == '(' ) {
      _state = eCssSelectCharSetG0;


    } else if ( ch == ')' ) {
      _state = eCssSelectCharSetG1;
    
    } else {

      decodeEscapeCode( ch );

      // and back to normal mode
      _state = eCssNormal;
    }
    break;

  case eCssEscapeParameter:

    switch (ch) {
      case '0' ... '9':

        if ( _escapeParams == 0 ) {

          // for the first parameter we must allocate memory
        
          _escapeParams = (int*) malloc( sizeof( int ) );
          *_escapeParams = ch - '0';
          _escapeParamsCount = 1;
      
        } else {

          // assemble figures to a number
          int &Param = _escapeParams[ _escapeParamsCount - 1 ];
          Param = Param * 10 + ( ch - '0' );

        }
        break;

      case ';':

        // here a new parameter follows
        _escapeParams = (int*) realloc( (void*) _escapeParams, ++_escapeParamsCount * sizeof( int ) );
        _escapeParams[ _escapeParamsCount - 1 ] = 0;
        break;

      case '?':
        break; // ignore the questionmark

      case CAN:
      case SUB:

        // cancel the sequence
        free( _escapeParams );
        _escapeParams = NULL;
        _escapeParamsCount = 0;

        _state = eCssNormal;
        break;

      default:

        // end of sequence reached
        decodeEscapeSequence( ch );

        free( _escapeParams );
        _escapeParams = NULL;
        _escapeParamsCount = 0;

        _state = eCssNormal;
        break;
    }

    break;

  case eCssEscapeSingleCode:
    
    decodeEscapeSingleCode( ch );
    _state = eCssNormal;
    break;

  case eCssSelectCharSetG0:
  case eCssSelectCharSetG1:

    SelectCharSet( _state - eCssSelectCharSetG0, ch );
    _state = eCssNormal;
    break;

  }
}




#define CONSOLE_PARAM( _index, _default ) (( _index < _escapeParamsCount ) ? _escapeParams[ _index ] : _default)

void cTerminalEmulation::decodeEscapeSequence( char code ) {

  switch ( code ) {

  case 'G':   MoveTo( CONSOLE_PARAM( 0, 1 ) - 1, _curY );                        break; // move to column
  case 'd':   MoveTo( _curX, CONSOLE_PARAM( 0, 1 ) - 1 );                        break; // move to line
  case 'f':
  case 'H':   MoveTo( CONSOLE_PARAM( 1, 1 ) - 1, CONSOLE_PARAM( 0, 1 ) - 1 );    break; // move to

  case 'e':
  case 'A':   MoveRelative( 0, - CONSOLE_PARAM( 0, 1 ) );                        break; // n rows up
  case 'B':   MoveRelative( 0,   CONSOLE_PARAM( 0, 1 ) );                        break; // n rows down
  case 'a':
  case 'C':   MoveRelative(   CONSOLE_PARAM( 0, 1 ), 0 );                        break; // n cols right
  case 'D':   MoveRelative( - CONSOLE_PARAM( 0, 1 ), 0 );                        break; // n cols left
  case 'E':   MoveRelative( -_curX,   CONSOLE_PARAM( 0, 1 ) );                   break; // n rows down, first col
  case 'F':   MoveRelative( -_curX, - CONSOLE_PARAM( 0, 1 ) );                   break; // n rows up, first col


  case 'K':   if ( _escapeParamsCount == 0 || _escapeParams[ 0 ] == 0 ) {        // Clear to end of line
                ClearToEnd();

              } else if ( _escapeParams[ 0 ] == 1 ) {                            // Clear from begin of line
                ClearFromBegin();

              } else if ( _escapeParams[ 0 ] == 2 ) {                            // Clear the hole line
                ClearLine();
              }                                                                  break;


  case 'J':   if ( _escapeParamsCount == 0 || _escapeParams[ 0 ] == 0 ) {        // Clear to end of screen
                ClearToEnd();
                if ( _modeOrigin )
                  Clear( 0, _curY + 1, _w - 1, _scrollRegionBottom );
                else
                  Clear( 0, _curY + 1, _w - 1, _h - 1 );

              } else if ( _escapeParams[ 0 ] == 1 ) {                            // Clear from begin of screen
                if ( _modeOrigin )
                  Clear( 0, _scrollRegionTop, _w - 1, _curY - 1 );
                else
                  Clear( 0, 0, _w - 1, _curY - 1 );
                ClearFromBegin();

              } else if ( _escapeParams[ 0 ] == 2 ) {                            // Clear the hole screen
                if ( _modeOrigin )
                  Clear( 0, _scrollRegionTop, _w - 1, _scrollRegionBottom );
                else
                  Clear();
              }
              MoveTo( 0, 0);                                                     break;

  case 'n':   reportDeviceStatus( CONSOLE_PARAM( 0, 0 ) );                       break;
  case 'c':   reportDeviceAttributes( CONSOLE_PARAM( 0, 0 ) );                   break;

  case 'm':   setAttributes( _escapeParams, _escapeParamsCount );                break; // set display attributes

  case 'h':   setModes( _escapeParams, _escapeParamsCount );                     break; // put in screen mode
  case 'l':   resetModes( _escapeParams, _escapeParamsCount );                   break; // resets Mode

  case 'r':   setScrollRegion( CONSOLE_PARAM(0, 1)-1, CONSOLE_PARAM(1, CONSOLE_MAXROWS)-1);    break;
  case 's':   CursorPosSave();                                                   break; // saves cursor position
  case 'u':   CursorPosRestore();                                                break; // return to saved cursor position

  case 'g':   if ( _escapeParamsCount == 0 || _escapeParams[ 0 ] == 0 ) {        // Clear tab at position
                tabStopRemove( _curY );

              } else if ( _escapeParams[ 0 ] == 3 ) {                            // Clear all tabs
                tabStopClear();
              }                                                                  break;

  case 'W':   if ( _escapeParamsCount == 0 || _escapeParams[ 0 ] == 0 ) {        // Add tab at position
                tabStopAdd( _curY );

              } else if ( _escapeParams[ 0 ] == 2 ) {                            // Clear tab at position
                tabStopRemove( _curY );

              } else if ( _escapeParams[ 0 ] == 5 ) {                            // Clear all tabs
                tabStopClear();
              }                                                                  break;

  case '@':   InsertChar( CONSOLE_PARAM( 0, 1 ) );                               break; // insert n spaces
  case 'X':
  case 'P':   DeleteChar( CONSOLE_PARAM( 0, 1 ) );                               break; // Delete n chars
  case 'L':   ScrollDown( CONSOLE_PARAM( 0, 1 ), _curY );                        break; // Insert n lines
  case 'M':   ScrollUp( CONSOLE_PARAM( 0, 1 ), _curY );                          break; // Delete n lines


  // ignore these
  case 'y':   break; // self test
  case 'q':   break; // show lamp on keyboard (1..4, 0 = off)

  default:    CONSOLE_DEBUG( 
                fprintf(stderr,"unknown escape sequence %c ", code );
                for ( int i = 0; i < _escapeParamsCount; ++i ) {
                  fprintf(stderr,"%i;", _escapeParams[ i ] );
                }
                fprintf(stderr,"\n");
              );
              break;
  }
}



void cTerminalEmulation::decodeEscapeCode( char code ) {

  switch ( code ) {

  case 'D':   keyLineFeed( false );                                              break;
  case 'E':   keyLineFeed( false ); MoveTo( 0, _curY );                          break;

  case 'M':   if ( _curY == _scrollRegionTop )
                ScrollDown( 1 );
              else if ( _curY > 0 ) {
                --_curY; Changed();
              }                                                                  break;

  case '@':   if ( _curX < _w ) { ++_curX; DeleteChar( 1 ); --_curX; }           break; // discard subsequent char

  case 'H':   tabStopAdd( _curX );                                               break; // set column tab

  case 'Z':   reportDeviceAttributes( 0 );                                       break;

  case '7':   CursorPosSave();                                                   break;
  case '8':   CursorPosRestore();                                                break;

  // ignore these
  case '=':   break; // Application Keypad
  case '>':   break; // Numeric Keypad
  case 'c':   break; // reset terminal

  default:  CONSOLE_DEBUG( printf("unknown escape code %c\n", code ) );
  }
}



void cTerminalEmulation::decodeEscapeSingleCode( char code ) {

  switch ( code ) {

  // not supported
  case '3':          // double height/width
  case '4':
  case '5':          // single width line
  case '6': break;

  case '8': break;   // show test pattern

  default:  CONSOLE_DEBUG( printf("unknown escape single code %c\n", code ) );
  }
}



void cTerminalEmulation::tabStopClear() {

  for ( int i = 0; i < CONSOLE_MAXCOLS; ++i ) {
    _tabs[ i ] = 0;
  }
}



void cTerminalEmulation::tabStopAdd( int tabstop ) {

  // search for right position in tab stop array
  for ( int i = 0; i < CONSOLE_MAXCOLS; ++i ) {

    if ( _tabs[ i ] == tabstop ) {
      // the tab stop is already here
      break;

    } else if ( _tabs[ i ] == 0 ) {
      // append on the end
      _tabs[ i ] = tabstop;
      break;

    } else if ( tabstop < _tabs[ i ] ) {

      //found the right place to insert
      for ( int j = CONSOLE_MAXROWS - 2; j >= i; --j ) {
        _tabs[ j + 1] = _tabs[ j ];
      }
      _tabs [ i ] = tabstop;
      break;
    }
  }
}



void cTerminalEmulation::tabStopRemove( int tabstop ) {

  // search the position of the tab stop
  for ( int i = 0; i < CONSOLE_MAXCOLS; ++i ) {

    if ( _tabs[ i ] == tabstop ) {

      int j = i;
      for ( ; j < CONSOLE_MAXCOLS - 1; ++j ) {
        _tabs[ j ] = _tabs[ j + 1 ];
      }
      if ( j == CONSOLE_MAXCOLS - 1 ) {
        _tabs[ j ] = 0;
      }
      break;
    }
  }
}



void cTerminalEmulation::MoveTo( int col, int row ) {

  int x = min( max( col, 0 ), _w - 1 );
  int y;
  if ( _modeOrigin )
    y = min( max( row, _scrollRegionTop ), _scrollRegionBottom );
  else
    y = min( max( row, 0 ), _h - 1 );

  if ( x != _curX || y != _curY ) {
    _curX = x; _curY = y;

    if ( _modeCurVisible )
      Changed();
  }
}



void cTerminalEmulation::MoveRelative( int d_col, int d_row ) {

  MoveTo( _curX + d_col, _curY + d_row );
}



void cTerminalEmulation::ClearFromBegin() {

  sConScreenChar* text = _screen[ _curY ];

  for ( int col = 0; col <= _curX; ++col ) {
    text[ col ] = _defaultChar;
  } 

  Changed();
}



void cTerminalEmulation::ClearToEnd() {

  sConScreenChar* text = _screen[ _curY ];

  for ( int col = _curX; col < _w; ++col ) {
    text[ col ] = _defaultChar;
  } 

  Changed();
}



void cTerminalEmulation::ClearLine() {

  sConScreenChar* text = _screen[ _curY ];

  for ( int col = 0; col < _w; ++col ) {
    text[ col ] = _defaultChar;
  } 

  Changed();
}



void cTerminalEmulation::InsertChar( int count ) {

  if ( count > 0 ) {

    if ( count > _w - _curX )
      count = _w - _curX;

    sConScreenChar* text = _screen[ _curY ];

    for ( int i = _w - count - 1; i >= _curX; --i ) {
      text[ i + count ] = text[ i ];
    }

    // clear the moved cols and keep the attributes from the first col
    for ( int i = _curX; i < _curX + count; ++i ) {
      text[ i ] = _defaultChar; //sConScreenChar( _defaultChar.ch, text[ _curX ] );
    }

    Changed();
  }
}



void cTerminalEmulation::DeleteChar( int count ) {

  if ( count > 0 ) {

    if ( count > _w - _curX )
      count = _w - _curX;

    sConScreenChar* text = _screen[ _curY ];

    for ( int i = _curX; i < _w - count; ++i ) {
      text[ i ] = text[ i + count ];
    }

    // clear the erased cols and keep the attributes from the last col
    for ( int i = _w - count; i < _w; ++i ) {
      text[ i ] = sConScreenChar( _defaultChar.ch, text[ _w - 1 ] );
    }

    Changed();
  }
}



void cTerminalEmulation::reportDeviceStatus( int request ) {
/*
  if ( _fd >= 0 ) {

  char buf[ 10 ];

  switch ( request ) {

    case 5:   write( _fd, buf, sprintf( buf, "%c[0n", ESC ) );                 break;     // -> no malfunctions
    case 15:  write( _fd, buf, sprintf( buf, "%c[?13n", ESC ) );               break;     // -> no printer connected

    case 6:   write( _fd, buf, sprintf( buf, "%c[%i;%iR", ESC, getCursorY()+1, getCursorX()+1 ) ); break; // -> report curor position

    default:  CONSOLE_DEBUG( fprintf( stderr, "CONSOLE: Unknown device status rewuest: %i\n", request ) );
  }

  } else
    dsyslog("console: file descriptor for output not set!");
*/
}



void cTerminalEmulation::reportDeviceAttributes( int request ) {
/*
  if ( _fd >= 0 ) {

  char buf[ 10 ];

  switch ( request ) {

    case 0:   write( _fd, buf, sprintf( buf, "%c[?1;2c", ESC ) );                break;   // -> i am a VT102

    default:  CONSOLE_DEBUG( fprintf( stderr, "CONSOLE: Unknown device attributes request: %i\n", request ) );
  }

  } else
    dsyslog("console: file descriptor for output not set!");
*/
}











void cTerminalEmulation::keyCarriageReturn() {

  MoveTo( 0, _curY );
}



void cTerminalEmulation::keyLineFeed( bool NewLine ) {

  // are we in the scrolling area?
  if ( _curY == _scrollRegionBottom ) {
      ScrollUp( 1 );

  } else {

    // no, don't scroll
    if ( _curY < _h - 1 )
      ++_curY;
      if ( _modeCurVisible )
        Changed();
  }

  if ( NewLine && _modeNewLine ) {
    keyCarriageReturn();
  }
}



void cTerminalEmulation::keyBackspace() {

  if ( _curX > 0 ) {

    // in insert mode, first move the other chars leftwards
    if ( _modeInsert ) {

      sConScreenChar* text = _screen[ _curY ];

      for ( int i = _curX - 1; i < _w - 1; ++i ) {
        text[ i ] = text[ i + 1 ];
      }
    }

    --_curX;
    Changed();
  }
}



void cTerminalEmulation::keyDelete() {

  sConScreenChar* text = _screen[ _curY ];

  for ( int col = _curX; col < _w - 1; ++col ) {
    text[ col ] = text[ col + 1 ];
  }
  text[ _w - 1 ] = _defaultChar;

  Changed();
}



void cTerminalEmulation::keyTab() {

  for ( int i = 0; i < CONSOLE_MAXCOLS; ++i ) {

    if ( _tabs[ i ] > _curX ) {

      for ( int j = _tabs[ i ] - _curX; j > 0; --j )
        keyInsert( ' ' );

      return;
    }
    if ( _tabs[ i ] == 0 )
      break;
  }

  // no tab found -> do nothing
}



void cTerminalEmulation::keyInsert( unsigned char ch ) {

  // ignore all not printable chars
  if ( ch < ' ' )
    return;

  int x = _curX, y = _curY;

  // This is to prevent a line feed if the cursor is on
  // the last col. In this case the cursor stayes out of
  // the screen until it will be placed elsewhere or
  // another char will put in. Then a line feed and if
  // necessary also a scroll up will accour.

  if ( _modeWrapAround ) {

    if ( ++_curX > _w ) {
      keyCarriageReturn(); keyLineFeed( false );
      return;
    }
  } else {

    if ( ++_curX >= _w )
      _curX = x = _w - 1;
  }

  sConScreenChar* text = _screen[ y ];

  // in insert mode, first move the other chars rightwards
  if ( _modeInsert ) {

    for ( int i = _w - 1; i > x; --i ) {
      text[ i ] = text[ i - 1 ];
    }
  }

  text[ x ] = sConScreenChar( ch, _defaultChar );

  Changed();
}



void cTerminalEmulation::keyBell() {

  // misuse the incoming console (tty) to produce the bell for us
  printf("\a"); fflush( STDIN_FILENO );

  if ( ! _bell && _wantRefreshEvent ) {
    _bell = true;
    cRemote::Put( (eKeys)kRefresh );
  }
  _bell = true;
}



void cTerminalEmulation::setScrollRegion( int top, int bottom ) {

  if ( top < 0 )
    top = 0;
  else if ( top >= _h-1 )
    top = _h-2;

  // range minimum 2 rows!
  if ( bottom < top+1 )
    bottom = top+1;
  else if ( bottom >= _h )
    bottom = _h-1;

  _scrollRegionTop = top;
  _scrollRegionBottom = bottom;
}



void cTerminalEmulation::CursorPosSave() {

  _curPosSaved = new sConScreenCursorPos( _curX, _curY, _defaultChar, _modeOrigin, _curPosSaved );
}



void cTerminalEmulation::CursorPosRestore() {

  sConScreenCursorPos* pOld = _curPosSaved;
  if ( pOld ) {

    _modeOrigin  = pOld->modeOrigin;

    if ( _curX != pOld->x || _curY != pOld->y ) {
      _curX = pOld->x; _curY = pOld->y;
      Changed();
    }

    _defaultChar = pOld->attributes;

    _curPosSaved = pOld->prev;
    delete( pOld );

  } else {
    // No cursor position stored, so set the cursor to home.
    MoveTo( 0, 0 );
  }
}



void cTerminalEmulation::setAttributes( int* attributes, int count ) {

  if ( count > 0 ) {

    for ( int i = 0; i < count; ++i ) {
  
      switch ( attributes[ i ] ) {

      case 0:  _defaultChar = sConScreenChar();         break;  // reset all attributes

      case  1: _defaultChar.Bold        = true;         break;  // set style
      case 22: _defaultChar.Bold        = false;        break;  // set style
      case  4: _defaultChar.Underscore  = true;         break;
      case 24: _defaultChar.Underscore  = false;        break;
      case  5: _defaultChar.Blink       = true;         break;
      case 25: _defaultChar.Blink       = false;        break;
      case  7: _defaultChar.Inverted    = true;         break;
      case 27: _defaultChar.Inverted    = false;        break;
      case  8: _defaultChar.Concealed   = true;         break;
      case 28: _defaultChar.Concealed   = false;        break;

      case 30 ... 37:                                           // set foreground color
               _defaultChar.foreColor = attributes[ i ] - 30;
               _defaultChar.useFore = true;             break;
      case 39: _defaultChar.useFore = false;            break;

      case 40 ... 47:                                           // set background color
               _defaultChar.backColor = attributes[ i ] - 40;
               _defaultChar.useBack = true;             break;
      case 49: _defaultChar.useBack = false;            break;

      default: return;                                  break;
      }

    } // end for

  } else {

    // no params -> reset attributes
    _defaultChar = sConScreenChar();
  }
}



void cTerminalEmulation::setModes( int* attributes, int count ) {

  for ( int i = 0; i < count; ++i ) {

    switch (attributes[ i ]) {
    case 4:    _modeInsert = true;                      break;
    case 6:    setModeOrigin( true );                   break;
    case 7:    _modeWrapAround = true; MoveTo( 0, 0 );  break;
    case 20:   _modeNewLine = true;                     break;
    case 25:   setModeCurVisible( true );               break;

    default: CONSOLE_DEBUG( fprintf( stderr, "unknown mode set %i\n", attributes[ i ] ) );
    }
  }
}



void cTerminalEmulation::resetModes( int* attributes, int count ) {

  for ( int i = 0; i < count; ++i ) {

    switch (attributes[ i ]) {
    case 4:    _modeInsert = false;                     break;
    case 6:    setModeOrigin( false );                  break;
    case 7:    _modeWrapAround = false; MoveTo( 0, 0 ); break;
    case 20:   _modeNewLine = false;                    break;
    case 25:   setModeCurVisible( false );              break;

    default: CONSOLE_DEBUG( fprintf( stderr, "unknown mode reset %i\n", attributes[ i ] ) );
    }
  }
}



void cTerminalEmulation::setModeCurVisible( bool visible ) {

  if ( _modeCurVisible != visible ) {
    _modeCurVisible = visible;
    Changed();
  }
}



void cTerminalEmulation::setModeOrigin( bool origin ) {

  if ( _modeOrigin != origin ) {
    _modeOrigin = origin;
    MoveTo( 0, 0  );
  }
}



// --- cVirtualConsole ----------------------------------------------------------



cVirtualConsole::cVirtualConsole(const char* title, const char* command, char* const argv[]) {

  _master = -1;
  _childPid = 0;
  _isOpen = false;

  _title = strdup( title );

  Open( command, argv );
}



cVirtualConsole::~cVirtualConsole() {

  Close();

  free( _title );
}



bool cVirtualConsole::IsOpen() {

  return _isOpen;
}



// closeall() -- close all FDs >= a specified value

void closeall(int fd) {

    int fdlimit = sysconf(_SC_OPEN_MAX);

    while (fd < fdlimit)
      close(fd++);
}



bool cVirtualConsole::Open( const char* command, char* const argv[] ) {

  // Let's watch if the slave is already running
  if ( ! _isOpen ) {

    CONSOLE_DEBUG( printf("opening master\n") );

    if ( ( _master = open("/dev/ptmx", O_RDWR | O_NONBLOCK) ) < 0 ) {  // open a master
      esyslog("console: could not open master pty for command %s", command);
      return false;
    }

    CONSOLE_DEBUG( fprintf( stderr, "master opened %i", _master ) );

    grantpt(_master);                           // change permission of slave
    unlockpt(_master);                          // unlock slave
    char* slavename = ptsname(_master);         // get name of slave

    CONSOLE_DEBUG( fprintf( stderr, "name of slave device is %s\n", slavename) );


    int pid = fork();
    if (pid < 0) {
      close( _master );
      _master = 0;
      esyslog("console: fork failed");
      return false;
    }


    if (pid == 0) {

      CONSOLE_DEBUG( fprintf(stderr, "slave: reached\n") );

      // We are in the child process
      closeall( 0 );



      // We need to make this process a session group leader, because
      // it is on a new PTY, and things like job control simply will
      // not work correctly unless there is a session group leader
      // and process group leader (which a session group leader
      // automatically is). This also disassociates us from our old
      // controlling tty. 

      if (setsid() < 0) {
        esyslog("console: could not set session leader for %s", slavename);
      }

      CONSOLE_DEBUG( fprintf( stderr, "slave: setsid ok\n") );


      int slave = open(slavename, O_RDWR);       // open slave
      if ( slave < 0 ) {
        esyslog("console: could not open slave pty %s", slavename);
        exit(1);
      }

      CONSOLE_DEBUG( fprintf( stderr, "slave: pts id is %i\n", slave ) );

      ioctl(slave, I_PUSH, "ptem");          // push ptem
      ioctl(slave, I_PUSH, "ldterm");        // push ldterm


      // Tie us to our new controlling tty.
      if (ioctl(slave, TIOCSCTTY, NULL)) {
        esyslog("console: could not set new controlling tty for %s", slavename);
      }

      CONSOLE_DEBUG( fprintf( stderr, "slave: ioctl ok\n") );

      // make slave pty be standard in, out, and error
      dup2(slave, STDIN_FILENO);
      dup2(slave, STDOUT_FILENO);
      dup2(slave, STDERR_FILENO);

      // at this point the slave pty should be standard input
      if (slave > 2) {
        close(slave);
      }

      // Tell the executing program which protocol we are using.
      putenv( "TERM=linux" );

      //printf("Message from slave :-)\n"); // Should be displayed on virtual console

      // now start the login
      execv( command, argv );

      // exec has failed
      _exit(1);
    }

    // save the child id
    _childPid = pid;
    _isOpen = true;


    // With this, the terminal emulation can respond to requests from the console.
    _screen.setOutputFileDescriptor( _master );

    CONSOLE_DEBUG( fprintf( stderr, "master: reached\n") );
    isyslog("console: new child started (%s, pid=%d, pts=%d)", _title, pid, _master);

    // We are in the master process
  }

  return true;
}



#define CONSOLE_USE_TIME_RESOLUTION 100

bool cVirtualConsole::ConsoleWaitPid( volatile int& pid, int timeoutMs ) {

  for ( int i = timeoutMs / CONSOLE_USE_TIME_RESOLUTION; i > 0; --i ) {

    if ( waitpid( pid, NULL, WNOHANG ) == pid )
      return true;

    HandleOutput();

    usleep( CONSOLE_USE_TIME_RESOLUTION * 1000 );
  }

  // timeout
  return (pid < 0);
}



bool cVirtualConsole::Close() {

  // give the process the ability to quit
  if ( _isOpen ) {

    //isyslog("console: sending SIGTERM to child (pid=%d)", _childPid);
    kill(_childPid, SIGTERM);

    if ( ! ConsoleWaitPid( _childPid, 500 ) ) {

      isyslog("console: killing not interuptable child (pid=%d)", _childPid);
      kill(_childPid, SIGKILL);

      if ( ! ConsoleWaitPid( _childPid, 500 ) )
        return false;
    }

    return true;
  }
  return false;
}



void cVirtualConsole::HasClosed( bool force ) {

  _isOpen = false;

  if ( force ) {

    // Ok, the child has terminated.
    // Try to read all pending output.
    if ( _master >= 0 )
      HandleOutput();

    if (_childPid >= 0) {

      // avoid zombies
      waitpid( _childPid, NULL, WNOHANG );

      CONSOLE_DEBUG( fprintf(stderr, "child terminated pid=%i\n", _childPid) );
      _childPid = -1;
    }


    if ( _master >= 0 ) {
      if (close( _master ) < 0)
        esyslog("console: could not close pts (pid=%d, pts=%d)", _childPid, _master);
      _master = -1;
    }
  }
}



void cVirtualConsole::setTerminalSize( int charW, int charH, int pixelW, int pixelH ) {

  _screen.setSize( charW, charH );

  if ( _isOpen ) {

    winsize ws;
    ws.ws_col = charW;
    ws.ws_row = charH;
    ws.ws_xpixel = pixelW;
    ws.ws_ypixel = pixelH;

    // Try to set window size; failure isn't critical
    if (ioctl( _master, TIOCSWINSZ, &ws) < 0)
      isyslog("console: could not set window size (pid=%d, pts=%d)", _childPid, _master);

//fprintf( stderr, "Terminal Resized: %i, %i\n", charW, charH );
  }
}



void cVirtualConsole::Write( const unsigned char* data, int len ) {

  if ( _isOpen ) {
    int i = write( _master, (char*)data, len );
    if ( i < 0 && errno != EINTR )
      HasClosed();
  }
}



bool cVirtualConsole::HandleOutput() {

  if ( _master >= 0 ) {

    int i = 0;

    for (;;) {

      i = read( _master, _buf, INPUT_BUFSIZE );
      if (i > 0) {

        _buf[ i ] = 0;                     // Terminate string
        _screen.Write( _buf );             // Print on screen

      } else if ( i==0 || (i<0 && errno!=EAGAIN && errno!=EINTR) ) {

        // Slave has terminated, so free the pty ...
        HasClosed();

        // ... and signal the user interface (with an empty string)
        _screen.Write( (unsigned char*)"" );

        // Brings the consoles to remove me from its polling list
        return false;
      }

      if ( i < INPUT_BUFSIZE )
        break;


      // If the buffer is full then do a new cycle to ensure
      // that all available data were read.
    }
  }

  return true;
}






// ----- cConsoles --------------------------------------------------------------------------



cConsoles::cConsoles()
: _inputActive( 0 ),
  _terminate( false )
{
  // The output handler thread should also react on this signal ...
  _wait.Add( &_consolesChanged );
}



cConsoles::~cConsoles() {

  // tell the thread to exit
  _terminate = true;
  _consolesChanged.Signal();

  if (_inputActive > 0) {
    _inputActive = 1;
    deactivateInputForTerminal();
  }

  // wait until thread has terminated
  Cancel( 500 );
  dsyslog("console: engine destructed");
}



bool cConsoles::Start() {

  return cThread::Start();
}



void cConsoles::activateInputForTerminal() {

  if (_inputActive++ == 0) {

    // prepare STDIN for Console
    termios t;
    tcgetattr(STDIN_FILENO, &t);

    // save settings to reset on exit
    _oldTerminalSettings = t;

    // change the settings for virtual console mode
    t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE | ECHOK | ECHOKE | ECHONL | ECHOPRT );
    t.c_iflag |= IGNBRK;
    t.c_iflag &= ~(ICRNL | INLCR | ISTRIP | BRKINT);

    t.c_cflag &= ~( CSIZE );
    t.c_cflag |= ( CS8  );

    t.c_cc[VMIN] = 0;
    t.c_cc[VTIME] = 0;
    tcsetattr(STDIN_FILENO, TCSANOW, &t);

    Flush(200);
  }
}



void cConsoles::deactivateInputForTerminal() {

  if (--_inputActive == 0) {

    // resetting console
    tcsetattr(STDIN_FILENO, TCSANOW, &_oldTerminalSettings);

    Flush(200);
  }
}



void cConsoles::WantAllRefreshEvents( bool WantIt ) {

  LOCK_THREAD;

  for ( int i = _consoles.Count() - 1; i >= 0; --i )
    _consoles.Get( i )->getScreen().WantRefreshEvent( WantIt );
}



void cConsoles::Flush(int WaitMs) {

  for (;;) {

    cPoller poll( STDIN_FILENO );
    if ( poll.Poll( WaitMs ) ) {

      char buf[ 20 ];
      read(STDIN_FILENO, buf, sizeof(buf));
    } else
      break;
  }
}



int cConsoles::CreateConsole() {

  char* const args[] = { PROG_FOR_CONSOLE, NULL };

  cVirtualConsole* p = new cVirtualConsole( tr("Console"), PROG_FOR_CONSOLE, args );

  if (p) {

    LOCK_THREAD;

    _consoles.Add(p);
    _wait.Add( p );
    _consolesChanged.Signal();
    return _consoles.Count()-1;
  }

  return -1;
}



int cConsoles::CreateCommand(const char* title, const char* command) {

  char* const args[] = { "sh", "-c", (char*)command, NULL };

  cVirtualConsole* p = new cVirtualConsole( title, "/bin/sh", args );

  if (p) {

    LOCK_THREAD;

    _consoles.Add(p);
    _wait.Add( p );
    _consolesChanged.Signal();
    return _consoles.Count()-1;
  }

  return -1;
}



void cConsoles::Remove(cVirtualConsole* pObject) {

  LOCK_THREAD;

  _wait.Remove( pObject );
  _consoles.Del(pObject);
  _consolesChanged.Signal();
}



// Return true if a console is open
bool cConsoles::Active() {

  cThreadLock l( gl_pConsoles );

  for ( int i = gl_pConsoles->getCount() - 1; i >= 0; --i ) {
    if ( gl_pConsoles->getItem( i ).IsOpen() )
      return true;
  }

  // no console is open
  return false;
}



void cConsoles::Action() {

  isyslog("console: output handler thread startet (pid=%d)", getpid());

  for(;;) {

    if ( _wait.Wait( -1 ) ) {  // wait infinite


      // This is to awaik the loop if the list has
      // changed and new consoles are to be watched
      // or old consoles are no more to be watched.

      if ( _consolesChanged.IsSignalled() ) {
        // Have to reset the signal manually
        _consolesChanged.Reset();
      }

      if ( _terminate )
        break;

      LOCK_THREAD;
    
      for ( int i = _consoles.Count() - 1; i >= 0; --i ) {

        cVirtualConsole* pConsole = _consoles.Get( i );

        if ( _wait.IsSignalled( pConsole ) )
          if ( ! pConsole->HandleOutput() )
            _wait.Remove( pConsole );

        // make possible a fast shutdown
        if ( _terminate )
          break;
      }
    }

    if ( _terminate )
      break;
  }

  isyslog("console: output handler thread stopped");
}



Generated by  Doxygen 1.6.0   Back to index