feat(lua): pass an argument to init() and let apps name themselves

sys.launch(path, arg) carries a string to the next app's init(arg), nil when there is
none. States share no memory, so one string is the whole handoff; anything structured
travels as a Lua literal the receiver loads. This is what a screen split across apps
needs to say "collect a password for this network".

sys.setAppName() retitles the status bar, defaulting to the directory name as before. A
setter rather than a declared constant, so one app can retitle per screen. The bar needs
no new invalidation path for it -- the name joins rotation and theme in the cache key --
and clips a name wide enough to reach the memory slot.
This commit is contained in:
2026-08-02 12:02:09 -04:00
parent 118e04e340
commit 021b94fca2
10 changed files with 129 additions and 28 deletions
+17 -5
View File
@@ -31,6 +31,10 @@ LuaApp app(tft, touch);
bool halted = false;
String nextApp;
// Carried beside the path, because sys.launch()'s argument has to outlive the state that
// passed it: the sender is torn down before the receiver exists.
String nextArg;
bool nextArgSet = false;
// Last resort only: the UI lives in Lua, so this exists purely to explain why
// nothing else could run.
@@ -46,12 +50,12 @@ void fallbackScreen(const char* message) {
halted = true;
}
void startApp(const String& path) {
void startApp(const String& path, const char* arg) {
// Full panel until the app's own status bar module reports its height in load().
tft.setRotation(settings.rotationIndex()); // apps may have rotated the frame
tft.resetViewport(); // load() re-clips once the new app's bar reports its height
Serial.printf("launching %s free=%u largest=%u\n", path.c_str(), ESP.getFreeHeap(), ESP.getMaxAllocHeap());
if (app.load(path.c_str(), path == HOME)) return;
if (app.load(path.c_str(), path == HOME, arg)) return;
if (path == HOME) fallbackScreen("home failed to start");
}
@@ -70,7 +74,7 @@ void setup() {
}
settings.load(); // absent file keeps the built-in defaults
net::begin();
startApp(HOME);
startApp(HOME, nullptr);
}
void loop() {
@@ -83,15 +87,23 @@ void loop() {
if (app.running()) {
app.loop();
if (!app.running()) nextApp = app.takePendingLaunch();
if (!app.running()) {
nextApp = app.takePendingLaunch();
nextArgSet = app.hasPendingArg();
nextArg = app.takePendingArg();
}
} else {
// An app that died left its message on screen, and home is about to paint over it.
// Serial alone is no help to anyone holding the device.
if (app.takeFailure()) delay(ERROR_HOLD_MS);
String path = nextApp.length() > 0 ? nextApp : String(HOME);
bool hasArg = nextApp.length() > 0 && nextArgSet;
String arg = nextArg;
nextApp = "";
startApp(path);
nextArg = "";
nextArgSet = false;
startApp(path, hasArg ? arg.c_str() : nullptr);
}
// Hands the core to the idle task instead of spinning between polls: touch reads are