Add uname, /uname and scp (key or masked password) in the Shell
CI / build (pull_request) Failing after 1m59s
Site / build (pull_request) Successful in 9s

- uname in the console and /uname in IRC: the firmware, its version and the chip
- scp: one file between the card and a trusted server, over SSH, with the
  device's key or a password asked (masked) in the Shell
- shared libssh helpers in src/platform/libssh_util
- README, user guide, command reference and a how-to updated

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0162FokPdvY2KsS4NBfwyWPk
This commit is contained in:
2026-10-09 19:01:36 +02:00
co-authored by Claude Sonnet 5.5
parent e707a5f2d4
commit 18285c3212
25 changed files with 730 additions and 34 deletions
+32 -1
View File
@@ -33,6 +33,9 @@ void ShellApp::onEnter() {
// Nothing is kept once it's left: the ring, the lines and the list of commands all go (Q207).
void ShellApp::onExit() {
if (password_ && abort) abort();
password_ = false;
input_.setText("");
console.closeShellRing();
console.shellShowsAll(false);
open_ = false;
@@ -49,6 +52,11 @@ void ShellApp::update(uint32_t) {
if (skipped) log_.add("[... " + std::to_string(skipped) + " bytes lost: more was printed than fits]");
log_.feed(reinterpret_cast<const char*>(buf), n);
}
if (!password_ && asking && asking()) { // after the question has been printed above
password_ = true;
input_.setText("");
requestRedraw();
}
if (log_.revision() != seenRevision_) {
seenRevision_ = log_.revision();
requestRedraw();
@@ -114,6 +122,23 @@ bool ShellApp::onKey(const KeyEvent& e) {
log_.add(console.shellShowsAll() ? "Showing everything the console prints." : "Showing only the replies to your commands.");
return true;
}
if (password_) { // masked, kept nowhere: not in the history, not in the log
switch (e.key) {
case Key::Char: input_.insert(e.ch); break;
case Key::Delete: input_.backspace(); break;
case Key::Select: {
std::string typed = input_.text();
input_.setText("");
password_ = false;
log_.add("> " + std::string(typed.size(), '*'));
if (answer) answer(typed);
typed.assign(typed.size(), '\0');
break;
}
default: return false; // Back leaves the Shell, which cancels the copy
}
return true;
}
std::string recalled;
switch (e.key) {
case Key::Char: input_.insert(e.ch); break;
@@ -197,7 +222,13 @@ void ShellApp::draw(Canvas& c) {
}
c.setTextDatum(top_left);
widgets::lineEditor(c, input_, {2, area.y + area.h - inputH, area.w - 4, 0});
if (password_) {
LineEditor stars(240);
stars.setText(std::string(input_.text().size(), '*'));
widgets::lineEditor(c, stars, {2, area.y + area.h - inputH, area.w - 4, 0});
} else {
widgets::lineEditor(c, input_, {2, area.y + area.h - inputH, area.w - 4, 0});
}
if (confirm_) widgets::dialog(c, "Delete?", question_, *confirm_);
}
+7
View File
@@ -36,6 +36,12 @@ class ShellApp : public App {
ShellApp(Run run, Probe probe, List list, Count count, const char* helpText, AppManager& apps)
: run_(std::move(run)), probe_(std::move(probe)), list_(std::move(list)), count_(std::move(count)), helpText_(helpText), apps_(apps) {}
// `scp` asking for a password: while asking() is true, the next line typed is masked and goes to
// answer() instead of the console; leaving the Shell calls abort().
std::function<bool()> asking;
std::function<void(const std::string&)> answer;
std::function<void()> abort;
void onEnter() override;
void onExit() override;
bool onKey(const KeyEvent& e) override;
@@ -63,6 +69,7 @@ class ShellApp : public App {
uint32_t ringPos_ = 0, seenRevision_ = 0;
int scroll_ = 0; // wrapped lines scrolled back from the bottom
bool open_ = false;
bool password_ = false; // the line being typed is a password
};
} // namespace roro