Public Access
SSH client: a terminal on another machine (#2)
The SSH App opens one session to a shell, over libssh (LibSSH-ESP32 5.10.0) on mbedTLS. A server is trusted the first time on its fingerprint, and a changed key is a warning with Cancel selected. The password is typed each time and kept nowhere; or the device makes itself an Ed25519 key, whose public half is shown, written to /ssh/id_ed25519.pub and printed by `ssh status`. lib/term is the terminal: what a shell, less, top, nano and vim send, with sixteen colours, scroll regions, the alternate screen and 100 lines of scrollback. Five text sizes with Ctrl and + or -, from 60x20 to 26x8, told to the far end. The session goes on when the App is left; SSH shows in the Status Bar. `ssh user@host` in the Shell opens the App. Also: - Keys that aren't characters carry Shift, Ctrl and Alt. The terminal needs it, and it makes Ctrl+Fn+up/down in a note and Shift+Tab in Gemini work from the real keyboard. - IRC doesn't try to connect under 60 KB free: started with a session open, its TLS handshake took the heap down to 236 bytes. - libssh's own curve25519 is left out of the build (scripts/libssh_filter.py): libsodium's has the same names. Costs 292 KB of flash and about 50 KB of heap while a session is open; not started under 75 KB free. Docs: guide page, how-to, FAQ, home page, Status Bar, SD card folders, the memory how-to, README, glossary, N1 notes with Q254 to Q266 and the checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// A terminal's screen (issue #2, N1 Q256): what a remote program's output makes of a grid of
|
||||
// characters. It understands what a shell, `less`, `top`, `nano` and plain `vim` send: the cursor,
|
||||
// erasing, sixteen colours, scroll regions, the alternate screen. No mouse. Characters are kept
|
||||
// as the screen's font has them (Latin-1); what it lacks becomes `?`, and box-drawing lines
|
||||
// become + - |.
|
||||
namespace roro::term {
|
||||
|
||||
struct Cell {
|
||||
uint8_t ch = ' ';
|
||||
uint8_t attr = 0x07; // low four bits: the colour of the character, high four: of what is behind it
|
||||
};
|
||||
|
||||
class Terminal {
|
||||
public:
|
||||
Terminal(int cols, int rows, int historyLines);
|
||||
|
||||
void feed(const uint8_t* data, size_t len);
|
||||
// A new size: what is on screen stays where it is, from the top left; if the cursor would fall
|
||||
// off the bottom, the top lines go to the history.
|
||||
void resize(int cols, int rows);
|
||||
// What the program asked to be told (where the cursor is, what kind of terminal this is).
|
||||
std::function<void(const std::string&)> reply;
|
||||
|
||||
int cols() const { return cols_; }
|
||||
int rows() const { return rows_; }
|
||||
const Cell& cell(int row, int col) const { return grid()[static_cast<size_t>(row * cols_ + col)]; }
|
||||
int cursorRow() const { return row_; }
|
||||
int cursorCol() const { return col_; }
|
||||
bool cursorVisible() const { return cursorShown_; }
|
||||
bool appCursorKeys() const { return appCursor_; } // the arrows are sent another way (vim, less)
|
||||
bool altScreen() const { return alt_; }
|
||||
uint32_t revision() const { return revision_; } // changes whenever the screen may have
|
||||
bool takeBell();
|
||||
|
||||
// Lines that scrolled off the top of the main screen, oldest first; their text only.
|
||||
int historyCount() const { return static_cast<int>(history_.size()); }
|
||||
const std::string& historyLine(int i) const { return history_[static_cast<size_t>(i)]; }
|
||||
|
||||
std::string rowText(int row) const; // without trailing spaces
|
||||
|
||||
private:
|
||||
enum class State { Ground, Esc, Csi, Osc, OscEsc, Charset };
|
||||
|
||||
std::vector<Cell>& grid() { return alt_ ? altGrid_ : mainGrid_; }
|
||||
const std::vector<Cell>& grid() const { return alt_ ? altGrid_ : mainGrid_; }
|
||||
Cell blank() const;
|
||||
void put(uint32_t codePoint);
|
||||
void control(uint8_t c);
|
||||
void escape(uint8_t c);
|
||||
void csi(uint8_t final);
|
||||
void sgr();
|
||||
void mode(bool on);
|
||||
void lineFeed();
|
||||
void reverseIndex();
|
||||
void scrollUp(int top, int bottom, int n, bool toHistory = true);
|
||||
void scrollDown(int top, int bottom, int n);
|
||||
void eraseCells(int row, int from, int to);
|
||||
void moveTo(int row, int col);
|
||||
void reset();
|
||||
int param(size_t i, int fallback) const;
|
||||
|
||||
int cols_, rows_, historyMax_;
|
||||
std::vector<Cell> mainGrid_, altGrid_;
|
||||
std::deque<std::string> history_;
|
||||
bool alt_ = false;
|
||||
int row_ = 0, col_ = 0, top_ = 0, bottom_ = 0;
|
||||
int savedRow_ = 0, savedCol_ = 0;
|
||||
uint8_t savedAttr_ = 0x07;
|
||||
bool wrapPending_ = false, autoWrap_ = true, cursorShown_ = true, appCursor_ = false, lineDrawing_ = false, bell_ = false;
|
||||
uint8_t fg_ = 7, bg_ = 0;
|
||||
bool bold_ = false, inverse_ = false;
|
||||
State state_ = State::Ground;
|
||||
std::vector<int> params_;
|
||||
bool paramStarted_ = false, private_ = false;
|
||||
uint32_t utf8_ = 0;
|
||||
int utf8Left_ = 0;
|
||||
uint32_t revision_ = 0;
|
||||
};
|
||||
|
||||
// What a key sends to the remote program.
|
||||
enum class TermKey { Char, Up, Down, Left, Right, Enter, Backspace, Tab, Escape, PageUp, PageDown };
|
||||
std::string encodeKey(TermKey key, uint32_t ch, bool ctrl, bool alt, bool appCursorKeys);
|
||||
|
||||
// One of the terminal's sixteen colours as red, green and blue.
|
||||
void colourRgb(int index, uint8_t& r, uint8_t& g, uint8_t& b);
|
||||
|
||||
} // namespace roro::term
|
||||
Reference in New Issue
Block a user