SSH client: a terminal on another machine (#2)
CI / build (pull_request) Successful in 2m22s
Site / build (pull_request) Successful in 10s

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:
2026-10-08 04:38:36 +02:00
co-authored by Claude Opus 5.5
parent 7223147f26
commit 6b71aace4f
47 changed files with 2801 additions and 20 deletions
+30
View File
@@ -231,6 +231,35 @@ void test_fn_h_is_help_in_both_modes() {
}
}
// Issue #2: a key that isn't a character says what was held with it. A terminal sends Page Up for
// Shift with the up arrow, and a note jumps to its start for Ctrl with it.
void test_arrows_enter_and_tab_carry_what_was_held() {
KeyMapper m;
m.setTextEntry(true);
RawKeys r = withFn({';'});
r.shift = true;
auto ev = press(m, r);
TEST_ASSERT_EQUAL(static_cast<int>(Key::Up), static_cast<int>(ev[0].key));
TEST_ASSERT_TRUE(ev[0].shift);
TEST_ASSERT_FALSE(ev[0].ctrl);
KeyMapper m2;
RawKeys tab;
tab.tab = true;
tab.shift = true;
ev = press(m2, tab);
TEST_ASSERT_EQUAL(static_cast<int>(Key::Tab), static_cast<int>(ev[0].key));
TEST_ASSERT_TRUE(ev[0].shift);
KeyMapper m3;
m3.setTextEntry(true);
RawKeys back = chars({'`'});
back.alt = true;
ev = press(m3, back);
TEST_ASSERT_EQUAL(static_cast<int>(Key::Back), static_cast<int>(ev[0].key));
TEST_ASSERT_TRUE(ev[0].alt);
}
// Issue #83: Fn+p everywhere.
void test_fn_p_is_a_screenshot_in_both_modes() {
for (bool typing : {true, false}) {
@@ -294,6 +323,7 @@ int main() {
RUN_TEST(test_other_characters_still_type_when_not_typing);
RUN_TEST(test_shifted_arrow_keys_type_their_symbols_when_not_typing);
RUN_TEST(test_fn_h_is_help_in_both_modes);
RUN_TEST(test_arrows_enter_and_tab_carry_what_was_held);
RUN_TEST(test_fn_p_is_a_screenshot_in_both_modes);
RUN_TEST(test_plain_h_still_types);
RUN_TEST(test_question_mark_is_help_only_when_not_typing);