1
0
mirror of https://github.com/taigrr/arduinolibs synced 2025-01-18 04:33:12 -08:00
arduinolibs/libraries/Shell/Terminal.cpp
2016-03-13 07:24:22 +10:00

1465 lines
43 KiB
C++

/*
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "Terminal.h"
#include "TelnetDefs.h"
/**
* \class Terminal Terminal.h <Terminal.h>
* \brief Extended stream interface for terminal operations.
*
* This class extends the standard Arduino Stream class with functions that
* are suitable for interfacing to VT100 terminal applications like PuTTY.
*
* The following example initializes a terminal running on the primary
* Arduino serial port:
*
* \code
* Terminal term;
* void setup() {
* Serial.begin(9600);
* term.begin(Serial);
* }
* \endcode
*
* The readKey() function reads input from the underlying stream, decodes
* any VT100 key escape sequences that it finds, and reports them to the
* application using USB, ASCII, or Unicode key codes. This is typically
* used in the application's main loop as follows:
*
* \code
* void loop() {
* int key = term.readKey();
* switch (key) {
* case -1: break; // No key available.
*
* case KEY_LEFT_ARROW:
* // Left arrow key has been pressed.
* ...
* break;
*
* case KEY_RIGHT_ARROW:
* // Right arrow key has been pressed.
* ...
* break;
*
* case KEY_ESC:
* // Escape key has been pressed.
* ...
* break;
*
* default:
* if (key >= 0x20 && key <= 7E) {
* // Visible ASCII character has been typed.
* ...
* }
* break;
* }
* }
* \endcode
*
* This class understands extended characters in the UTF-8 encoding,
* allowing the full Unicode character set to be used in applications.
* Extended Unicode characters are reported by readKey() with the key code
* KEY_UNICODE, with the actual code point returned via unicodeKey().
*
* On the output side, UTF-8 strings can be written to the terminal
* using write(), or writeUnicode() can be used to write a single
* Unicode character in UTF-8.
*
* \note This class does not have any special support for right-to-left
* scripts or composed characters. Unicode characters are read and written
* in the order in which they arrive. Applications may need to alter
* strings to display them correctly in such scripts. Patches are
* welcome to fix this.
*
* \sa Shell
*/
/** @cond */
// States for the key recognition state machine.
#define STATE_INIT 0 // Initial state.
#define STATE_CR 1 // Last character was CR, eat following LF.
#define STATE_ESC 2 // Last character was ESC.
#define STATE_MATCH 3 // Matching an escape sequence.
#define STATE_UTF8 4 // Recognizing a UTF-8 sequence.
#define STATE_IAC 5 // Recognizing telnet command after IAC (0xFF).
#define STATE_WILL 6 // Waiting for option code for WILL command.
#define STATE_WONT 7 // Waiting for option code for WONT command.
#define STATE_DO 8 // Waiting for option code for DO command.
#define STATE_DONT 9 // Waiting for option code for DONT command.
#define STATE_SB 10 // Option sub-negotiation.
#define STATE_SB_IAC 11 // Option sub-negotiation, byte after IAC.
// Number of milliseconds to wait after an ESC character before
// concluding that it is KEY_ESC rather than an escape sequence.
#define ESC_TIMEOUT_MS 40
// Number of milliseconds to wait for a new character within an
// escape sequence before concluding that the sequence was invalid
// or truncated, or not actually an escape sequence at all.
#define SEQ_TIMEOUT_MS 200
/** @endcond */
/**
* \brief Constructs a terminal object.
*
* This constructor must be followed by a call to begin() to specify
* the underlying stream to use for reading and writing.
*
* \sa begin()
*/
Terminal::Terminal()
: _stream(0)
, ucode(-1)
, ncols(80)
, nrows(24)
, timer(0)
, offset(0)
, state(STATE_INIT)
, utf8len(0)
, mod(Terminal::Serial)
, flags(0)
{
}
/**
* \brief Destroys this terminal object.
*/
Terminal::~Terminal()
{
}
/**
* \enum Terminal::Mode
* \brief Mode to operate in, Serial or Telnet.
*/
/**
* \var Terminal::Serial
* \brief Operates the terminal in serial mode.
*/
/**
* \var Terminal::Telnet
* \brief Operates the terminal in telnet mode.
*/
/**
* \brief Begins terminal operations on an underlying stream.
*
* \param stream The underlying stream, whether a serial port, TCP connection,
* or some other stream.
* \param mode The mode to operate in, either Serial or Telnet.
*
* If Telnet mode is selected, then embedded commands and options from the
* telnet protocol (<a href="https://tools.ietf.org/html/rfc854">RFC 854</a>)
* will be interpreted. This is useful if the underlying \a stream is a TCP
* connection on port 23. The mode operates as a telnet server.
*
* \sa end(), stream(), mode()
*/
void Terminal::begin(Stream &stream, Mode mode)
{
_stream = &stream;
ucode = -1;
state = STATE_INIT;
flags = 0;
mod = mode;
}
/**
* \brief Ends terminal operations on an underlying stream.
*
* This function may be useful if you want to detach the terminal from
* the underlying stream so that it can be used for something else.
*/
void Terminal::end()
{
_stream = 0;
}
/**
* \fn Stream *Terminal::stream() const
* \brief Returns a pointer to the underlying Stream, or NULL if the
* stream has not been set with begin() yet.
*
* \sa begin()
*/
/**
* \fn Terminal::Mode Terminal::mode() const
* \brief Returns the mode this terminal is operating in, Serial or Telnet.
*
* \sa begin()
*/
/**
* \brief Returns the number of bytes that are available for reading.
*
* \note It is possible for this function to return a positive value
* while readKey() does not produce a new key. This can happen with
* VT100 key escape sequences and UTF-8 characters that extend over
* multiple bytes.
*
* \sa readKey()
*/
int Terminal::available()
{
return _stream ? _stream->available() : 0;
}
/**
* \brief Peeks at the next byte from the underlying stream.
*
* \return The next byte or -1 if no bytes are available yet.
*
* \sa read()
*/
int Terminal::peek()
{
return _stream ? _stream->peek() : -1;
}
/**
* \brief Reads the next byte from the underlying stream.
*
* \return Returns 0x00 to 0xFF if a byte is ready, or -1 if none available.
*
* This function performs a low-level read on the underlying byte stream
* without applying any specific interpretation to the byte. In particular,
* escape sequences corresponding to arrow and function keys will not
* be recognized.
*
* Applications will usually want to call readKey() instead to handle
* escape sequences for arrow and function keys. This function is provided
* as a convenience to implement the parent Stream interface.
*
* \sa readKey()
*/
int Terminal::read()
{
// Clear the key recognition state because we are bypassing readKey().
state = STATE_INIT;
ucode = -1;
// Read the next byte from the underlying stream.
return _stream ? _stream->read() : -1;
}
/**
* \brief Flushes all data in the underlying stream.
*/
void Terminal::flush()
{
if (_stream)
_stream->flush();
}
/**
* \brief Writes a single byte to the underlying stream.
*
* \param c The byte to write.
* \return The number of bytes written, zero on error.
*/
size_t Terminal::write(uint8_t c)
{
return _stream ? _stream->write(c) : 0;
}
/**
* \brief Writes a buffer of data to the underlying stream.
*
* \param buffer Points to the buffer to write.
* \param size The number of bytes in the \a buffer.
*
* \return The number of bytes written, which may be short on error.
*/
size_t Terminal::write(const uint8_t *buffer, size_t size)
{
return _stream ? _stream->write(buffer, size) : 0;
}
/**
* \brief Writes a static string that is stored in program memory.
*
* \param str Points to the NUL-terminated string in program memory.
*
* This is a convenience function for printing static strings that
* are stored in program memory.
*
* \sa write()
*/
void Terminal::writeProgMem(const char *str)
{
uint8_t buffer[16];
uint8_t posn;
uint8_t ch;
if (!_stream || !str)
return;
posn = 0;
while ((ch = pgm_read_byte((const uint8_t *)str)) != 0) {
buffer[posn++] = ch;
if (posn == sizeof(buffer)) {
_stream->write(buffer, posn);
posn = 0;
}
++str;
}
if (posn != 0)
_stream->write(buffer, posn);
}
/**
* \brief Determine if a character starts an escape sequence after ESC.
*
* \param ch The character to test.
*
* \return Returns true if \a ch starts an escape sequence; false otherwise.
*/
static bool escapeSequenceStart(int ch)
{
if (ch == '[' || ch == '?')
return true;
else if (ch >= 'A' && ch <= 'Z')
return true;
else
return false;
}
/**
* \brief Reads the next key that was typed on this terminal.
*
* \return Returns -1 if there is no key ready yet; 0x00 to 0x7F for
* an ASCII character; KEY_UNICODE for an extended Unicode code point,
* or a USB keyboard code for special arrow or function keys.
*
* For example, if the user types the Home key, then this function
* will return KEY_HOME. If the user types the capital letter A,
* then this function will return 0x41.
*
* If the user types an extended Unicode character (U+0080 and higher),
* then this function will return KEY_UNICODE. The application should
* call unicodeKey() to retrieve the actual code point. All Unicode
* characters are assumed to be in the UTF-8 encoding on the stream.
*
* Some ASCII control characters correspond to special keys and will
* be mapped appropriately:
*
* \li 0x08 (CTRL-H) and 0x7F (DEL) are mapped to KEY_BACKSPACE
* \li 0x0D (CTRL-M) and 0x0A (CTRL-J) are mapped to KEY_RETURN
* \li 0x09 (CTRL-I) is mapped to KEY_TAB
* \li 0x1B (CTRL-[) is mapped to KEY_ESCAPE
*
* In all of these cases, the original ASCII code will be reported
* by unicodeKey(). As a special case, if 0x0D is immediately followed
* by 0x0A (that is, CRLF) then KEY_RETURN will be reported only once
* with unicodeKey() set to 0x0D. This ensures that all line ending
* types are mapped to a single KEY_RETURN report.
*
* If the window size has changed due to a remote event, then KEY_WINSIZE
* will be returned. This can allow the caller to clear and redraw the
* window in the new size.
*
* \sa unicodeKey(), read()
*/
int Terminal::readKey()
{
int ch;
// Bail out if there is no underlying stream.
if (!_stream)
return -1;
// Read the next character and bail out if nothing yet. Some special
// peek-ahead handling is needed just after the ESC character.
if (state == STATE_ESC) {
ch = _stream->peek();
if (ch < 0) {
// We just saw an ESC. If there has been a timeout
// then the key is KEY_ESC rather than the start of a
// VT100 escape sequence.
if ((millis() - timer) >= ESC_TIMEOUT_MS) {
state = STATE_INIT;
ucode = 0x1B;
return KEY_ESC;
}
ucode = -1;
return -1;
} else if (!escapeSequenceStart(ch)) {
// The next character is not legitimate as the start of
// an escape sequence, so the ESC must have been KEY_ESC.
state = STATE_INIT;
ucode = 0x1B;
return KEY_ESC;
} else {
// Part of an escape sequence. Read the character properly.
ch = _stream->read();
}
} else {
// Read the next character without any peek-ahead.
ch = _stream->read();
}
if (ch < 0) {
if (state == STATE_MATCH && (millis() - timer) >= SEQ_TIMEOUT_MS) {
// Timeout while waiting for the next character in an
// escape sequence. Abort and return to the initial state.
state = STATE_INIT;
}
ucode = -1;
return -1;
}
// Determine what to do based on the key recognition state.
switch (state) {
case STATE_CR:
// We just saw a CR, so check for CRLF and eat the LF.
state = STATE_INIT;
if (ch == 0x0A) {
ucode = -1;
return -1;
} else if (ch == 0x00 && mod == Telnet) {
// In telnet mode, CR NUL is a literal carriage return,
// separate from the newline sequence CRLF. Eat the NUL.
// We already reported KEY_RETURN for the CR character.
ucode = -1;
return -1;
}
// Fall through to the next case.
case STATE_INIT:
if (ch >= 0x20 && ch <= 0x7E) {
// Printable ASCII character.
state = STATE_INIT;
ucode = ch;
return ch;
} else if (ch == 0x1B) {
// Start of an escape sequence, or the escape character itself.
state = STATE_ESC;
timer = millis();
} else if (ch == 0x0D) {
// CR which may be followed by an LF.
state = STATE_CR;
ucode = ch;
return KEY_RETURN;
} else if (ch == 0x0A) {
// LF on its own without a preceding CR.
ucode = ch;
return KEY_RETURN;
} else if (ch == 0x08 || ch == 0x7F) {
// Backspace or DEL character.
state = STATE_INIT;
ucode = ch;
return KEY_BACKSPACE;
} else if (ch == 0x09) {
// TAB character.
state = STATE_INIT;
ucode = ch;
return KEY_TAB;
} else if (ch < 0x80) {
// Some other ASCII control character.
state = STATE_INIT;
ucode = ch;
return ch;
} else if (ch >= 0xC1 && ch <= 0xDF) {
// Two-byte UTF-8 sequence.
offset = ch & 0x1F;
utf8len = 2;
state = STATE_UTF8;
} else if (ch >= 0xE1 && ch <= 0xEF) {
// Three-byte UTF-8 sequence.
offset = ch & 0x0F;
utf8len = 3;
state = STATE_UTF8;
} else if (ch >= 0xF1 && ch <= 0xF7) {
// Four-byte UTF-8 sequence.
offset = ch & 0x07;
utf8len = 4;
state = STATE_UTF8;
} else if (ch == 0xFF && mod == Telnet) {
// Start of a telnet command (IAC byte).
state = STATE_IAC;
}
break;
case STATE_ESC:
// Next character just after the ESC. Start the escape
// sequence matching engine at offset zero in the keymap table.
state = STATE_MATCH;
offset = 0;
// Fall through to the next case.
case STATE_MATCH:
// In the middle of matching an escape sequence.
if (ch == 0x1B) {
// ESC character seen in the middle of an escape sequence.
// The previous escape sequence is invalid so abort and restart.
state = STATE_ESC;
timer = millis();
break;
}
ch = matchEscape(ch);
if (ch == -1) {
// Need more characters before knowing what this is.
timer = millis();
} else if (ch == -2) {
// Invalid escape sequence so abort and restart.
state = STATE_INIT;
} else if (ch < 0x80) {
// Escape sequence corresponds to a normal ASCII character.
state = STATE_INIT;
ucode = ch;
return ch;
} else {
// Extended keycode for an arrow or function key.
state = STATE_INIT;
ucode = -1;
return ch;
}
break;
case STATE_UTF8:
// Recognize a multi-byte UTF-8 character encoding.
if ((ch & 0xC0) == 0x80) {
if (utf8len <= 2) {
// Final character in the sequence.
ucode = (((long)offset) << 6) | (ch & 0x3F);
state = STATE_INIT;
if (ucode > 0x10FFFFL)
break; // The code point is out of range.
return KEY_UNICODE;
} else {
// More characters still yet to come.
--utf8len;
offset = (offset << 6) | (ch & 0x3F);
}
} else {
// This character is invalid as part of a UTF-8 sequence.
state = STATE_INIT;
}
break;
case STATE_IAC:
// Telnet command byte just after an IAC (0xFF) character.
switch (ch) {
case TelnetDefs::EndOfFile:
// Convert EOF into CTRL-D.
state = STATE_INIT;
ucode = 0x04;
return 0x04;
case TelnetDefs::EndOfRecord:
// Convert end of record markers into CR.
state = STATE_INIT;
ucode = 0x0D;
return KEY_RETURN;
case TelnetDefs::Interrupt:
// Convert interrupt into CTRL-C.
state = STATE_INIT;
ucode = 0x03;
return 0x03;
case TelnetDefs::EraseChar:
// Convert erase character into DEL.
state = STATE_INIT;
ucode = 0x7F;
return KEY_BACKSPACE;
case TelnetDefs::EraseLine:
// Convert erase line into CTRL-U.
state = STATE_INIT;
ucode = 0x15;
return 0x15;
case TelnetDefs::SubStart:
// Option sub-negotiation.
utf8len = 0;
state = STATE_SB;
break;
case TelnetDefs::WILL:
// Option negotiation, WILL command.
state = STATE_WILL;
break;
case TelnetDefs::WONT:
// Option negotiation, WONT command.
state = STATE_WONT;
break;
case TelnetDefs::DO:
// Option negotiation, DO command.
state = STATE_DO;
break;
case TelnetDefs::DONT:
// Option negotiation, DONT command.
state = STATE_DONT;
break;
case TelnetDefs::IAC:
// IAC followed by IAC is the literal byte 0xFF,
// but that isn't valid UTF-8 so we just drop it.
state = STATE_INIT;
break;
default:
// Everything else is treated as a NOP.
state = STATE_INIT;
break;
}
break;
case STATE_WILL:
// Telnet option negotiation, WILL command. Note: We don't do any
// loop detection. We assume that the client will eventually break
// the loop as it probably has more memory than us to store state.
if (ch == TelnetDefs::WindowSize ||
ch == TelnetDefs::RemoteFlowControl) {
// Send a DO command in response - we accept this option.
telnetCommand(TelnetDefs::DO, ch);
} else {
// Send a DONT command in response - we don't accept this option.
telnetCommand(TelnetDefs::DONT, ch);
}
if (!(flags & 0x01)) {
// The first time we see a WILL command from the client we
// send a request back saying that we will handle echoing.
flags |= 0x01;
telnetCommand(TelnetDefs::WILL, TelnetDefs::Echo);
}
state = STATE_INIT;
break;
case STATE_WONT:
case STATE_DONT:
// Telnet option negotiation, WONT/DONT command. The other side
// is telling us that it does not understand this option or wants
// us to stop using it. For now there is nothing to do.
state = STATE_INIT;
break;
case STATE_DO:
// Telnet option negotiation, DO command. Note: Other than Echo
// we don't do any loop detection. We assume that the client will
// break the loop as it probably has more memory than us to store state.
if (ch == TelnetDefs::Echo) {
// Special handling needed for Echo - don't say WILL again
// when the client acknowledges us with a DO command.
} else if (ch == TelnetDefs::SuppressGoAhead) {
// Send a WILL command in response - we accept this option.
telnetCommand(TelnetDefs::WILL, ch);
} else {
// Send a WONT command in response - we don't accept this option.
telnetCommand(TelnetDefs::WONT, ch);
}
state = STATE_INIT;
break;
case STATE_SB:
// Telnet option sub-negotiation. Collect up all bytes and
// then execute the option once "IAC SubEnd" is seen.
if (ch == TelnetDefs::IAC) {
// IAC byte, which will be followed by either IAC or SubEnd.
state = STATE_SB_IAC;
break;
}
if (utf8len < sizeof(sb))
sb[utf8len++] = ch;
break;
case STATE_SB_IAC:
// Telnet option sub-negotiation, byte after IAC.
if (ch == TelnetDefs::IAC) {
// Two IAC bytes in a row is a single escaped 0xFF byte.
if (utf8len < sizeof(sb))
sb[utf8len++] = 0xFF;
state = STATE_SB;
break;
} else if (ch == TelnetDefs::SubEnd) {
// End of the sub-negotiation field. Handle window size changes.
if (utf8len >= 5 && sb[0] == TelnetDefs::WindowSize) {
int width = (((int)(sb[1])) << 8) | sb[2];
int height = (((int)(sb[3])) << 8) | sb[4];
if (!width) // Zero width or height means "unspecified".
width = ncols;
if (!height)
height = nrows;
if (setWindowSize(width, height)) {
// The window size has changed; notify the caller.
ucode = -1;
state = STATE_INIT;
return KEY_WINSIZE;
}
}
}
state = STATE_INIT;
break;
}
// If we get here, then we're still waiting for a full sequence.
ucode = -1;
return -1;
}
/**
* \fn long Terminal::unicodeKey() const
* \brief Gets the Unicode version of the last key returned by readKey().
*
* If readKey() returned an ASCII character (0x00 to 0x7F) or KEY_UNICODE,
* then this function can be used to query the full Unicode code point for
* the key that was typed. If some other key is typed, or no key was
* typed, then this function will return -1.
*
* Unicode code points range between 0 and 0x10FFFF.
*
* \sa readKey(), writeUnicode()
*/
/**
* \brief Writes a Unicode code point to the output in UTF-8 encoding.
*
* \param code The code point to be written between 0 and 0x10FFFF.
* \return The number of bytes that were written to the underlying stream
* to represent \a code. Returns zero if \a code is not a valid code point.
*
* This function is useful when a specific Unicode character is desired;
* for example the code point 0x2264 corresponds to the less than or
* equal to operator "≤". See the Unicode standard for more information.
*
* Unicode characters between 0x00 and 0x7F and strings that are already
* in the UTF-8 encoding can also be written using write().
*
* \sa write()
*/
size_t Terminal::writeUnicode(long code)
{
uint8_t utf8[4];
size_t size = utf8Format(utf8, code);
if (size > 0)
write(utf8, size);
return size;
}
/**
* \fn int Terminal::columns() const
* \brief Gets the number of columns in the window; defaults to 80.
*
* \sa rows(), setWindowSize(), cursorMove()
*/
/**
* \fn int Terminal::rows() const
* \brief Gets the number of rows in the window; defaults to 24.
*
* \sa columns(), setWindowSize(), cursorMove()
*/
/**
* \brief Sets the number of columns and rows in the window.
*
* \param columns The number of columns between 1 and 10000.
* \param rows The number of rows between 1 and 10000.
*
* \return Returns true if the window size has changed.
*
* This function should be used if the application has some information
* about the actual window size. For serial ports, this usually isn't
* available but telnet and ssh sessions can get the window size from
* the remote host.
*
* The window size defaults to 80x24 which is the standard default for
* terminal programs like PuTTY that emulate a VT100.
*
* If the window size changes due to a remote event, readKey() will
* return KEY_WINSIZE to inform the application.
*
* \sa columns(), rows(), readKey()
*/
bool Terminal::setWindowSize(int columns, int rows)
{
// Sanity-check the range first.
if (columns < 1)
columns = 1;
else if (columns > 10000)
columns = 10000;
if (rows < 1)
rows = 1;
else if (rows > 10000)
rows = 10000;
if (ncols != columns || nrows != rows) {
ncols = columns;
nrows = rows;
return true;
} else {
return false;
}
}
/**
* \brief Move the cursor to the top-left position and clear the screen.
*/
void Terminal::clear()
{
static char const escape[] PROGMEM = "\033[H\033[J";
writeProgMem(escape);
}
/**
* \brief Clears from the current cursor position to the end of the line.
*/
void Terminal::clearToEOL()
{
static char const escape[] PROGMEM = "\033[K";
writeProgMem(escape);
}
// Writes a decimal number to a buffer.
static void writeNumber(uint8_t *buf, uint8_t &posn, int value)
{
int divisor = 10000;
bool haveDigits = false;
while (divisor >= 1) {
int digit = value / divisor;
if (digit || haveDigits) {
buf[posn++] = '0' + digit;
haveDigits = true;
}
value %= divisor;
divisor /= 10;
}
if (!haveDigits) {
buf[posn++] = '0';
}
}
/**
* \brief Moves the cursor to a specific location in the window.
*
* \param x The x position for the cursor between 0 and columns() - 1.
* \param y The y position for the cursor between 0 and rows() - 1.
*
* \sa cursorLeft(), columns(), rows(), setWindowSize()
*/
void Terminal::cursorMove(int x, int y)
{
if (!_stream)
return;
// Range check the arguments.
if (x < 0)
x = 0;
else if (x >= ncols)
x = ncols - 1;
if (y < 0)
y = 0;
else if (y >= nrows)
y = nrows - 1;
// Format the command "ESC[row;colH" and send it.
uint8_t buffer[16];
uint8_t posn = 0;
buffer[posn++] = 0x1B;
buffer[posn++] = '[';
writeNumber(buffer, posn, y + 1);
buffer[posn++] = ';';
writeNumber(buffer, posn, x + 1);
buffer[posn++] = 'H';
_stream->write(buffer, posn);
}
/**
* \brief Moves the cursor left by one character.
*
* \sa cursorRight(), cursorUp(), cursorDown(), cursorMove()
*/
void Terminal::cursorLeft()
{
static char const escape[] PROGMEM = "\033[D";
writeProgMem(escape);
}
/**
* \brief Moves the cursor right by one character.
*
* \sa cursorLeft(), cursorUp(), cursorDown(), cursorMove()
*/
void Terminal::cursorRight()
{
static char const escape[] PROGMEM = "\033[C";
writeProgMem(escape);
}
/**
* \brief Moves the cursor up by one line.
*
* \sa cursorDown(), cursorLeft(), cursorRight(), cursorMove()
*/
void Terminal::cursorUp()
{
static char const escape[] PROGMEM = "\033[A";
writeProgMem(escape);
}
/**
* \brief Moves the cursor down by one line.
*
* \sa cursorUp(), cursorLeft(), cursorRight(), cursorMove()
*/
void Terminal::cursorDown()
{
static char const escape[] PROGMEM = "\033[B";
writeProgMem(escape);
}
/**
* \brief Backspaces over the last character.
*
* This function prints CTRL-H, a space, and another CTRL-H to backspace
* over and erase the last character on the current line.
*
* If the last character was a wide Unicode character, then two calls to
* this function are required to erase it. See isWideCharacter() for
* more information.
*
* \sa isWideCharacter()
*/
void Terminal::backspace()
{
static char const escape[] PROGMEM = "\b \b";
writeProgMem(escape);
}
/**
* \brief Inserts a line at the cursor position.
*
* \sa insertChar(), deleteLine()
*/
void Terminal::insertLine()
{
static char const escape[] PROGMEM = "\033[L";
writeProgMem(escape);
}
/**
* \brief Inserts a blank character at the cursor position.
*
* \sa insertLine(), deleteChar()
*/
void Terminal::insertChar()
{
static char const escape[] PROGMEM = "\033[@";
writeProgMem(escape);
}
/**
* \brief Deletes a line at the cursor position.
*
* \sa deleteChar(), insertLine()
*/
void Terminal::deleteLine()
{
static char const escape[] PROGMEM = "\033[M";
writeProgMem(escape);
}
/**
* \brief Deletes the character at the cursor position.
*
* \sa deleteLine(), insertChar()
*/
void Terminal::deleteChar()
{
static char const escape[] PROGMEM = "\033[P";
writeProgMem(escape);
}
/**
* \brief Scrolls the contents of the window up one line.
*
* \sa scrollDown()
*/
void Terminal::scrollUp()
{
static char const escape[] PROGMEM = "\033[S";
writeProgMem(escape);
}
/**
* \brief Scrolls the contents of the window down one line.
*
* \sa scrollUp()
*/
void Terminal::scrollDown()
{
static char const escape[] PROGMEM = "\033[T";
writeProgMem(escape);
}
/**
* \brief Selects normal text with all attributes and colors off.
*
* \sa color(), bold(), underline(), blink(), reverse()
*/
void Terminal::normal()
{
static char const escape[] PROGMEM = "\033[0m";
writeProgMem(escape);
}
/**
* \brief Enables bold text.
*
* \sa normal()
*/
void Terminal::bold()
{
static char const escape[] PROGMEM = "\033[1m";
writeProgMem(escape);
}
/**
* \brief Enables underlined text.
*/
void Terminal::underline()
{
static char const escape[] PROGMEM = "\033[4m";
writeProgMem(escape);
}
/**
* \brief Enables blinking text.
*/
void Terminal::blink()
{
static char const escape[] PROGMEM = "\033[5m";
writeProgMem(escape);
}
/**
* \brief Reverse the foreground and background colors for inverted text.
*/
void Terminal::reverse()
{
static char const escape[] PROGMEM = "\033[7m";
writeProgMem(escape);
}
/**
* \enum Terminal::Color
* \brief Terminal foreground or background colors.
*/
/**
* \var Terminal::Black
* \brief Color is black.
*/
/**
* \var Terminal::DarkRed
* \brief Color is dark red.
*/
/**
* \var Terminal::DarkGreen
* \brief Color is dark green.
*/
/**
* \var Terminal::DarkYellow
* \brief Color is dark yellow.
*/
/**
* \var Terminal::DarkBlue
* \brief Color is dark blue.
*/
/**
* \var Terminal::DarkMagenta
* \brief Color is dark magenta.
*/
/**
* \var Terminal::DarkCyan
* \brief Color is dark cyan.
*/
/**
* \var Terminal::LightGray
* \brief Color is light gray.
*/
/**
* \var Terminal::DarkGray
* \brief Color is dark gray.
*/
/**
* \var Terminal::Red
* \brief Color is light red.
*/
/**
* \var Terminal::Green
* \brief Color is light green.
*/
/**
* \var Terminal::Yellow
* \brief Color is light yellow.
*/
/**
* \var Terminal::Blue
* \brief Color is light blue.
*/
/**
* \var Terminal::Magenta
* \brief Color is light magenta.
*/
/**
* \var Terminal::Cyan
* \brief Color is light cyan.
*/
/**
* \var Terminal::White
* \brief Color is white.
*/
/**
* \brief Selects a text foreground color with the default background color.
*
* \param fg The foreground color to select.
*
* All other text attributes (reverse, underline, etc) are disabled.
*
* The following example displays a warning string with the initial
* word in red and all following words in normal text:
*
* \code
* term.color(Terminal::Red);
* term.print("WARNING: ");
* term.normal();
* term.println("All files on the SD card will be deleted!");
* \endcode
*
* \sa normal()
*/
void Terminal::color(Color fg)
{
uint8_t code = (fg & 0x07);
uint8_t bold = (fg & 0x08) ? 1 : 0;
if (!_stream)
return;
uint8_t buffer[16];
uint8_t posn = 0;
buffer[posn++] = 0x1B;
buffer[posn++] = '[';
buffer[posn++] = '0'; // reset all attributes first
buffer[posn++] = ';';
buffer[posn++] = '3';
buffer[posn++] = '0' + code;
if (bold) {
buffer[posn++] = ';';
buffer[posn++] = '1';
}
buffer[posn++] = 'm';
_stream->write(buffer, posn);
}
/**
* \brief Selects text foreground and background colors.
*
* \param fg The foreground color to select.
* \param bg The background color to select.
*
* All other text attributes (reverse, underline, etc) are disabled.
*
* \sa normal()
*/
void Terminal::color(Color fg, Color bg)
{
uint8_t codefg = (fg & 0x07);
uint8_t boldfg = (fg & 0x08) ? 1 : 0;
uint8_t codebg = (bg & 0x07);
if (!_stream)
return;
uint8_t buffer[16];
uint8_t posn = 0;
buffer[posn++] = 0x1B;
buffer[posn++] = '[';
buffer[posn++] = '0'; // reset all attributes first
buffer[posn++] = ';';
buffer[posn++] = '3';
buffer[posn++] = '0' + codefg;
if (boldfg) {
buffer[posn++] = ';';
buffer[posn++] = '1';
}
buffer[posn++] = ';';
buffer[posn++] = '4';
buffer[posn++] = '0' + codebg;
buffer[posn++] = 'm';
_stream->write(buffer, posn);
}
/**
* \brief Determine if a Unicode character is wide.
*
* \param code The code point for the Unicode character.
* \return Returns true if \a code is a wide character, false otherwise.
*
* Wide characters typically come from East Asian languages and occupy
* two spaces in a terminal. Two calls to backspace() are required to
* erase such characters.
*
* References: http://www.unicode.org/reports/tr11/,
* http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt
*
* \sa backspace(), writeUnicode()
*/
bool Terminal::isWideCharacter(long code)
{
// This function was automatically generated by genwcwidth.c
static unsigned char const range3000[32] PROGMEM = {
0xF1, 0xFF, 0xF3, 0x3F, 0x01, 0x00, 0x01, 0x78,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88
};
static unsigned char const rangeFE00[64] PROGMEM = {
0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xE1, 0xFF,
0x9F, 0x01, 0x00, 0x7F, 0x0C, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8,
0x01, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00
};
unsigned c;
if (code < 0x2300) {
return false;
} else if (code >= 0x3000 && code <= 0x30FF) {
c = (unsigned)(code - 0x3000);
return (pgm_read_byte(range3000 + (c / 8)) & (1 << (c % 8))) != 0;
} else if (code >= 0xFE00 && code <= 0xFFFF) {
c = (unsigned)(code - 0xFE00);
return (pgm_read_byte(rangeFE00 + (c / 8)) & (1 << (c % 8))) != 0;
} else if (code >= 0x3400 && code <= 0x4DBF) {
return true;
} else if (code >= 0x4E00 && code <= 0x9FFF) {
return true;
} else if (code >= 0xF900 && code <= 0xFAFF) {
return true;
} else if (code >= 0x20000 && code <= 0x2FFFD) {
return true;
} else if (code >= 0x30000 && code <= 0x3FFFD) {
return true;
} else if (code == 0x2329 ||
code == 0x232A ||
code == 0x3250 ||
code == 0xA015) {
return true;
}
return false;
}
/**
* \brief Determines the length of a Unicode code point in the UTF-8 encoding.
*
* \param code The code point to be written between 0 and 0x10FFFF.
* \return The number of bytes that makes up the UTF-8 encoding of \a code.
* Returns zero if \a code is not a valid code point.
*
* \sa utf8Format(), writeUnicode()
*/
size_t Terminal::utf8Length(long code)
{
// Reference: https://tools.ietf.org/html/rfc3629
if (code < 0) {
return 0;
} else if (code <= 0x7FL) {
return 1;
} else if (code <= 0x07FFL) {
return 2;
} else if (code >= 0xD800L && code <= 0xDFFF) {
// UTF-16 surrogate pairs are not valid in UTF-8.
return 0;
} else if (code <= 0xFFFFL) {
return 3;
} else if (code <= 0x10FFFFL) {
return 4;
} else {
return 0;
}
}
/**
* \brief Formats a Unicode code point in a buffer in the UTF-8 encoding.
*
* \param buffer The buffer to write the UTF-8 encoding to. At most 4
* bytes will be written to this buffer.
* \param code The code point to be written between 0 and 0x10FFFF.
* \return The number of bytes that were written to \a buffer to represent
* \a code. Returns zero if \a code is not a valid code point.
*
* \sa utf8Length(), writeUnicode()
*/
size_t Terminal::utf8Format(uint8_t *buffer, long code)
{
// Reference: https://tools.ietf.org/html/rfc3629
if (code < 0) {
return 0;
} else if (code <= 0x7FL) {
buffer[0] = (uint8_t)code;
return 1;
} else if (code <= 0x07FFL) {
buffer[0] = 0xC0 | (uint8_t)(code >> 6);
buffer[1] = 0x80 | (((uint8_t)code) & 0x3F);
return 2;
} else if (code >= 0xD800L && code <= 0xDFFF) {
// UTF-16 surrogate pairs are not valid in UTF-8.
return 0;
} else if (code <= 0xFFFFL) {
buffer[0] = 0xE0 | (uint8_t)(code >> 12);
buffer[1] = 0x80 | (((uint8_t)(code >> 6)) & 0x3F);
buffer[2] = 0x80 | (((uint8_t)code) & 0x3F);
return 3;
} else if (code <= 0x10FFFFL) {
buffer[0] = 0xF0 | (uint8_t)(code >> 18);
buffer[1] = 0x80 | (((uint8_t)(code >> 12)) & 0x3F);
buffer[2] = 0x80 | (((uint8_t)(code >> 6)) & 0x3F);
buffer[3] = 0x80 | (((uint8_t)code) & 0x3F);
return 4;
} else {
return 0;
}
}
// Keymap rule table. Compact representation of a recognition tree.
// Each tree node is an array of entries of the following forms:
// 0 End of this tree level.
// ch code Leaf node: ASCII character (bit 7 clear) plus 8-bit keycode.
// ch offset Interior node: ASCII character with the high bit set
// plus a 16-bit offset to the first child node.
// This table was generated with the "genkeymap" tool. Do not edit this
// table but rather edit the tool and rebuild the table from it.
static uint8_t const keymap[459] PROGMEM = {
0xDB, 0x1A, 0x00, 0xCF, 0x57, 0x01, 0x41, 0xDA, 0x42, 0xD9, 0x43, 0xD7,
0x44, 0xD8, 0xBF, 0xA2, 0x01, 0x50, 0xC2, 0x51, 0xC3, 0x52, 0xC4, 0x53,
0xC5, 0x00, 0x41, 0xDA, 0x42, 0xD9, 0x43, 0xD7, 0x44, 0xD8, 0x48, 0xD2,
0xB1, 0x42, 0x00, 0x46, 0xD5, 0xB4, 0xC9, 0x00, 0xB2, 0xCC, 0x00, 0xB3,
0x2B, 0x01, 0xB5, 0x46, 0x01, 0xB6, 0x49, 0x01, 0xDB, 0x4C, 0x01, 0x5A,
0x0B, 0x50, 0xD0, 0x47, 0xE5, 0x00, 0x7E, 0xD2, 0xB1, 0x5D, 0x00, 0xB2,
0x6C, 0x00, 0xB3, 0x7B, 0x00, 0xB4, 0x88, 0x00, 0xB5, 0x95, 0x00, 0xB7,
0xA2, 0x00, 0xB8, 0xAF, 0x00, 0xB9, 0xBC, 0x00, 0x00, 0x7E, 0xC2, 0xBB,
0x65, 0x00, 0x5E, 0xFA, 0x00, 0xB2, 0x69, 0x00, 0x00, 0x7E, 0xF0, 0x00,
0x7E, 0xC3, 0xBB, 0x74, 0x00, 0x5E, 0xFB, 0x00, 0xB2, 0x78, 0x00, 0x00,
0x7E, 0xF1, 0x00, 0x7E, 0xC4, 0xBB, 0x81, 0x00, 0x00, 0xB2, 0x85, 0x00,
0x00, 0x7E, 0xF2, 0x00, 0x7E, 0xC5, 0xBB, 0x8E, 0x00, 0x00, 0xB2, 0x92,
0x00, 0x00, 0x7E, 0xF3, 0x00, 0x7E, 0xC6, 0xBB, 0x9B, 0x00, 0x00, 0xB2,
0x9F, 0x00, 0x00, 0x7E, 0xF4, 0x00, 0x7E, 0xC7, 0xBB, 0xA8, 0x00, 0x00,
0xB2, 0xAC, 0x00, 0x00, 0x7E, 0xF5, 0x00, 0x7E, 0xC8, 0xBB, 0xB5, 0x00,
0x00, 0xB2, 0xB9, 0x00, 0x00, 0x7E, 0xF6, 0x00, 0x7E, 0xC9, 0xBB, 0xC2,
0x00, 0x00, 0xB2, 0xC6, 0x00, 0x00, 0x7E, 0xF7, 0x00, 0x7E, 0xD5, 0x00,
0x7E, 0xD1, 0xB0, 0xE7, 0x00, 0xB1, 0xF4, 0x00, 0xB3, 0x01, 0x01, 0xB4,
0x10, 0x01, 0xB5, 0x1F, 0x01, 0xB6, 0x22, 0x01, 0xB8, 0x25, 0x01, 0xB9,
0x28, 0x01, 0x00, 0x7E, 0xCA, 0xBB, 0xED, 0x00, 0x00, 0xB2, 0xF1, 0x00,
0x00, 0x7E, 0xF8, 0x00, 0x7E, 0xCB, 0xBB, 0xFA, 0x00, 0x00, 0xB2, 0xFE,
0x00, 0x00, 0x7E, 0xF9, 0x00, 0x7E, 0xCC, 0x24, 0xF8, 0xBB, 0x09, 0x01,
0x00, 0xB2, 0x0D, 0x01, 0x00, 0x7E, 0xFA, 0x00, 0x7E, 0xCD, 0x24, 0xF9,
0xBB, 0x18, 0x01, 0x00, 0xB2, 0x1C, 0x01, 0x00, 0x7E, 0xFB, 0x00, 0x7E,
0xF0, 0x00, 0x7E, 0xF1, 0x00, 0x7E, 0xF2, 0x00, 0x7E, 0xF3, 0x00, 0x7E,
0xD4, 0xB1, 0x3A, 0x01, 0xB2, 0x3D, 0x01, 0xB3, 0x40, 0x01, 0xB4, 0x43,
0x01, 0x00, 0x7E, 0xF4, 0x00, 0x7E, 0xF5, 0x00, 0x7E, 0xF6, 0x00, 0x7E,
0xF7, 0x00, 0x7E, 0xD3, 0x00, 0x7E, 0xD6, 0x00, 0x41, 0xC2, 0x42, 0xC3,
0x43, 0xC4, 0x44, 0xC5, 0x45, 0xC6, 0x00, 0x41, 0xDA, 0x42, 0xD9, 0x43,
0xD7, 0x44, 0xD8, 0x48, 0xD2, 0x46, 0xD5, 0x20, 0x20, 0x49, 0xB3, 0x4D,
0xB0, 0x6A, 0x2A, 0x6B, 0x2B, 0x6C, 0x2C, 0x6D, 0x2D, 0x6E, 0x2E, 0x6F,
0x2F, 0x70, 0x30, 0x71, 0x31, 0x72, 0x32, 0x73, 0x33, 0x74, 0x34, 0x75,
0x35, 0x76, 0x36, 0x77, 0x37, 0x78, 0x38, 0x79, 0x39, 0x58, 0x3D, 0x50,
0xC2, 0x51, 0xC3, 0x52, 0xC4, 0x53, 0xC5, 0xB2, 0x99, 0x01, 0x5A, 0x0B,
0x00, 0x50, 0xF0, 0x51, 0xF1, 0x52, 0xF2, 0x53, 0xF3, 0x00, 0x20, 0x20,
0x49, 0xB3, 0x4D, 0xB0, 0x6A, 0x2A, 0x6B, 0x2B, 0x6C, 0x2C, 0x6D, 0x2D,
0x6E, 0x2E, 0x6F, 0x2F, 0x70, 0x30, 0x71, 0x31, 0x72, 0x32, 0x73, 0x33,
0x74, 0x34, 0x75, 0x35, 0x76, 0x36, 0x77, 0x37, 0x78, 0x38, 0x79, 0x39,
0x58, 0x3D, 0x00
};
/**
* \brief Matches the next character in an escape sequence.
*
* \param ch The next character to match.
* \return -1 if more characters are required, -2 if the escape sequence
* is invalid, or a positive key code if the match is complete.
*/
int Terminal::matchEscape(int ch)
{
uint8_t kch;
for (;;) {
kch = pgm_read_byte(keymap + offset);
if (!kch) {
// No match at this level, so the escape sequence is invalid.
break;
} else if (kch & 0x80) {
// Interior node.
if ((kch & 0x7F) == ch) {
// Interior node matches. Go down one tree level.
offset = ((int)(pgm_read_byte(keymap + offset + 1))) |
(((int)(pgm_read_byte(keymap + offset + 2))) << 8);
return -1;
}
offset += 3;
} else {
// Leaf node.
if (kch == (uint8_t)ch) {
// We have found a match on a full escape sequence.
return pgm_read_byte(keymap + offset + 1);
}
offset += 2;
}
}
return -2;
}
/**
* \brief Sends a telnet command to the client.
*
* \param type The type of command: WILL, WONT, DO, or DONT.
* \param option The telnet option the command applies to.
*/
void Terminal::telnetCommand(uint8_t type, uint8_t option)
{
uint8_t buf[3];
buf[0] = (uint8_t)TelnetDefs::IAC;
buf[1] = type;
buf[2] = option;
_stream->write(buf, 3);
}