ArduinoLibs
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
Terminal.cpp
1 /*
2  * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20  * DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include "Terminal.h"
24 #include "TelnetDefs.h"
25 
100 // States for the key recognition state machine.
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.
113 
114 // Number of milliseconds to wait after an ESC character before
115 // concluding that it is KEY_ESC rather than an escape sequence.
116 #define ESC_TIMEOUT_MS 40
117 
118 // Number of milliseconds to wait for a new character within an
119 // escape sequence before concluding that the sequence was invalid
120 // or truncated, or not actually an escape sequence at all.
121 #define SEQ_TIMEOUT_MS 200
122 
134  : _stream(0)
135  , ucode(-1)
136  , ncols(80)
137  , nrows(24)
138  , timer(0)
139  , offset(0)
140  , state(STATE_INIT)
141  , utf8len(0)
142  , mod(Terminal::Serial)
143  , flags(0)
144 {
145 }
146 
151 {
152 }
153 
183 void Terminal::begin(Stream &stream, Mode mode)
184 {
185  _stream = &stream;
186  ucode = -1;
187  state = STATE_INIT;
188  flags = 0;
189  mod = mode;
190 }
191 
199 {
200  _stream = 0;
201 }
202 
229 {
230  return _stream ? _stream->available() : 0;
231 }
232 
241 {
242  return _stream ? _stream->peek() : -1;
243 }
244 
262 {
263  // Clear the key recognition state because we are bypassing readKey().
264  state = STATE_INIT;
265  ucode = -1;
266 
267  // Read the next byte from the underlying stream.
268  return _stream ? _stream->read() : -1;
269 }
270 
275 {
276  if (_stream)
277  _stream->flush();
278 }
279 
286 size_t Terminal::write(uint8_t c)
287 {
288  return _stream ? _stream->write(c) : 0;
289 }
290 
299 size_t Terminal::write(const uint8_t *buffer, size_t size)
300 {
301  return _stream ? _stream->write(buffer, size) : 0;
302 }
303 
314 void Terminal::writeProgMem(const char *str)
315 {
316  uint8_t buffer[16];
317  uint8_t posn;
318  uint8_t ch;
319  if (!_stream || !str)
320  return;
321  posn = 0;
322  while ((ch = pgm_read_byte((const uint8_t *)str)) != 0) {
323  buffer[posn++] = ch;
324  if (posn == sizeof(buffer)) {
325  _stream->write(buffer, posn);
326  posn = 0;
327  }
328  ++str;
329  }
330  if (posn != 0)
331  _stream->write(buffer, posn);
332 }
333 
341 static bool escapeSequenceStart(int ch)
342 {
343  if (ch == '[' || ch == '?')
344  return true;
345  else if (ch >= 'A' && ch <= 'Z')
346  return true;
347  else
348  return false;
349 }
350 
388 {
389  int ch;
390 
391  // Bail out if there is no underlying stream.
392  if (!_stream)
393  return -1;
394 
395  // Read the next character and bail out if nothing yet. Some special
396  // peek-ahead handling is needed just after the ESC character.
397  if (state == STATE_ESC) {
398  ch = _stream->peek();
399  if (ch < 0) {
400  // We just saw an ESC. If there has been a timeout
401  // then the key is KEY_ESC rather than the start of a
402  // VT100 escape sequence.
403  if ((millis() - timer) >= ESC_TIMEOUT_MS) {
404  state = STATE_INIT;
405  ucode = 0x1B;
406  return KEY_ESC;
407  }
408  ucode = -1;
409  return -1;
410  } else if (!escapeSequenceStart(ch)) {
411  // The next character is not legitimate as the start of
412  // an escape sequence, so the ESC must have been KEY_ESC.
413  state = STATE_INIT;
414  ucode = 0x1B;
415  return KEY_ESC;
416  } else {
417  // Part of an escape sequence. Read the character properly.
418  ch = _stream->read();
419  }
420  } else {
421  // Read the next character without any peek-ahead.
422  ch = _stream->read();
423  }
424  if (ch < 0) {
425  if (state == STATE_MATCH && (millis() - timer) >= SEQ_TIMEOUT_MS) {
426  // Timeout while waiting for the next character in an
427  // escape sequence. Abort and return to the initial state.
428  state = STATE_INIT;
429  }
430  ucode = -1;
431  return -1;
432  }
433 
434  // Determine what to do based on the key recognition state.
435  switch (state) {
436  case STATE_CR:
437  // We just saw a CR, so check for CRLF and eat the LF.
438  state = STATE_INIT;
439  if (ch == 0x0A) {
440  ucode = -1;
441  return -1;
442  } else if (ch == 0x00 && mod == Telnet) {
443  // In telnet mode, CR NUL is a literal carriage return,
444  // separate from the newline sequence CRLF. Eat the NUL.
445  // We already reported KEY_RETURN for the CR character.
446  ucode = -1;
447  return -1;
448  }
449  // Fall through to the next case.
450 
451  case STATE_INIT:
452  if (ch >= 0x20 && ch <= 0x7E) {
453  // Printable ASCII character.
454  state = STATE_INIT;
455  ucode = ch;
456  return ch;
457  } else if (ch == 0x1B) {
458  // Start of an escape sequence, or the escape character itself.
459  state = STATE_ESC;
460  timer = millis();
461  } else if (ch == 0x0D) {
462  // CR which may be followed by an LF.
463  state = STATE_CR;
464  ucode = ch;
465  return KEY_RETURN;
466  } else if (ch == 0x0A) {
467  // LF on its own without a preceding CR.
468  ucode = ch;
469  return KEY_RETURN;
470  } else if (ch == 0x08 || ch == 0x7F) {
471  // Backspace or DEL character.
472  state = STATE_INIT;
473  ucode = ch;
474  return KEY_BACKSPACE;
475  } else if (ch == 0x09) {
476  // TAB character.
477  state = STATE_INIT;
478  ucode = ch;
479  return KEY_TAB;
480  } else if (ch < 0x80) {
481  // Some other ASCII control character.
482  state = STATE_INIT;
483  ucode = ch;
484  return ch;
485  } else if (ch >= 0xC1 && ch <= 0xDF) {
486  // Two-byte UTF-8 sequence.
487  offset = ch & 0x1F;
488  utf8len = 2;
489  state = STATE_UTF8;
490  } else if (ch >= 0xE1 && ch <= 0xEF) {
491  // Three-byte UTF-8 sequence.
492  offset = ch & 0x0F;
493  utf8len = 3;
494  state = STATE_UTF8;
495  } else if (ch >= 0xF1 && ch <= 0xF7) {
496  // Four-byte UTF-8 sequence.
497  offset = ch & 0x07;
498  utf8len = 4;
499  state = STATE_UTF8;
500  } else if (ch == 0xFF && mod == Telnet) {
501  // Start of a telnet command (IAC byte).
502  state = STATE_IAC;
503  }
504  break;
505 
506  case STATE_ESC:
507  // Next character just after the ESC. Start the escape
508  // sequence matching engine at offset zero in the keymap table.
509  state = STATE_MATCH;
510  offset = 0;
511  // Fall through to the next case.
512 
513  case STATE_MATCH:
514  // In the middle of matching an escape sequence.
515  if (ch == 0x1B) {
516  // ESC character seen in the middle of an escape sequence.
517  // The previous escape sequence is invalid so abort and restart.
518  state = STATE_ESC;
519  timer = millis();
520  break;
521  }
522  ch = matchEscape(ch);
523  if (ch == -1) {
524  // Need more characters before knowing what this is.
525  timer = millis();
526  } else if (ch == -2) {
527  // Invalid escape sequence so abort and restart.
528  state = STATE_INIT;
529  } else if (ch < 0x80) {
530  // Escape sequence corresponds to a normal ASCII character.
531  state = STATE_INIT;
532  ucode = ch;
533  return ch;
534  } else {
535  // Extended keycode for an arrow or function key.
536  state = STATE_INIT;
537  ucode = -1;
538  return ch;
539  }
540  break;
541 
542  case STATE_UTF8:
543  // Recognize a multi-byte UTF-8 character encoding.
544  if ((ch & 0xC0) == 0x80) {
545  if (utf8len <= 2) {
546  // Final character in the sequence.
547  ucode = (((long)offset) << 6) | (ch & 0x3F);
548  state = STATE_INIT;
549  if (ucode > 0x10FFFFL)
550  break; // The code point is out of range.
551  return KEY_UNICODE;
552  } else {
553  // More characters still yet to come.
554  --utf8len;
555  offset = (offset << 6) | (ch & 0x3F);
556  }
557  } else {
558  // This character is invalid as part of a UTF-8 sequence.
559  state = STATE_INIT;
560  }
561  break;
562 
563  case STATE_IAC:
564  // Telnet command byte just after an IAC (0xFF) character.
565  switch (ch) {
566  case TelnetDefs::EndOfFile:
567  // Convert EOF into CTRL-D.
568  state = STATE_INIT;
569  ucode = 0x04;
570  return 0x04;
571 
572  case TelnetDefs::EndOfRecord:
573  // Convert end of record markers into CR.
574  state = STATE_INIT;
575  ucode = 0x0D;
576  return KEY_RETURN;
577 
578  case TelnetDefs::Interrupt:
579  // Convert interrupt into CTRL-C.
580  state = STATE_INIT;
581  ucode = 0x03;
582  return 0x03;
583 
584  case TelnetDefs::EraseChar:
585  // Convert erase character into DEL.
586  state = STATE_INIT;
587  ucode = 0x7F;
588  return KEY_BACKSPACE;
589 
590  case TelnetDefs::EraseLine:
591  // Convert erase line into CTRL-U.
592  state = STATE_INIT;
593  ucode = 0x15;
594  return 0x15;
595 
596  case TelnetDefs::SubStart:
597  // Option sub-negotiation.
598  utf8len = 0;
599  state = STATE_SB;
600  break;
601 
602  case TelnetDefs::WILL:
603  // Option negotiation, WILL command.
604  state = STATE_WILL;
605  break;
606 
607  case TelnetDefs::WONT:
608  // Option negotiation, WONT command.
609  state = STATE_WONT;
610  break;
611 
612  case TelnetDefs::DO:
613  // Option negotiation, DO command.
614  state = STATE_DO;
615  break;
616 
617  case TelnetDefs::DONT:
618  // Option negotiation, DONT command.
619  state = STATE_DONT;
620  break;
621 
622  case TelnetDefs::IAC:
623  // IAC followed by IAC is the literal byte 0xFF,
624  // but that isn't valid UTF-8 so we just drop it.
625  state = STATE_INIT;
626  break;
627 
628  default:
629  // Everything else is treated as a NOP.
630  state = STATE_INIT;
631  break;
632  }
633  break;
634 
635  case STATE_WILL:
636  // Telnet option negotiation, WILL command. Note: We don't do any
637  // loop detection. We assume that the client will eventually break
638  // the loop as it probably has more memory than us to store state.
639  if (ch == TelnetDefs::WindowSize ||
640  ch == TelnetDefs::RemoteFlowControl) {
641  // Send a DO command in response - we accept this option.
642  telnetCommand(TelnetDefs::DO, ch);
643  } else {
644  // Send a DONT command in response - we don't accept this option.
645  telnetCommand(TelnetDefs::DONT, ch);
646  }
647  if (!(flags & 0x01)) {
648  // The first time we see a WILL command from the client we
649  // send a request back saying that we will handle echoing.
650  flags |= 0x01;
651  telnetCommand(TelnetDefs::WILL, TelnetDefs::Echo);
652  }
653  state = STATE_INIT;
654  break;
655 
656  case STATE_WONT:
657  case STATE_DONT:
658  // Telnet option negotiation, WONT/DONT command. The other side
659  // is telling us that it does not understand this option or wants
660  // us to stop using it. For now there is nothing to do.
661  state = STATE_INIT;
662  break;
663 
664  case STATE_DO:
665  // Telnet option negotiation, DO command. Note: Other than Echo
666  // we don't do any loop detection. We assume that the client will
667  // break the loop as it probably has more memory than us to store state.
668  if (ch == TelnetDefs::Echo) {
669  // Special handling needed for Echo - don't say WILL again
670  // when the client acknowledges us with a DO command.
671  } else if (ch == TelnetDefs::SuppressGoAhead) {
672  // Send a WILL command in response - we accept this option.
673  telnetCommand(TelnetDefs::WILL, ch);
674  } else {
675  // Send a WONT command in response - we don't accept this option.
676  telnetCommand(TelnetDefs::WONT, ch);
677  }
678  state = STATE_INIT;
679  break;
680 
681  case STATE_SB:
682  // Telnet option sub-negotiation. Collect up all bytes and
683  // then execute the option once "IAC SubEnd" is seen.
684  if (ch == TelnetDefs::IAC) {
685  // IAC byte, which will be followed by either IAC or SubEnd.
686  state = STATE_SB_IAC;
687  break;
688  }
689  if (utf8len < sizeof(sb))
690  sb[utf8len++] = ch;
691  break;
692 
693  case STATE_SB_IAC:
694  // Telnet option sub-negotiation, byte after IAC.
695  if (ch == TelnetDefs::IAC) {
696  // Two IAC bytes in a row is a single escaped 0xFF byte.
697  if (utf8len < sizeof(sb))
698  sb[utf8len++] = 0xFF;
699  state = STATE_SB;
700  break;
701  } else if (ch == TelnetDefs::SubEnd) {
702  // End of the sub-negotiation field. Handle window size changes.
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];
706  if (!width) // Zero width or height means "unspecified".
707  width = ncols;
708  if (!height)
709  height = nrows;
710  if (setWindowSize(width, height)) {
711  // The window size has changed; notify the caller.
712  ucode = -1;
713  state = STATE_INIT;
714  return KEY_WINSIZE;
715  }
716  }
717  }
718  state = STATE_INIT;
719  break;
720  }
721 
722  // If we get here, then we're still waiting for a full sequence.
723  ucode = -1;
724  return -1;
725 }
726 
757 size_t Terminal::writeUnicode(long code)
758 {
759  uint8_t utf8[4];
760  size_t size = utf8Format(utf8, code);
761  if (size > 0)
762  write(utf8, size);
763  return size;
764 }
765 
801 bool Terminal::setWindowSize(int columns, int rows)
802 {
803  // Sanity-check the range first.
804  if (columns < 1)
805  columns = 1;
806  else if (columns > 10000)
807  columns = 10000;
808  if (rows < 1)
809  rows = 1;
810  else if (rows > 10000)
811  rows = 10000;
812  if (ncols != columns || nrows != rows) {
813  ncols = columns;
814  nrows = rows;
815  return true;
816  } else {
817  return false;
818  }
819 }
820 
825 {
826  static char const escape[] PROGMEM = "\033[H\033[J";
827  writeProgMem(escape);
828 }
829 
834 {
835  static char const escape[] PROGMEM = "\033[K";
836  writeProgMem(escape);
837 }
838 
839 // Writes a decimal number to a buffer.
840 static void writeNumber(uint8_t *buf, uint8_t &posn, int value)
841 {
842  int divisor = 10000;
843  bool haveDigits = false;
844  while (divisor >= 1) {
845  int digit = value / divisor;
846  if (digit || haveDigits) {
847  buf[posn++] = '0' + digit;
848  haveDigits = true;
849  }
850  value %= divisor;
851  divisor /= 10;
852  }
853  if (!haveDigits) {
854  buf[posn++] = '0';
855  }
856 }
857 
866 void Terminal::cursorMove(int x, int y)
867 {
868  if (!_stream)
869  return;
870 
871  // Range check the arguments.
872  if (x < 0)
873  x = 0;
874  else if (x >= ncols)
875  x = ncols - 1;
876  if (y < 0)
877  y = 0;
878  else if (y >= nrows)
879  y = nrows - 1;
880 
881  // Format the command "ESC[row;colH" and send it.
882  uint8_t buffer[16];
883  uint8_t posn = 0;
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);
891 }
892 
899 {
900  static char const escape[] PROGMEM = "\033[D";
901  writeProgMem(escape);
902 }
903 
910 {
911  static char const escape[] PROGMEM = "\033[C";
912  writeProgMem(escape);
913 }
914 
921 {
922  static char const escape[] PROGMEM = "\033[A";
923  writeProgMem(escape);
924 }
925 
932 {
933  static char const escape[] PROGMEM = "\033[B";
934  writeProgMem(escape);
935 }
936 
950 {
951  static char const escape[] PROGMEM = "\b \b";
952  writeProgMem(escape);
953 }
954 
961 {
962  static char const escape[] PROGMEM = "\033[L";
963  writeProgMem(escape);
964 }
965 
972 {
973  static char const escape[] PROGMEM = "\033[@";
974  writeProgMem(escape);
975 }
976 
983 {
984  static char const escape[] PROGMEM = "\033[M";
985  writeProgMem(escape);
986 }
987 
994 {
995  static char const escape[] PROGMEM = "\033[P";
996  writeProgMem(escape);
997 }
998 
1005 {
1006  static char const escape[] PROGMEM = "\033[S";
1007  writeProgMem(escape);
1008 }
1009 
1016 {
1017  static char const escape[] PROGMEM = "\033[T";
1018  writeProgMem(escape);
1019 }
1020 
1027 {
1028  static char const escape[] PROGMEM = "\033[0m";
1029  writeProgMem(escape);
1030 }
1031 
1038 {
1039  static char const escape[] PROGMEM = "\033[1m";
1040  writeProgMem(escape);
1041 }
1042 
1047 {
1048  static char const escape[] PROGMEM = "\033[4m";
1049  writeProgMem(escape);
1050 }
1051 
1056 {
1057  static char const escape[] PROGMEM = "\033[5m";
1058  writeProgMem(escape);
1059 }
1060 
1065 {
1066  static char const escape[] PROGMEM = "\033[7m";
1067  writeProgMem(escape);
1068 }
1069 
1175 {
1176  uint8_t code = (fg & 0x07);
1177  uint8_t bold = (fg & 0x08) ? 1 : 0;
1178  if (!_stream)
1179  return;
1180  uint8_t buffer[16];
1181  uint8_t posn = 0;
1182  buffer[posn++] = 0x1B;
1183  buffer[posn++] = '[';
1184  buffer[posn++] = '0'; // reset all attributes first
1185  buffer[posn++] = ';';
1186  buffer[posn++] = '3';
1187  buffer[posn++] = '0' + code;
1188  if (bold) {
1189  buffer[posn++] = ';';
1190  buffer[posn++] = '1';
1191  }
1192  buffer[posn++] = 'm';
1193  _stream->write(buffer, posn);
1194 }
1195 
1207 {
1208  uint8_t codefg = (fg & 0x07);
1209  uint8_t boldfg = (fg & 0x08) ? 1 : 0;
1210  uint8_t codebg = (bg & 0x07);
1211  if (!_stream)
1212  return;
1213  uint8_t buffer[16];
1214  uint8_t posn = 0;
1215  buffer[posn++] = 0x1B;
1216  buffer[posn++] = '[';
1217  buffer[posn++] = '0'; // reset all attributes first
1218  buffer[posn++] = ';';
1219  buffer[posn++] = '3';
1220  buffer[posn++] = '0' + codefg;
1221  if (boldfg) {
1222  buffer[posn++] = ';';
1223  buffer[posn++] = '1';
1224  }
1225  buffer[posn++] = ';';
1226  buffer[posn++] = '4';
1227  buffer[posn++] = '0' + codebg;
1228  buffer[posn++] = 'm';
1229  _stream->write(buffer, posn);
1230 }
1231 
1248 {
1249  // This function was automatically generated by genwcwidth.c
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
1255  };
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
1265  };
1266  unsigned c;
1267  if (code < 0x2300) {
1268  return false;
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) {
1276  return true;
1277  } else if (code >= 0x4E00 && code <= 0x9FFF) {
1278  return true;
1279  } else if (code >= 0xF900 && code <= 0xFAFF) {
1280  return true;
1281  } else if (code >= 0x20000 && code <= 0x2FFFD) {
1282  return true;
1283  } else if (code >= 0x30000 && code <= 0x3FFFD) {
1284  return true;
1285  } else if (code == 0x2329 ||
1286  code == 0x232A ||
1287  code == 0x3250 ||
1288  code == 0xA015) {
1289  return true;
1290  }
1291  return false;
1292 }
1302 size_t Terminal::utf8Length(long code)
1303 {
1304  // Reference: https://tools.ietf.org/html/rfc3629
1305  if (code < 0) {
1306  return 0;
1307  } else if (code <= 0x7FL) {
1308  return 1;
1309  } else if (code <= 0x07FFL) {
1310  return 2;
1311  } else if (code >= 0xD800L && code <= 0xDFFF) {
1312  // UTF-16 surrogate pairs are not valid in UTF-8.
1313  return 0;
1314  } else if (code <= 0xFFFFL) {
1315  return 3;
1316  } else if (code <= 0x10FFFFL) {
1317  return 4;
1318  } else {
1319  return 0;
1320  }
1321 }
1322 
1334 size_t Terminal::utf8Format(uint8_t *buffer, long code)
1335 {
1336  // Reference: https://tools.ietf.org/html/rfc3629
1337  if (code < 0) {
1338  return 0;
1339  } else if (code <= 0x7FL) {
1340  buffer[0] = (uint8_t)code;
1341  return 1;
1342  } else if (code <= 0x07FFL) {
1343  buffer[0] = 0xC0 | (uint8_t)(code >> 6);
1344  buffer[1] = 0x80 | (((uint8_t)code) & 0x3F);
1345  return 2;
1346  } else if (code >= 0xD800L && code <= 0xDFFF) {
1347  // UTF-16 surrogate pairs are not valid in UTF-8.
1348  return 0;
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);
1353  return 3;
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);
1359  return 4;
1360  } else {
1361  return 0;
1362  }
1363 }
1364 
1365 // Keymap rule table. Compact representation of a recognition tree.
1366 // Each tree node is an array of entries of the following forms:
1367 // 0 End of this tree level.
1368 // ch code Leaf node: ASCII character (bit 7 clear) plus 8-bit keycode.
1369 // ch offset Interior node: ASCII character with the high bit set
1370 // plus a 16-bit offset to the first child node.
1371 // This table was generated with the "genkeymap" tool. Do not edit this
1372 // table but rather edit the tool and rebuild the table from it.
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,
1412  0x58, 0x3D, 0x00
1413 };
1414 
1422 int Terminal::matchEscape(int ch)
1423 {
1424  uint8_t kch;
1425  for (;;) {
1426  kch = pgm_read_byte(keymap + offset);
1427  if (!kch) {
1428  // No match at this level, so the escape sequence is invalid.
1429  break;
1430  } else if (kch & 0x80) {
1431  // Interior node.
1432  if ((kch & 0x7F) == ch) {
1433  // Interior node matches. Go down one tree level.
1434  offset = ((int)(pgm_read_byte(keymap + offset + 1))) |
1435  (((int)(pgm_read_byte(keymap + offset + 2))) << 8);
1436  return -1;
1437  }
1438  offset += 3;
1439  } else {
1440  // Leaf node.
1441  if (kch == (uint8_t)ch) {
1442  // We have found a match on a full escape sequence.
1443  return pgm_read_byte(keymap + offset + 1);
1444  }
1445  offset += 2;
1446  }
1447  }
1448  return -2;
1449 }
1450 
1457 void Terminal::telnetCommand(uint8_t type, uint8_t option)
1458 {
1459  uint8_t buf[3];
1460  buf[0] = (uint8_t)TelnetDefs::IAC;
1461  buf[1] = type;
1462  buf[2] = option;
1463  _stream->write(buf, 3);
1464 }
void insertChar()
Inserts a blank character at the cursor position.
Definition: Terminal.cpp:971
void cursorDown()
Moves the cursor down by one line.
Definition: Terminal.cpp:931
void insertLine()
Inserts a line at the cursor position.
Definition: Terminal.cpp:960
virtual ~Terminal()
Destroys this terminal object.
Definition: Terminal.cpp:150
void deleteLine()
Deletes a line at the cursor position.
Definition: Terminal.cpp:982
void cursorLeft()
Moves the cursor left by one character.
Definition: Terminal.cpp:898
void reverse()
Reverse the foreground and background colors for inverted text.
Definition: Terminal.cpp:1064
Color
Terminal foreground or background colors.
Definition: Terminal.h:102
void scrollDown()
Scrolls the contents of the window down one line.
Definition: Terminal.cpp:1015
virtual void flush()
Flushes all data in the underlying stream.
Definition: Terminal.cpp:274
virtual size_t write(uint8_t c)
Writes a single byte to the underlying stream.
Definition: Terminal.cpp:286
void deleteChar()
Deletes the character at the cursor position.
Definition: Terminal.cpp:993
void bold()
Enables bold text.
Definition: Terminal.cpp:1037
void clearToEOL()
Clears from the current cursor position to the end of the line.
Definition: Terminal.cpp:833
void color(Color fg)
Selects a text foreground color with the default background color.
Definition: Terminal.cpp:1174
void begin(Stream &stream, Mode mode=Serial)
Begins terminal operations on an underlying stream.
Definition: Terminal.cpp:183
void cursorUp()
Moves the cursor up by one line.
Definition: Terminal.cpp:920
void backspace()
Backspaces over the last character.
Definition: Terminal.cpp:949
void cursorRight()
Moves the cursor right by one character.
Definition: Terminal.cpp:909
void cursorMove(int x, int y)
Moves the cursor to a specific location in the window.
Definition: Terminal.cpp:866
Operates the terminal in telnet mode.
Definition: Terminal.h:45
bool setWindowSize(int columns, int rows)
Sets the number of columns and rows in the window.
Definition: Terminal.cpp:801
Terminal::Mode mode() const
Returns the mode this terminal is operating in, Serial or Telnet.
Definition: Terminal.h:52
Extended stream interface for terminal operations.
Definition: Terminal.h:36
size_t writeUnicode(long code)
Writes a Unicode code point to the output in UTF-8 encoding.
Definition: Terminal.cpp:757
Mode
Mode to operate in, Serial or Telnet.
Definition: Terminal.h:42
Terminal()
Constructs a terminal object.
Definition: Terminal.cpp:133
Stream * stream() const
Returns a pointer to the underlying Stream, or NULL if the stream has not been set with begin() yet...
Definition: Terminal.h:51
virtual int peek()
Peeks at the next byte from the underlying stream.
Definition: Terminal.cpp:240
void underline()
Enables underlined text.
Definition: Terminal.cpp:1046
void clear()
Move the cursor to the top-left position and clear the screen.
Definition: Terminal.cpp:824
void writeProgMem(const char *str)
Writes a static string that is stored in program memory.
Definition: Terminal.cpp:314
virtual int read()
Reads the next byte from the underlying stream.
Definition: Terminal.cpp:261
static size_t utf8Format(uint8_t *buffer, long code)
Formats a Unicode code point in a buffer in the UTF-8 encoding.
Definition: Terminal.cpp:1334
int rows() const
Gets the number of rows in the window; defaults to 24.
Definition: Terminal.h:73
void scrollUp()
Scrolls the contents of the window up one line.
Definition: Terminal.cpp:1004
static size_t utf8Length(long code)
Determines the length of a Unicode code point in the UTF-8 encoding.
Definition: Terminal.cpp:1302
void end()
Ends terminal operations on an underlying stream.
Definition: Terminal.cpp:198
int readKey()
Reads the next key that was typed on this terminal.
Definition: Terminal.cpp:387
void normal()
Selects normal text with all attributes and colors off.
Definition: Terminal.cpp:1026
void blink()
Enables blinking text.
Definition: Terminal.cpp:1055
int columns() const
Gets the number of columns in the window; defaults to 80.
Definition: Terminal.h:72
static bool isWideCharacter(long code)
Determine if a Unicode character is wide.
Definition: Terminal.cpp:1247
virtual int available()
Returns the number of bytes that are available for reading.
Definition: Terminal.cpp:228