24 #include "TelnetDefs.h"
101 #define STATE_INIT 0 // Initial state.
102 #define STATE_CR 1 // Last character was CR, eat following LF.
103 #define STATE_ESC 2 // Last character was ESC.
104 #define STATE_MATCH 3 // Matching an escape sequence.
105 #define STATE_UTF8 4 // Recognizing a UTF-8 sequence.
106 #define STATE_IAC 5 // Recognizing telnet command after IAC (0xFF).
107 #define STATE_WILL 6 // Waiting for option code for WILL command.
108 #define STATE_WONT 7 // Waiting for option code for WONT command.
109 #define STATE_DO 8 // Waiting for option code for DO command.
110 #define STATE_DONT 9 // Waiting for option code for DONT command.
111 #define STATE_SB 10 // Option sub-negotiation.
112 #define STATE_SB_IAC 11 // Option sub-negotiation, byte after IAC.
116 #define ESC_TIMEOUT_MS 40
121 #define SEQ_TIMEOUT_MS 200
230 return _stream ? _stream->available() : 0;
242 return _stream ? _stream->peek() : -1;
268 return _stream ? _stream->read() : -1;
288 return _stream ? _stream->write(c) : 0;
301 return _stream ? _stream->write(buffer, size) : 0;
319 if (!_stream || !str)
322 while ((ch = pgm_read_byte((
const uint8_t *)str)) != 0) {
324 if (posn ==
sizeof(buffer)) {
325 _stream->write(buffer, posn);
331 _stream->write(buffer, posn);
341 static bool escapeSequenceStart(
int ch)
343 if (ch ==
'[' || ch ==
'?')
345 else if (ch >=
'A' && ch <=
'Z')
397 if (state == STATE_ESC) {
398 ch = _stream->peek();
403 if ((millis() - timer) >= ESC_TIMEOUT_MS) {
410 }
else if (!escapeSequenceStart(ch)) {
418 ch = _stream->read();
422 ch = _stream->read();
425 if (state == STATE_MATCH && (millis() - timer) >= SEQ_TIMEOUT_MS) {
442 }
else if (ch == 0x00 && mod ==
Telnet) {
452 if (ch >= 0x20 && ch <= 0x7E) {
457 }
else if (ch == 0x1B) {
461 }
else if (ch == 0x0D) {
466 }
else if (ch == 0x0A) {
470 }
else if (ch == 0x08 || ch == 0x7F) {
474 return KEY_BACKSPACE;
475 }
else if (ch == 0x09) {
480 }
else if (ch < 0x80) {
485 }
else if (ch >= 0xC1 && ch <= 0xDF) {
490 }
else if (ch >= 0xE1 && ch <= 0xEF) {
495 }
else if (ch >= 0xF1 && ch <= 0xF7) {
500 }
else if (ch == 0xFF && mod ==
Telnet) {
522 ch = matchEscape(ch);
526 }
else if (ch == -2) {
529 }
else if (ch < 0x80) {
544 if ((ch & 0xC0) == 0x80) {
547 ucode = (((long)offset) << 6) | (ch & 0x3F);
549 if (ucode > 0x10FFFFL)
555 offset = (offset << 6) | (ch & 0x3F);
566 case TelnetDefs::EndOfFile:
572 case TelnetDefs::EndOfRecord:
578 case TelnetDefs::Interrupt:
584 case TelnetDefs::EraseChar:
588 return KEY_BACKSPACE;
590 case TelnetDefs::EraseLine:
596 case TelnetDefs::SubStart:
602 case TelnetDefs::WILL:
607 case TelnetDefs::WONT:
617 case TelnetDefs::DONT:
622 case TelnetDefs::IAC:
639 if (ch == TelnetDefs::WindowSize ||
640 ch == TelnetDefs::RemoteFlowControl) {
642 telnetCommand(TelnetDefs::DO, ch);
645 telnetCommand(TelnetDefs::DONT, ch);
647 if (!(flags & 0x01)) {
651 telnetCommand(TelnetDefs::WILL, TelnetDefs::Echo);
668 if (ch == TelnetDefs::Echo) {
671 }
else if (ch == TelnetDefs::SuppressGoAhead) {
673 telnetCommand(TelnetDefs::WILL, ch);
676 telnetCommand(TelnetDefs::WONT, ch);
684 if (ch == TelnetDefs::IAC) {
686 state = STATE_SB_IAC;
689 if (utf8len <
sizeof(sb))
695 if (ch == TelnetDefs::IAC) {
697 if (utf8len <
sizeof(sb))
698 sb[utf8len++] = 0xFF;
701 }
else if (ch == TelnetDefs::SubEnd) {
703 if (utf8len >= 5 && sb[0] == TelnetDefs::WindowSize) {
704 int width = (((int)(sb[1])) << 8) | sb[2];
705 int height = (((int)(sb[3])) << 8) | sb[4];
806 else if (columns > 10000)
810 else if (rows > 10000)
812 if (ncols != columns || nrows != rows) {
826 static char const escape[] PROGMEM =
"\033[H\033[J";
835 static char const escape[] PROGMEM =
"\033[K";
840 static void writeNumber(uint8_t *buf, uint8_t &posn,
int value)
843 bool haveDigits =
false;
844 while (divisor >= 1) {
845 int digit = value / divisor;
846 if (digit || haveDigits) {
847 buf[posn++] =
'0' + digit;
884 buffer[posn++] = 0x1B;
885 buffer[posn++] =
'[';
886 writeNumber(buffer, posn, y + 1);
887 buffer[posn++] =
';';
888 writeNumber(buffer, posn, x + 1);
889 buffer[posn++] =
'H';
890 _stream->write(buffer, posn);
900 static char const escape[] PROGMEM =
"\033[D";
911 static char const escape[] PROGMEM =
"\033[C";
922 static char const escape[] PROGMEM =
"\033[A";
933 static char const escape[] PROGMEM =
"\033[B";
951 static char const escape[] PROGMEM =
"\b \b";
962 static char const escape[] PROGMEM =
"\033[L";
973 static char const escape[] PROGMEM =
"\033[@";
984 static char const escape[] PROGMEM =
"\033[M";
995 static char const escape[] PROGMEM =
"\033[P";
1006 static char const escape[] PROGMEM =
"\033[S";
1017 static char const escape[] PROGMEM =
"\033[T";
1028 static char const escape[] PROGMEM =
"\033[0m";
1039 static char const escape[] PROGMEM =
"\033[1m";
1048 static char const escape[] PROGMEM =
"\033[4m";
1057 static char const escape[] PROGMEM =
"\033[5m";
1066 static char const escape[] PROGMEM =
"\033[7m";
1176 uint8_t code = (fg & 0x07);
1177 uint8_t
bold = (fg & 0x08) ? 1 : 0;
1182 buffer[posn++] = 0x1B;
1183 buffer[posn++] =
'[';
1184 buffer[posn++] =
'0';
1185 buffer[posn++] =
';';
1186 buffer[posn++] =
'3';
1187 buffer[posn++] =
'0' + code;
1189 buffer[posn++] =
';';
1190 buffer[posn++] =
'1';
1192 buffer[posn++] =
'm';
1193 _stream->write(buffer, posn);
1208 uint8_t codefg = (fg & 0x07);
1209 uint8_t boldfg = (fg & 0x08) ? 1 : 0;
1210 uint8_t codebg = (bg & 0x07);
1215 buffer[posn++] = 0x1B;
1216 buffer[posn++] =
'[';
1217 buffer[posn++] =
'0';
1218 buffer[posn++] =
';';
1219 buffer[posn++] =
'3';
1220 buffer[posn++] =
'0' + codefg;
1222 buffer[posn++] =
';';
1223 buffer[posn++] =
'1';
1225 buffer[posn++] =
';';
1226 buffer[posn++] =
'4';
1227 buffer[posn++] =
'0' + codebg;
1228 buffer[posn++] =
'm';
1229 _stream->write(buffer, posn);
1250 static unsigned char const range3000[32] PROGMEM = {
1251 0xF1, 0xFF, 0xF3, 0x3F, 0x01, 0x00, 0x01, 0x78,
1252 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1253 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00,
1254 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88
1256 static unsigned char const rangeFE00[64] PROGMEM = {
1257 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xE1, 0xFF,
1258 0x9F, 0x01, 0x00, 0x7F, 0x0C, 0x03, 0x00, 0x00,
1259 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1260 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1261 0x10, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8,
1262 0x01, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x00,
1263 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1264 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00
1267 if (code < 0x2300) {
1269 }
else if (code >= 0x3000 && code <= 0x30FF) {
1270 c = (unsigned)(code - 0x3000);
1271 return (pgm_read_byte(range3000 + (c / 8)) & (1 << (c % 8))) != 0;
1272 }
else if (code >= 0xFE00 && code <= 0xFFFF) {
1273 c = (unsigned)(code - 0xFE00);
1274 return (pgm_read_byte(rangeFE00 + (c / 8)) & (1 << (c % 8))) != 0;
1275 }
else if (code >= 0x3400 && code <= 0x4DBF) {
1277 }
else if (code >= 0x4E00 && code <= 0x9FFF) {
1279 }
else if (code >= 0xF900 && code <= 0xFAFF) {
1281 }
else if (code >= 0x20000 && code <= 0x2FFFD) {
1283 }
else if (code >= 0x30000 && code <= 0x3FFFD) {
1285 }
else if (code == 0x2329 ||
1307 }
else if (code <= 0x7FL) {
1309 }
else if (code <= 0x07FFL) {
1311 }
else if (code >= 0xD800L && code <= 0xDFFF) {
1314 }
else if (code <= 0xFFFFL) {
1316 }
else if (code <= 0x10FFFFL) {
1339 }
else if (code <= 0x7FL) {
1340 buffer[0] = (uint8_t)code;
1342 }
else if (code <= 0x07FFL) {
1343 buffer[0] = 0xC0 | (uint8_t)(code >> 6);
1344 buffer[1] = 0x80 | (((uint8_t)code) & 0x3F);
1346 }
else if (code >= 0xD800L && code <= 0xDFFF) {
1349 }
else if (code <= 0xFFFFL) {
1350 buffer[0] = 0xE0 | (uint8_t)(code >> 12);
1351 buffer[1] = 0x80 | (((uint8_t)(code >> 6)) & 0x3F);
1352 buffer[2] = 0x80 | (((uint8_t)code) & 0x3F);
1354 }
else if (code <= 0x10FFFFL) {
1355 buffer[0] = 0xF0 | (uint8_t)(code >> 18);
1356 buffer[1] = 0x80 | (((uint8_t)(code >> 12)) & 0x3F);
1357 buffer[2] = 0x80 | (((uint8_t)(code >> 6)) & 0x3F);
1358 buffer[3] = 0x80 | (((uint8_t)code) & 0x3F);
1373 static uint8_t
const keymap[459] PROGMEM = {
1374 0xDB, 0x1A, 0x00, 0xCF, 0x57, 0x01, 0x41, 0xDA, 0x42, 0xD9, 0x43, 0xD7,
1375 0x44, 0xD8, 0xBF, 0xA2, 0x01, 0x50, 0xC2, 0x51, 0xC3, 0x52, 0xC4, 0x53,
1376 0xC5, 0x00, 0x41, 0xDA, 0x42, 0xD9, 0x43, 0xD7, 0x44, 0xD8, 0x48, 0xD2,
1377 0xB1, 0x42, 0x00, 0x46, 0xD5, 0xB4, 0xC9, 0x00, 0xB2, 0xCC, 0x00, 0xB3,
1378 0x2B, 0x01, 0xB5, 0x46, 0x01, 0xB6, 0x49, 0x01, 0xDB, 0x4C, 0x01, 0x5A,
1379 0x0B, 0x50, 0xD0, 0x47, 0xE5, 0x00, 0x7E, 0xD2, 0xB1, 0x5D, 0x00, 0xB2,
1380 0x6C, 0x00, 0xB3, 0x7B, 0x00, 0xB4, 0x88, 0x00, 0xB5, 0x95, 0x00, 0xB7,
1381 0xA2, 0x00, 0xB8, 0xAF, 0x00, 0xB9, 0xBC, 0x00, 0x00, 0x7E, 0xC2, 0xBB,
1382 0x65, 0x00, 0x5E, 0xFA, 0x00, 0xB2, 0x69, 0x00, 0x00, 0x7E, 0xF0, 0x00,
1383 0x7E, 0xC3, 0xBB, 0x74, 0x00, 0x5E, 0xFB, 0x00, 0xB2, 0x78, 0x00, 0x00,
1384 0x7E, 0xF1, 0x00, 0x7E, 0xC4, 0xBB, 0x81, 0x00, 0x00, 0xB2, 0x85, 0x00,
1385 0x00, 0x7E, 0xF2, 0x00, 0x7E, 0xC5, 0xBB, 0x8E, 0x00, 0x00, 0xB2, 0x92,
1386 0x00, 0x00, 0x7E, 0xF3, 0x00, 0x7E, 0xC6, 0xBB, 0x9B, 0x00, 0x00, 0xB2,
1387 0x9F, 0x00, 0x00, 0x7E, 0xF4, 0x00, 0x7E, 0xC7, 0xBB, 0xA8, 0x00, 0x00,
1388 0xB2, 0xAC, 0x00, 0x00, 0x7E, 0xF5, 0x00, 0x7E, 0xC8, 0xBB, 0xB5, 0x00,
1389 0x00, 0xB2, 0xB9, 0x00, 0x00, 0x7E, 0xF6, 0x00, 0x7E, 0xC9, 0xBB, 0xC2,
1390 0x00, 0x00, 0xB2, 0xC6, 0x00, 0x00, 0x7E, 0xF7, 0x00, 0x7E, 0xD5, 0x00,
1391 0x7E, 0xD1, 0xB0, 0xE7, 0x00, 0xB1, 0xF4, 0x00, 0xB3, 0x01, 0x01, 0xB4,
1392 0x10, 0x01, 0xB5, 0x1F, 0x01, 0xB6, 0x22, 0x01, 0xB8, 0x25, 0x01, 0xB9,
1393 0x28, 0x01, 0x00, 0x7E, 0xCA, 0xBB, 0xED, 0x00, 0x00, 0xB2, 0xF1, 0x00,
1394 0x00, 0x7E, 0xF8, 0x00, 0x7E, 0xCB, 0xBB, 0xFA, 0x00, 0x00, 0xB2, 0xFE,
1395 0x00, 0x00, 0x7E, 0xF9, 0x00, 0x7E, 0xCC, 0x24, 0xF8, 0xBB, 0x09, 0x01,
1396 0x00, 0xB2, 0x0D, 0x01, 0x00, 0x7E, 0xFA, 0x00, 0x7E, 0xCD, 0x24, 0xF9,
1397 0xBB, 0x18, 0x01, 0x00, 0xB2, 0x1C, 0x01, 0x00, 0x7E, 0xFB, 0x00, 0x7E,
1398 0xF0, 0x00, 0x7E, 0xF1, 0x00, 0x7E, 0xF2, 0x00, 0x7E, 0xF3, 0x00, 0x7E,
1399 0xD4, 0xB1, 0x3A, 0x01, 0xB2, 0x3D, 0x01, 0xB3, 0x40, 0x01, 0xB4, 0x43,
1400 0x01, 0x00, 0x7E, 0xF4, 0x00, 0x7E, 0xF5, 0x00, 0x7E, 0xF6, 0x00, 0x7E,
1401 0xF7, 0x00, 0x7E, 0xD3, 0x00, 0x7E, 0xD6, 0x00, 0x41, 0xC2, 0x42, 0xC3,
1402 0x43, 0xC4, 0x44, 0xC5, 0x45, 0xC6, 0x00, 0x41, 0xDA, 0x42, 0xD9, 0x43,
1403 0xD7, 0x44, 0xD8, 0x48, 0xD2, 0x46, 0xD5, 0x20, 0x20, 0x49, 0xB3, 0x4D,
1404 0xB0, 0x6A, 0x2A, 0x6B, 0x2B, 0x6C, 0x2C, 0x6D, 0x2D, 0x6E, 0x2E, 0x6F,
1405 0x2F, 0x70, 0x30, 0x71, 0x31, 0x72, 0x32, 0x73, 0x33, 0x74, 0x34, 0x75,
1406 0x35, 0x76, 0x36, 0x77, 0x37, 0x78, 0x38, 0x79, 0x39, 0x58, 0x3D, 0x50,
1407 0xC2, 0x51, 0xC3, 0x52, 0xC4, 0x53, 0xC5, 0xB2, 0x99, 0x01, 0x5A, 0x0B,
1408 0x00, 0x50, 0xF0, 0x51, 0xF1, 0x52, 0xF2, 0x53, 0xF3, 0x00, 0x20, 0x20,
1409 0x49, 0xB3, 0x4D, 0xB0, 0x6A, 0x2A, 0x6B, 0x2B, 0x6C, 0x2C, 0x6D, 0x2D,
1410 0x6E, 0x2E, 0x6F, 0x2F, 0x70, 0x30, 0x71, 0x31, 0x72, 0x32, 0x73, 0x33,
1411 0x74, 0x34, 0x75, 0x35, 0x76, 0x36, 0x77, 0x37, 0x78, 0x38, 0x79, 0x39,
1422 int Terminal::matchEscape(
int ch)
1426 kch = pgm_read_byte(keymap + offset);
1430 }
else if (kch & 0x80) {
1432 if ((kch & 0x7F) == ch) {
1434 offset = ((int)(pgm_read_byte(keymap + offset + 1))) |
1435 (((int)(pgm_read_byte(keymap + offset + 2))) << 8);
1441 if (kch == (uint8_t)ch) {
1443 return pgm_read_byte(keymap + offset + 1);
1457 void Terminal::telnetCommand(uint8_t type, uint8_t option)
1460 buf[0] = (uint8_t)TelnetDefs::IAC;
1463 _stream->write(buf, 3);
void insertChar()
Inserts a blank character at the cursor position.
void cursorDown()
Moves the cursor down by one line.
void insertLine()
Inserts a line at the cursor position.
virtual ~Terminal()
Destroys this terminal object.
void deleteLine()
Deletes a line at the cursor position.
void cursorLeft()
Moves the cursor left by one character.
void reverse()
Reverse the foreground and background colors for inverted text.
Color
Terminal foreground or background colors.
void scrollDown()
Scrolls the contents of the window down one line.
virtual void flush()
Flushes all data in the underlying stream.
virtual size_t write(uint8_t c)
Writes a single byte to the underlying stream.
void deleteChar()
Deletes the character at the cursor position.
void bold()
Enables bold text.
void clearToEOL()
Clears from the current cursor position to the end of the line.
void color(Color fg)
Selects a text foreground color with the default background color.
void begin(Stream &stream, Mode mode=Serial)
Begins terminal operations on an underlying stream.
void cursorUp()
Moves the cursor up by one line.
void backspace()
Backspaces over the last character.
void cursorRight()
Moves the cursor right by one character.
void cursorMove(int x, int y)
Moves the cursor to a specific location in the window.
Operates the terminal in telnet mode.
bool setWindowSize(int columns, int rows)
Sets the number of columns and rows in the window.
Terminal::Mode mode() const
Returns the mode this terminal is operating in, Serial or Telnet.
Extended stream interface for terminal operations.
size_t writeUnicode(long code)
Writes a Unicode code point to the output in UTF-8 encoding.
Mode
Mode to operate in, Serial or Telnet.
Terminal()
Constructs a terminal object.
Stream * stream() const
Returns a pointer to the underlying Stream, or NULL if the stream has not been set with begin() yet...
virtual int peek()
Peeks at the next byte from the underlying stream.
void underline()
Enables underlined text.
void clear()
Move the cursor to the top-left position and clear the screen.
void writeProgMem(const char *str)
Writes a static string that is stored in program memory.
virtual int read()
Reads the next byte from the underlying stream.
static size_t utf8Format(uint8_t *buffer, long code)
Formats a Unicode code point in a buffer in the UTF-8 encoding.
int rows() const
Gets the number of rows in the window; defaults to 24.
void scrollUp()
Scrolls the contents of the window up one line.
static size_t utf8Length(long code)
Determines the length of a Unicode code point in the UTF-8 encoding.
void end()
Ends terminal operations on an underlying stream.
int readKey()
Reads the next key that was typed on this terminal.
void normal()
Selects normal text with all attributes and colors off.
void blink()
Enables blinking text.
int columns() const
Gets the number of columns in the window; defaults to 80.
static bool isWideCharacter(long code)
Determine if a Unicode character is wide.
virtual int available()
Returns the number of bytes that are available for reading.