// NOTE: disable a couple of defines if you get "IndexError: list index out of range" //#define DEBUG // Indicate data on the LEDs as a counter //#define TESTS // Run some test procedures instead of actual terminal, defined in helpers.h //#define SLOW // Runs tests slower #define COLORS // Enable terminal colors (costs extra memory) //#define LEDS // Allow for usage of Gigatron LEDs within the application (costs extra memory) #define FAST_START // Quick startup by disabling the screen during initialization #define ANSI_BLOCK_CHARS // Allow ANSI block characters (costs extra memory) //#define TERMINAL_MODES // Enable terminal modes (costs extra memory) #define TINY_FONT // Enables small font (costs extra memory) // Successful combinations (trial and error), leave out one of the following: // COLORS // LEDS and ANSI_BLOCK_CHARS // LEDS and TERMINAL_MODES // ANSI_BLOCK_CHARS and TERMINAL_MODES // We can only define the port after including the helper file #include "vtactionstates.h" #include "helpers.h" // Enable SPI hardware by assigning Slave Select pin (SS0 to SS3). Note that SS0 is reserved for an SD card. // Comment out to do local loopback #define IOPORT SS1 byte oldchar; byte intermediate = 0; int params[10] = {0}; byte paramCount = 0; int savedCursor = 0; char response[10] = {0}; byte responseIndex = 0; #ifdef TESTS void main(void) { test(); while( 1 ); } #else // Forward declaration void vt_parse( byte _byte ); byte SPIExchangeKey( byte _key ); void main(void) { byte spiData; ScreenPos = (int)screenMemory; #ifndef TINY_FONT ScreenPos |= 0x02; #endif if ( romType < romTypeValue_DEVROM ) { //puts( "Terminal is only supported\non ROMv4 or higher." ); puts( "Terminal is only supported\non xopr's DEVROM." ); while(1); } #ifdef FAST_START // Disable video to increase speed sysFn = SYS_SetMode_v2_80; vAC = 1975; __syscall(230); // 270-80/2 #else // Set lower video mode to increase speed sysFn = SYS_SetMode_v2_80; vAC = 3; __syscall(230); // 270-80/2 #endif EraseInDisplay( 2, ScreenPos ); drawCursor( 1 ); setCursor( 0, 0 ); #ifdef IOPORT // Enable SPI port sysFn = SYS_ExpanderControl_v4_40; vAC = ~IOPORT & SPI_MASK | BANK0; __syscall(250); // 270-40/2 // To disable //vAC = IOPORT & SPI_MASK | BANK0; #endif #ifdef LEDS // Disable LED sequencer and set 4th led ledState = 1; xoutMask = 8; #endif #ifdef FAST_START // Set lower video mode to increase speed sysFn = SYS_SetMode_v2_80; vAC = 3; __syscall(230); // 270-80/2 #endif while ( 1 ) { if ( response[ responseIndex ] ) { spiData = response[ responseIndex ]; responseIndex++; // End of string? if ( !response[ responseIndex ] ) { response[ 0 ] = 0; responseIndex = 0; } #ifndef IOPORT // Color output marking it dummy Color = Red; PutChar( spiData ); Color = White; #endif } else { spiData = serialRaw; #ifndef IOPORT if ( spiData == DEL ) spiData = BS; #endif if ( spiData == oldchar ) spiData = 0; else oldchar = spiData; #if defined(DEBUG) && defined(LEDS) // Blinky led on input to detect freezes if ( spiData && spiData != 0xff ) xoutMask++; #endif #ifdef IOPORT // Translate buttons to escape code switch ( spiData ) { case buttonUp: response[ 0 ] = 0x1B; response[ 1 ] = '['; response[ 2 ] = 'A'; response[ 3 ] = 0;break; case buttonDown: response[ 0 ] = 0x1B; response[ 1 ] = '['; response[ 2 ] = 'B'; response[ 3 ] = 0;break; case buttonRight: response[ 0 ] = 0x1B; response[ 1 ] = '['; response[ 2 ] = 'C'; response[ 3 ] = 0;break; case buttonLeft: response[ 0 ] = 0x1B; response[ 1 ] = '['; response[ 2 ] = 'D'; response[ 3 ] = 0;break; } #else // Translate buttons to local action switch ( spiData ) { case buttonUp: cursorUp( 1, 0 ); break; case buttonDown: cursorDown( 1, 0 ); break; case buttonRight: cursorRight( 1 ); break; case buttonLeft: cursorLeft( 1 ); break; } vt_parse( spiData ); #endif } #ifdef IOPORT // Send key to terminal server spiData = SPIExchangeKey( spiData ); vt_parse( spiData ); #endif } } void vt_action_regular( byte _byte ) { if ( _byte >= SP && _byte < DEL ) { // Print character PutChar( _byte ); drawCursor( 1 ); } #ifdef ANSI_BLOCK_CHARS else if ( _byte == 176 ) { // Block light PutGlyph( 0x280a ); drawCursor( 1 ); } else if ( _byte == 177 ) { // Block medium PutGlyph( 0x5555 ); drawCursor( 1 ); } else if ( _byte == 178 ) { // Block dark PutGlyph( 0x7d5f ); drawCursor( 1 ); } else if ( _byte == 219 ) { // Block PutChar( 127 ); drawCursor( 1 ); } else if ( _byte == 220 ) { // Block bottom PutGlyph( 0x6318 ); drawCursor( 1 ); } else if ( _byte == 221 ) { // Block left PutGlyph( 0x03ff ); drawCursor( 1 ); } else if ( _byte == 222 ) { // Block right PutGlyph( 0xec00 ); drawCursor( 1 ); } else if ( _byte == 223 ) { // Block top PutGlyph( 0x1ce7 ); drawCursor( 1 ); } #endif } void vt_action_control_sequence_execute( byte _byte ) { switch ( _byte ) { case 'A': // ESC [ Pn A CUU -- Cursor Up -- Host to VT100 and VT100 to Host if ( params[0] == 0 ) params[0] = 1; cursorUp( params[0], 0 ); break; case 'B': // ESC [ Pn B CUD -- Cursor Down -- Host to VT100 and VT100 to Host if ( params[0] == 0 ) params[0] = 1; cursorDown( params[0], 0 ); break; case 'C': // ESC [ Pn C CUF -- Cursor Forward -- Host to VT100 and VT100 to Host if ( params[0] == 0 ) params[0] = 1; cursorRight( params[0] ); break; case 'D': // ESC [ Pn D CUB -- Cursor Backward -- Host to VT100 and VT100 to Host (n positions, default 1), stop at margin if ( params[0] == 0 ) params[0] = 1; cursorLeft( params[0] ); break; case 'H': // ESC [ Pn ; Pn H CUP -- Cursor Position (line, column) case 'f': // ESC [ Pn ; Pn f HVP -- Horizontal and Vertical Position (line, column) // The numbering of lines depends on the state of the Origin Mode (DECOM). if ( params[0] ) params[0]--; if ( paramCount < 2 ) params[1] = 0; if ( params[1] ) params[1]--; setCursor( params[0], params[1] ); break; case 'J': // ESC [ Ps J ED -- Erase In Display ( // 0 Erase from the active position to the end of the screen, inclusive (default) // 1 Erase from start of the screen to the active position, inclusive // 2 Erase all of the display -- all lines are erased, changed to single-width, and the cursor does not move. // Erase stored cursor character drawCursor( 0 ); EraseInDisplay( params[0], ScreenPos ); // Restore cursor drawCursor( 1 ); break; case 'K': // ESC [ Ps K EL -- Erase In Line // 0 Erase from the active position to the end of the line, inclusive (default) // 1 Erase from the start of the screen to the active position, inclusive // 2 Erase all of the line, inclusive // Erase stored cursor character drawCursor( 0 ); EraseInLine( params[0], ScreenPos ); // Restore cursor drawCursor( 1 ); break; case 'c': // ESC [ Pn c DA -- Device Attributes (empty/0 from host, response from terminal:) // No options ESC [?1;0c // Processor option (STP) ESC [?1;1c // Advanced video option (AVO) ESC [?1;2c (10 additional lines of 132 column display,bold...) // AVO and STP ESC [?1;3c // Graphics option (GPO) ESC [?1;4c // GPO and STP ESC [?1;5c // GPO and AVO ESC [?1;6c // GPO, STP and AVO ESC [?1;7c // Identify: set response to send back snprintf(response, sizeof response, "\x1B[?1;0c"); //puts_ex( response ); break; case 'g': // ESC [ Ps g TBC -- Tabulation Clear (0=current, 3=all) break; #ifdef TERMINAL_MODES case 'h': // ESC [ Ps ; . . . ; Ps h SM -- Set Mode (reset mode counterpart) while ( paramCount-- ) { if ( !params[ paramCount ] ) continue; if ( params[ paramCount ] == 20 ) params[ paramCount ] = 0; if ( params[ paramCount ] > 9 ) continue; terminalModes |= (1 << params[ paramCount ]); } break; case 'l': // ESC [ Ps ; Ps ; . . . ; Ps l RM -- Reset Mode (set mode counterpart) while ( paramCount-- ) { if ( !params[ paramCount ] ) continue; if ( params[ paramCount ] == 20 ) params[ paramCount ] = 0; if ( params[ paramCount ] > 9 ) continue; terminalModes &= ~(1 << params[ paramCount ]); } break; #endif #ifdef COLORS case 'm': // ESC [ Ps ; . . . ; Ps m SGR -- Select Graphic Rendition (attributes) // Restore character (attribute under cursor will change) drawChar( cursor ); while ( paramCount-- ) { switch ( params[ paramCount ] ) { case 0: Color = White; BgColor = Blue; break; case 30: Color = Black; break; case 31: Color = Red; break; case 32: Color = Green; break; case 33: Color = Yellow; break; case 34: Color = Blue; break; case 35: Color = Magenta; break; case 36: Color = Cyan; break; case 37: Color = LightGray; break; case 40: BgColor = Black; break; case 41: BgColor = Red; break; case 42: BgColor = Green; break; case 43: BgColor = Yellow; break; case 44: BgColor = Blue; break; case 45: BgColor = Magenta; break; case 46: BgColor = Cyan; break; case 47: BgColor = LightGray; break; case 90: Color = DarkGray; break; case 91: Color = LightRed; break; case 92: Color = LightGreen; break; case 93: Color = LightYellow; break; case 94: Color = LightBlue; break; case 95: Color = LightMagenta; break; case 96: Color = LightCyan; break; case 97: Color = White; break; case 100: BgColor = DarkGray; break; case 101: BgColor = LightRed; break; case 102: BgColor = LightGreen; break; case 103: BgColor = LightYellow; break; case 104: BgColor = LightBlue; break; case 105: BgColor = LightMagenta; break; case 106: BgColor = LightCyan; break; case 107: BgColor = White; break; } } // Restore cursor drawCursor( 1 ); break; #endif case 'n': // ESC [ Ps n DSR -- Device Status Report switch ( params[ 0 ] ) { case 5: // 5 Command from host -- Please report status (using a DSR control sequence) // 0 Response from VT100 -- Ready, No malfunctions detected (default) // 3 Response from VT100 -- Malfunction -- retry // Report ready: set response to send back snprintf( response, sizeof response, "\x1B[0n" ); break; case 6: // 6 Command from host -- Please report active position (using a CPR control sequence) response // ESC [ Pn ; Pn R: CPR -- Cursor Position Report -- VT100 to Host (line,column) // Report ready: set response to send back // Abuse intermediate variable intermediate = (ScreenPos >> 8); if ( videoTable[0] > intermediate ) intermediate = intermediate + 120 - videoTable[0]; else intermediate -= videoTable[0]; intermediate /= ( CHAR_HEIGHT + CHAR_LEADING ); snprintf( response, sizeof response, "\x1B[%u,%uR", intermediate + 1, ( ScreenPos & 0xff ) / CHAR_WIDTH + 1 ); break; } break; #ifdef LEDS case 'q': // ESC [ Ps q DECLL -- Load LEDS (Linux supports L1 as scroll lock, L2 NumLock, L3 as Caps lock , ) // ESC [ Ps;Ps;...Ps q // 0 or None All LEDs Off // 1 L1 On // 2 L2 On // 3 L3 On // 4 L4 On // Set LEDs while ( paramCount-- ) { if ( !params[ paramCount ] ) { xoutMask = 0; } else if ( params[ paramCount ] <= 4 ) { // Turn number into bit mask params[ paramCount ]--; xoutMask |= ( 1 << params[ paramCount ] ); } } break; #endif /* case 'r': // ESC [ Pn; Pn r DECSTBM -- Set Top and Bottom Margins (startline, endline scroll) break; case 'x': // ESC [ ; ; ; ; ; ; x DECREPTPARM -- Report Terminal Parameters break; case 'y': // ESC [ 2 ; Ps y DECTST -- Invoke Confidence Test // Test Weight // 0: reset only // 1: Power up self-test (ROM check sum, RAM, NVR keyboard and AVO if installed) // 2: Data Loop Back (loop back connector required) // 4: EIA modem control test (loop back connector required) // 8: Repeat Selected Test(s) indefinitely (until failure or power off) // 0 Attributes off // 1 Bold or increased intensity // 4 Underscore // 5 Blink // 7 Negative (reverse) image break; */ } } void vt_action_control_sequence( byte _byte ) { if ( _byte >= 0x30 && _byte <= 0x39 ) { // Pn: Numbers: param params[paramCount] = params[paramCount] * 10 + _byte - 0x30; } else if ( _byte == ';' ) { // Ps: Next param paramCount++; params[paramCount] = 0; } else if ( _byte >= 0x40 && _byte <= 0x7E ) { // Offset param count (0->1) paramCount++; // Handle Control sequence vt_action_control_sequence_execute( _byte ); paramCount = 0; params[0] = 0; mode = VTPARSE_MODE_REGULAR; } else if ( _byte == '?' ) { // Note that in some cases, ESC[? is sent and therefore allowed } } void vt_action_escape_sequence( byte _byte ) { switch ( _byte ) { case '0': // SCS -- Select Character Set // G0 ( or G1 ) Special graphics break; case '1': // SCS -- Select Character Set // G0 ( or G1 ) Alternate ROM standard break; case '2': // SCS -- Select Character Set // G0 ( or G1 ) Alternate ROM special graphics break; case '3': case '4': // # DECDHL -- Double Height Line // top/bottom break; case '5': // # DECSWL -- Single-width Line break; case '6': // # DECDWL -- Double-Width Line break; case '7': // DECSC -- Save Cursor // This sequence causes the cursor position, graphic rendition (attributes), and character set to be saved. (See DECRC). savedCursor = ScreenPos; break; case '8': // # DECALN -- Screen Alignment Display // Fill screen with 'E' if ( intermediate != '#' ) { // DECRC -- Restore Cursor if ( savedCursor != ScreenPos ) { // Restore character behind cursor drawChar( cursor ); ScreenPos = savedCursor; // Draw cursor drawCursor( 1 ); } } break; case 'A': // SCS -- Select Character Set // G0 ( or G1 ) UK // VT52: Cursor up (no scroll) break; case 'B': // SCS -- Select Character Set // G0 ( or G1 ) ASCII // VT52: Cursor down (no scroll) break; case 'C': // VT52: Cursor right (no scroll/newline) break; case 'D': // IND -- Index (cursor down, allow scroll) cursorDown( 1, 0 ); // VT52: Cursor left (no scroll/newline) break; case 'E': // NEL -- Next Line (CRLF, allow scroll) Newline(); drawCursor( 1 ); break; case 'F': // VT52: Enter Graphics Mode break; case 'G': // VT52: Select ASCII character set break; case 'H': // HTS -- Horizontal Tabulation Set // VT52: Move the cursor to the home position. break; case 'I': // VT52: Reverse Line Feed break; case 'J': // VT52: Erase to End of Screen break; case 'K': // VT52: Erase to End of Line break; case 'M': // RI -- Reverse Index (cursor up, allow scoll) cursorUp( 1, 1 ); break; case 'Y': // VT52: Direct Cursor Address (needs line, column) //mode = VTPARSE_MODE_ESCAPE_CURSOR_LINE; break; case 'Z': // DECID -- Identify Terminal, deprecated->ESC[?1;0c // set response to send back snprintf(response, sizeof response, "\x1B[?1;0c"); //puts_ex( response ); // VT52: Identify, reply with ESC / Z break; #ifdef TERMINAL_MODES case '=': // DECKPAM -- Keypad Application Mode terminalModes |= DECKPAM; // VT52: Enter Alternate Keypad Mode break; case '>': // DECKPNM -- Keypad Numeric Mode terminalModes &= ~DECKPAM; #endif // VT52: Exit Alternate Keypad Mode break; case '<': // VT52: Enter ANSI Mode (ignore VT52 escape codes) break; } // Note: the previous switch statement is at its max apparently switch ( _byte ) { case 'c': // RIS -- Reset To Initial State break; case '[': // CSI -- Control Sequence Introducer mode = VTPARSE_MODE_CONTROL_SEQUENCE; break; } // Reset mode if it was not altered if ( mode == VTPARSE_MODE_ESCAPE_INTERMEDIATE ) mode = VTPARSE_MODE_REGULAR; intermediate = 0; } void vt_action_escape_intermediate( byte _byte ) { if ( _byte >= 0x30 && _byte <= 0x7E ) { // Handle escape (might turn into control sequence) vt_action_escape_sequence( _byte ); } else if ( _byte < 0x20 || _byte >= 0x2F ) { // TODO: Error } // Store intermediate (assume only 1 character) if ( !intermediate ) intermediate = _byte; } byte vt_parse_control_char( byte _byte ) { /* if ( _byte != NUL ) { snprintf(response, sizeof response, "%d", _byte ); puts_ex( response ); } */ // Check if we need to parse a control char in the stream if ( _byte == NUL ) { // Ignored on input (not stored in input buffer; see full duplex protocol). } else if ( _byte == ENQ ) { // Transmit answerback message. } else if ( _byte == BEL ) { // bell sound } else if ( _byte == BS ) { // Backspace // TODO: rollover if ((ScreenPos & 0xff) >= CHAR_WIDTH) { // Remove cursor drawChar( cursor ); ScreenPos -= CHAR_WIDTH; // draw cursor drawCursor( 0 ); } } else if ( _byte == '`'/*HT*/ ) { // move to next tabstop (align on 8*CHAR_WIDTH pixels) drawChar( cursor ); PutChar_ex( HT ); // Restore character behind cursor drawCursor( 1 ); } else if ( _byte >= LF && _byte <= FF ) { // Line feed: depends on mode // Note: incoming keyboard enter translates to line feed #ifndef IOPORT drawChar( cursor ); Newline(); drawCursor( 1 ); #endif } else if ( _byte == CR ) { // (Carriage) Return Newline(); drawCursor( 1 ); } else if ( _byte == SO ) { // TODO: SO 016 0E Invoke G1 character set, as designated by SCS control sequence. } else if ( _byte == SI ) { // TODO: SI 017 0F Select G0 character set, as selected by ESC ( sequence. } else if ( _byte == XON ) { // TODO: XON 021 11 Causes terminal to resume transmission. } else if ( _byte == XOFF ) { // TODO: XOFF 023 13 Causes terminal to stop transmitted all codes except XOFF and XON. } else if ( _byte == ESC || _byte == CAN || _byte == SUB ) { // Clear params/intermediates, abort or restart escape sequence intermediate = 0; paramCount = 0; params[0] = 0; // Restart or Abort? if ( _byte == ESC ) mode = VTPARSE_MODE_ESCAPE_INTERMEDIATE; else mode = VTPARSE_MODE_REGULAR; } else if ( _byte == DEL ) { // Ignored on input (not stored in input buffer). } else { // No control character return 0; } // Control character parsed return 1; } void vt_parse( byte _byte ) { // Check if we got a control character (which will be parsed) if ( vt_parse_control_char( _byte ) ) return; switch ( mode ) { case VTPARSE_MODE_REGULAR: // (control+printable characters) vt_action_regular( _byte ); break; case VTPARSE_MODE_ESCAPE_INTERMEDIATE: // 0x20-0x2F (attribute chars) vt_action_escape_intermediate( _byte ); break; case VTPARSE_MODE_CONTROL_SEQUENCE: // 0x40-x07E (characters) vt_action_control_sequence( _byte ); break; } } byte SPIExchangeKey( byte _key ) { *(int*)(sysArgs) = (int)&_key; // Start address *(int*)(sysArgs+2) = (int)(&_key) + 1; // End address sysFn = SYS_SpiExchangeBytes_v4_134; __syscall(203); // 270-134/2 return _key; } #endif