Shell: only its own replies, Apps by their names, and rm as Unix has it (#67)
CI / build (pull_request) Successful in 1m38s
Site / build (pull_request) Successful in 10s

The Shell shows the replies to its own commands and nothing else. The
console knows who each line is printed for (Console::As, Console::origin):
a command run from the Shell prints as the Shell's, and what answers it
later from another task carries that along (ls, tasks, du, cp, update
check, sd list, screenshot, gemini get). Ctrl+b shows everything instead.
This replaces the ten-second window, which was a guess.

An App's name with a capital opens it (Notes, Irc, Wifi, Gnss, Gemini, Lora,
Storage, Shell, System, Settings), from the Shell and from the consoles.

rm needs -r for a folder, here and over the consoles. In the Shell a file,
or a folder with something in it, is asked about unless -f; an empty folder
with -r goes without a word.

The Shell now hands its line to the main loop to run: run from inside the
key handler, rm on a folder overflowed the loop's stack and crashed the
device. `info` says which App is in front.

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-07 11:35:29 +02:00
co-authored by Claude Opus 5.5
parent 3863d28593
commit 7b5df713ad
24 changed files with 414 additions and 124 deletions
+52 -1
View File
@@ -4,6 +4,7 @@
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <algorithm>
#include <cstdio>
@@ -37,6 +38,20 @@ void Console::captureEspLogs() {
namespace {
// The tasks printing As the Shell right now, and how deep each is in it. Under ringLock. Two at
// once is the most there is (the main loop, and the storage task finishing a listing).
struct ShellTask {
TaskHandle_t task;
uint8_t depth;
};
ShellTask shellTasks[4] = {};
bool fromShellLocked(TaskHandle_t task) {
for (auto& t : shellTasks)
if (t.depth && t.task == task) return true;
return false;
}
// One ring: the last kRingBytes written, and how many were written in all. Under ringLock.
bool openOne(uint8_t*& ring, uint32_t& head) {
if (ring) return true;
@@ -97,9 +112,45 @@ void Console::toRing(const uint8_t* data, size_t len) {
data += len - kRingBytes; // only the tail can fit
len = kRingBytes;
}
TaskHandle_t task = xTaskGetCurrentTaskHandle();
portENTER_CRITICAL(&ringLock);
writeOne(ring_, head_, data, len);
writeOne(shellRing_, shellHead_, data, len);
if (shellRing_ && (shellAll_ || fromShellLocked(task))) writeOne(shellRing_, shellHead_, data, len);
portEXIT_CRITICAL(&ringLock);
}
Console::Origin Console::origin() const {
TaskHandle_t task = xTaskGetCurrentTaskHandle();
portENTER_CRITICAL(&ringLock);
bool shell = fromShellLocked(task);
portEXIT_CRITICAL(&ringLock);
return shell ? Origin::Shell : Origin::System;
}
Console::As::As(Origin origin) : entered_(false) {
if (origin != Origin::Shell) return;
TaskHandle_t task = xTaskGetCurrentTaskHandle();
portENTER_CRITICAL(&ringLock);
ShellTask* slot = nullptr;
for (auto& t : shellTasks)
if (t.depth && t.task == task) slot = &t;
if (!slot)
for (auto& t : shellTasks)
if (!t.depth && !slot) slot = &t;
if (slot) {
slot->task = task;
slot->depth++;
entered_ = true;
}
portEXIT_CRITICAL(&ringLock);
}
Console::As::~As() {
if (!entered_) return;
TaskHandle_t task = xTaskGetCurrentTaskHandle();
portENTER_CRITICAL(&ringLock);
for (auto& t : shellTasks)
if (t.depth && t.task == task) t.depth--;
portEXIT_CRITICAL(&ringLock);
}