app-module events & callstack config, crash diagnostics (#630)

- Apps can specify task stack depth and preferred memory placement in their manifests.
- App identifiers are validated against length and character requirements.
- Crash diagnostics now show the crash cause, reason, fault address, call stack, and program-counter details, with logs saved for review.
- App closing is more consistent across built-in screens. There's now a dedicated function, and the old _emit() function is made private.
- Crash diagnostics no longer display a QR code without a call stack.
- Improved memory allocation for unrestricted requests.
This commit is contained in:
Ken Van Hoeylandt
2026-08-27 21:50:12 +02:00
committed by GitHub
parent c656ee9ffd
commit 92ca046681
74 changed files with 654 additions and 381 deletions
+1 -6
View File
@@ -54,12 +54,7 @@ std::vector<std::string> getModelNames() {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onAddGpsPressed(lv_event_t* event) {
@@ -51,13 +51,7 @@ void onButtonPressed(lv_event_t* e) {
auto* btnCtx = static_cast<ButtonContext*>(lv_event_get_user_data(e));
LOG_I(TAG, "Selected item at index %d", (int)btnCtx->index);
btnCtx->ctx->result = btnCtx->index;
// Async, non-blocking - just wakes this dialog's own thread. Must NOT call
// app_manager_stop() here: that bound-waits (thread_join) for the dialog's thread to
// finish, which needs the LVGL lock (window_manager_remove()) - but this callback is
// running ON the LVGL task, which would deadlock against itself. The caller reaps this
// instance via app_manager_stop() after it receives the APP_EVENT_RESULT instead.
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(btnCtx->ctx->appInstanceId, &event);
app_event_emit_close(btnCtx->ctx->appInstanceId);
}
void createButton(Context* ctx, lv_obj_t* parent, const std::string& text, int32_t index) {
@@ -50,12 +50,7 @@ void onPressUninstall(lv_event_t* event) {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void createWidgets(lv_obj_t* parent, void* userData) {
+1 -2
View File
@@ -51,8 +51,7 @@ void refresh(Context* ctx);
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
AppEvent closeEvent {.type = APP_EVENT_CLOSE, .timestamp = 0, .result = {}};
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onAppPressed(lv_event_t* e) {
@@ -63,8 +63,7 @@ uint32_t showConfirmDialog(Context* ctx, const char* action) {
void onBackPressed(lv_event_t* e) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(e));
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onInstallPressed(lv_event_t* e) {
+14 -15
View File
@@ -20,7 +20,9 @@ namespace tt::app::applist {
namespace {
uint32_t appListInstanceId = 0;
struct Context {
uint32_t appInstanceId;
};
void onAppPressed(lv_event_t* e) {
// Fire-and-forget top-level navigation, same as Launcher's own app-launch buttons.
@@ -29,15 +31,9 @@ void onAppPressed(lv_event_t* e) {
app_manager_start(manifest->id, &instanceId);
}
void onBackPressed(lv_event_t*) {
// The global toolbar nav callback (ToolbarConfig.nav_action_callback, set once in
// Tactility.cpp) only knows how to stop old-model apps, so this new-model app overrides
// its own toolbar's nav action to close itself instead. Async, non-blocking - must NOT
// call app_manager_stop() directly here: that bound-waits (thread_join) for this app's
// own thread to finish, which needs the LVGL lock (window_manager_remove()) - but this
// callback runs ON the LVGL task, which would deadlock against itself.
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(appListInstanceId, &event);
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
app_event_emit_close(ctx->appInstanceId);
}
void createAppWidget(const ::AppManifest* manifest, lv_obj_t* list) {
@@ -54,9 +50,11 @@ void collectManifest(const ::AppManifest* manifest, void* context) {
manifests->push_back(manifest);
}
void createWidgets(lv_obj_t* parent, void*) {
void createWidgets(lv_obj_t* parent, void* userData) {
auto* ctx = static_cast<Context*>(userData);
auto* toolbar = lvgl_toolbar_create(parent, "Apps");
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, nullptr);
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t* list = lv_list_create(parent);
@@ -83,7 +81,7 @@ void createWidgets(lv_obj_t* parent, void*) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
appListInstanceId = appInstanceId;
Context ctx { appInstanceId };
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
@@ -91,7 +89,7 @@ int32_t appMain(int argc, char* argv[]) {
AppEventSubscription sub {};
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
@@ -119,8 +117,9 @@ extern const ::AppManifest manifest = {
.id = "tactility.applist",
.name = "Apps",
.category = APP_CATEGORY_SYSTEM,
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
.flags = APP_MANIFEST_FLAG_HIDDEN,
.stack = { .depth = 2400, .desired_memory_capability = 0 },
};
} // namespace
@@ -24,21 +24,18 @@ extern const ::AppManifest manifest;
namespace {
// Set by appMain() right before window_manager_create(), read by onBackPressed().
uint32_t appSettingsInstanceId = 0;
struct Context {
uint32_t appInstanceId;
};
void onAppPressed(lv_event_t* e) {
const auto* target_manifest = static_cast<const ::AppManifest*>(lv_event_get_user_data(e));
appdetails::start(target_manifest->id);
}
void onBackPressed(lv_event_t*) {
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(appSettingsInstanceId, &event);
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
app_event_emit_close(ctx->appInstanceId);
}
void createAppWidget(const ::AppManifest* target_manifest, lv_obj_t* list) {
@@ -55,10 +52,12 @@ void collectManifest(const ::AppManifest* manifest, void* context) {
manifests->push_back(manifest);
}
void createWidgets(lv_obj_t* parent, void*) {
void createWidgets(lv_obj_t* parent, void* userData) {
auto* ctx = static_cast<Context*>(userData);
auto* toolbar = lvgl_toolbar_create(parent, "Installed Apps");
// The global toolbar nav callback only knows how to stop old-model apps.
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, nullptr);
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t* list = lv_list_create(parent);
@@ -92,7 +91,7 @@ void createWidgets(lv_obj_t* parent, void*) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
appSettingsInstanceId = appInstanceId;
Context ctx { appInstanceId };
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
@@ -100,7 +99,7 @@ int32_t appMain(int argc, char* argv[]) {
AppEventSubscription sub {};
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
@@ -132,7 +131,9 @@ extern const ::AppManifest manifest = {
.id = "tactility.appsettings",
.name = "Apps",
.category = APP_CATEGORY_SETTINGS,
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
.flags = 0,
.stack = { .depth = 2400, .desired_memory_capability = 0 },
};
} // namespace
@@ -37,12 +37,7 @@ struct Context {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void createWidgets(lv_obj_t* parent, void* userData) {
@@ -37,12 +37,7 @@ struct Context {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onInputEnabledSwitch(lv_event_t* event) {
+18 -5
View File
@@ -1,3 +1,4 @@
#include "tactility/memory.h"
#include "tactility/system_event.h"
#include <tactility/check.h>
@@ -274,16 +275,27 @@ void runBootSequence(TickType_t startTime) {
}
#endif
if (!setupUsbBootMode()) {
registerApps();
waitForMinimalSplashDuration(startTime);
startNextApp();
if (setupUsbBootMode()) {
// Stay open: the splash's "Return to OS" button is this app's only way to leave mass
// storage mode, so it must not self-close here like the normal boot path does below.
return;
}
registerApps();
waitForMinimalSplashDuration(startTime);
startNextApp();
if (sdCardMissing) {
// Stay open: the error screen's "Reboot" button is this app's only way to leave here.
return;
}
// This event will likely block as other systems are initialized
// e.g. Wi-Fi reads AP configs from SD card
LOG_I(TAG, "Publish event");
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
app_event_emit_close(app_scheduler_current_app_id());
}
int32_t appMain(int argc, char* argv[]) {
@@ -337,8 +349,9 @@ extern const ::AppManifest manifest = {
.id = "tactility.boot",
.name = "Boot",
.category = APP_CATEGORY_SYSTEM,
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
.flags = APP_MANIFEST_FLAG_HIDDEN,
.stack = { .depth = 4096, .desired_memory_capability = MEMORY_CAPABILITY_INTERNAL }
};
} // namespace
+1 -2
View File
@@ -133,8 +133,7 @@ void onBtEvent(Context* ctx, const BtEvent& event) {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void createWidgets(lv_obj_t* parent, void* userData) {
+1 -6
View File
@@ -20,12 +20,7 @@ namespace tt::app::btmanage {
static void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
static void onEnableSwitchChanged(lv_event_t* event) {
@@ -93,12 +93,7 @@ void onPressForget(lv_event_t* event) {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onToggleAutoConnect(lv_event_t* event) {
+1 -6
View File
@@ -148,12 +148,7 @@ void ChatView::createChannelPanel(lv_obj_t* parent) {
void ChatView::onBackPressed(lv_event_t* e) {
auto* self = static_cast<ChatView*>(lv_event_get_user_data(e));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(self->app->appInstanceId, &closeEvent);
app_event_emit_close(self->app->appInstanceId);
}
void ChatView::init(lv_obj_t* parent) {
@@ -1,8 +1,12 @@
#ifdef ESP_PLATFORM
#include "Tactility/PanicHandler.h"
#include <Tactility/app/crashdiagnostics/QrHelpers.h>
#include <Tactility/app/crashdiagnostics/QrUrl.h>
#include <Tactility/app/launcher/Launcher.h>
#include <Tactility/file/File.h>
#include <Tactility/lvgl/Statusbar.h>
#include <app/event.h>
@@ -17,8 +21,19 @@
#include <tactility/check.h>
#include <tactility/drivers/pointer.h>
#include <tactility/log.h>
#include <tactility/paths.h>
#if CONFIG_IDF_TARGET_ARCH_XTENSA
#include <esp_cpu_utils.h>
#else
#include <esp_cpu.h>
#endif
#include <sdkconfig.h>
#include <iomanip>
#include <memory>
#include <sstream>
namespace tt::app::crashdiagnostics {
@@ -41,16 +56,70 @@ struct Context {
};
const char* crashCauseToString(CrashCause cause) {
switch (cause) {
case CrashCause::Debug: return "Debug";
case CrashCause::WatchdogInterrupt: return "Watchdog (interrupt)";
case CrashCause::WatchdogTask: return "Watchdog (task)";
case CrashCause::Abort: return "Abort";
case CrashCause::Fault: return "Fault";
case CrashCause::Unknown:
default: return "Unknown";
}
}
std::string formatCrashData(const CrashData& crashData) {
std::stringstream stream;
stream << "Cause: " << crashCauseToString(crashData.cause) << "\n";
stream << "Reason: ";
if (crashData.reason[0] != '\0') {
stream << crashData.reason;
} else {
stream << "unknown";
}
stream << "\n";
stream << "Fault address: " << std::hex << std::setw(8) << std::setfill('0') << crashData.faultAddress << std::dec << "\n";
stream << "Callstack" << (crashData.callstackCorrupted ? " (corrupted)" : "") << ":";
if (crashData.callstackLength > 0) {
stream << "\n";
for (uint8_t i = 0; i < crashData.callstackLength; i++) {
#if CONFIG_IDF_TARGET_ARCH_XTENSA
uint32_t pc = esp_cpu_process_stack_pc(crashData.callstack[i].pc);
#else
uint32_t pc = crashData.callstack[i].pc; // No processing needed on RISC-V
#endif
stream << std::hex << std::setw(8) << std::setfill('0') << pc << std::dec << " ";
}
} else {
stream << " empty" << "\n";
}
return stream.str();
}
// Best-effort: crash.txt is a convenience for offline inspection, not required for the app to work.
void writeCrashLogFile(const CrashData& crashData) {
char root[128];
if (paths_get_data_path(root, sizeof(root)) != ERROR_NONE) {
LOG_E(TAG, "Failed to resolve data path for crash.txt");
return;
}
std::string path = std::string(root) + "/crash.txt";
file::FileMutexGuard guard(path);
if (!file::writeString(path, formatCrashData(crashData))) {
LOG_E(TAG, "Failed to write %s", path.c_str());
}
}
void onContinuePressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
ctx->continuePressed = true;
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself. launcher::start() is deferred to appMain(), after this app's
// own thread has finished cleaning up.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void createWidgets(lv_obj_t* parent, void* userData) {
@@ -72,7 +141,9 @@ void createWidgets(lv_obj_t* parent, void* userData) {
}
lv_obj_align(bottom_label, LV_ALIGN_BOTTOM_MID, 0, -2);
std::string url = getUrlFromCrashData();
const auto& crash_data = getRtcCrashData();
std::string url = getUrlFromCrashData(crash_data);
LOG_I(TAG, "%s", url.c_str());
size_t url_length = url.length();
@@ -156,6 +227,8 @@ int32_t appMain(int argc, char* argv[]) {
Context ctx {};
ctx.appInstanceId = appInstanceId;
writeCrashLogFile(getRtcCrashData());
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
@@ -207,7 +280,7 @@ extern const ::AppManifest manifest = {
.id = "tactility.crashdiagnostics",
.name = "Crash Diagnostics",
.category = APP_CATEGORY_SYSTEM,
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
.flags = APP_MANIFEST_FLAG_HIDDEN,
};
@@ -1,7 +1,6 @@
#ifdef ESP_PLATFORM
#include <Tactility/app/crashdiagnostics/QrUrl.h>
#include <Tactility/PanicHandler.h>
#include <sstream>
#include <vector>
@@ -14,8 +13,7 @@
#include <sdkconfig.h>
std::string getUrlFromCrashData() {
auto crash_data = getRtcCrashData();
std::string getUrlFromCrashData(const CrashData& crash_data) {
std::vector<uint32_t> stack_buffer(crash_data.callstackLength * 2);
for (int i = 0; i < crash_data.callstackLength; ++i) {
const CallstackFrame&frame = crash_data.callstack[i];
@@ -46,12 +46,7 @@ void updateViewState(Context* ctx);
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onEnableSwitchChanged(lv_event_t* event) {
+1 -6
View File
@@ -61,12 +61,7 @@ Device* getBacklightDevice() {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onBacklightSliderEvent(lv_event_t* event) {
+1 -6
View File
@@ -376,12 +376,7 @@ void View::createDirEntryWidget(lv_obj_t* list, dirent& dir_entry) {
}
void View::onBackPressed() {
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(appInstanceId, &event);
app_event_emit_close(appInstanceId);
}
void View::onNavigateUpPressed() {
@@ -62,8 +62,7 @@ int32_t appMain(int argc, char* argv[]) {
// app_manager_stop() after it receives the APP_EVENT_RESULT instead.
lastPath = path;
ctx.result = 0;
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(appInstanceId, &closeEvent);
app_event_emit_close(appInstanceId);
});
TaskEventGroup event_group {};
+1 -6
View File
@@ -41,12 +41,7 @@ static void onNavigateUpPressedCallback(lv_event_t* event) {
void View::onBackPressedCallback(lv_event_t* event) {
auto* view = static_cast<View*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(view->appInstanceId, &closeEvent);
app_event_emit_close(view->appInstanceId);
}
void View::onTapFile(const std::string& path, const std::string& filename) {
@@ -64,12 +64,7 @@ void createWidgets(lv_obj_t* parent, void* userData);
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onAddGpsPressed(lv_event_t* event) {
@@ -45,12 +45,7 @@ void onModeChanged(lv_event_t* e) {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void createWidgets(lv_obj_t* parent, void* userData) {
@@ -284,12 +284,7 @@ void selectBus(Context* ctx, int32_t selected) {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onSelectBus(lv_event_t* event) {
@@ -32,12 +32,7 @@ struct Context {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void createWidgets(lv_obj_t* parent, void* userData) {
@@ -59,10 +59,7 @@ void onButtonPressed(lv_event_t* e) {
LOG_I(TAG, "Cancel pressed");
btnCtx->ctx->result = 1;
}
// Async, non-blocking - see AlertDialog.cpp's onButtonPressed() for why this must not
// call app_manager_stop() directly (would deadlock against the LVGL lock).
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(btnCtx->ctx->appInstanceId, &event);
app_event_emit_close(btnCtx->ctx->appInstanceId);
}
void createButton(Context* ctx, lv_obj_t* parent, const std::string& text, lv_obj_t* textarea) {
@@ -59,12 +59,7 @@ struct Context {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onBacklightSwitch(lv_event_t* e) {
+5 -4
View File
@@ -1,6 +1,3 @@
#include "tactility/drivers/pointer.h"
#include <app/event.h>
#include <app/manager.h>
#include <app/manifest.h>
@@ -17,8 +14,10 @@
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/pointer.h>
#include <tactility/drivers/power_supply.h>
#include <tactility/log.h>
#include <tactility/memory.h>
#include <Tactility/app/setup/Setup.h>
#include <Tactility/settings/BootSettings.h>
@@ -273,8 +272,10 @@ extern const ::AppManifest manifest = {
.id = "tactility.launcher",
.name = "Launcher",
.category = APP_CATEGORY_SYSTEM,
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
.flags = APP_MANIFEST_FLAG_HIDDEN,
// No file IO, so callstack can be in external RAM
.stack = { .depth = 3072 , .desired_memory_capability = MEMORY_CAPABILITY_EXTERNAL }
};
// Kept for Tactility/Private/Tactility/app/launcher/Launcher.h's existing declaration (still
@@ -99,12 +99,7 @@ void onLanguageSet(lv_event_t* event) {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void createWidgets(lv_obj_t* parent, void* userData) {
+1 -6
View File
@@ -104,12 +104,7 @@ void updateUi(Context* ctx) {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onPowerEnabledChanged(lv_event_t* event) {
+1 -6
View File
@@ -99,12 +99,7 @@ void onYesPressed(lv_event_t* /*event*/) {
void onNoPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void createWidgets(lv_obj_t* parent, void* userData) {
@@ -66,12 +66,7 @@ void updateScreenshotMode(Context* ctx) {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onStartPressed(lv_event_t* event) {
@@ -47,13 +47,7 @@ void onItemSelected(lv_event_t* e) {
auto* itemCtx = static_cast<ItemContext*>(lv_event_get_user_data(e));
LOG_I(TAG, "Selected item at index %d", (int)itemCtx->index);
itemCtx->ctx->result = itemCtx->index;
// Async, non-blocking - just wakes this dialog's own thread. Must NOT call
// app_manager_stop() here: that bound-waits (thread_join) for the dialog's thread to
// finish, which needs the LVGL lock (window_manager_remove()) - but this callback is
// running ON the LVGL task, which would deadlock against itself. The caller reaps this
// instance via app_manager_stop() after it receives the APP_EVENT_RESULT instead.
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(itemCtx->ctx->appInstanceId, &event);
app_event_emit_close(itemCtx->ctx->appInstanceId);
}
void createChoiceItem(Context* ctx, lv_obj_t* list, const std::string& title, int32_t index) {
@@ -67,8 +61,7 @@ void createChoiceItem(Context* ctx, lv_obj_t* list, const std::string& title, in
// mirrors the original's 0-items (error) and 1-item (auto-select) shortcuts.
void closeWithResult(Context* ctx, int32_t result) {
ctx->result = result;
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &event);
app_event_emit_close(ctx->appInstanceId);
}
void createWidgets(lv_obj_t* parent, void* userData) {
+14 -13
View File
@@ -20,7 +20,9 @@ namespace tt::app::settings {
namespace {
uint32_t settingsInstanceId = 0;
struct Context {
uint32_t appInstanceId;
};
void onAppPressed(lv_event_t* e) {
// Fire-and-forget top-level navigation, same as AppList's own app-launch buttons.
@@ -29,13 +31,9 @@ void onAppPressed(lv_event_t* e) {
app_manager_start(manifest->id, &instanceId);
}
void onBackPressed(lv_event_t*) {
// The global toolbar nav callback only knows how to stop old-model apps, so this
// new-model app overrides its own toolbar's nav action to close itself instead. Async,
// non-blocking - see AppList.cpp's onBackPressed() for why this must not call
// app_manager_stop() directly (would deadlock against the LVGL lock).
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(settingsInstanceId, &event);
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
app_event_emit_close(ctx->appInstanceId);
}
void createWidget(const ::AppManifest* manifest, lv_obj_t* list) {
@@ -53,12 +51,14 @@ void collectManifest(const ::AppManifest* manifest, void* context) {
manifests->push_back(manifest);
}
void createWidgets(lv_obj_t* parent, void*) {
void createWidgets(lv_obj_t* parent, void* userData) {
auto* ctx = static_cast<Context*>(userData);
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
auto* toolbar = lvgl_toolbar_create(parent, "Settings");
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, nullptr);
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
auto* list = lv_list_create(parent);
lv_obj_set_width(list, LV_PCT(100));
@@ -79,7 +79,7 @@ void createWidgets(lv_obj_t* parent, void*) {
int32_t appMain(int argc, char* argv[]) {
uint32_t appInstanceId = app_scheduler_current_app_id();
settingsInstanceId = appInstanceId;
Context ctx { appInstanceId };
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
@@ -87,7 +87,7 @@ int32_t appMain(int argc, char* argv[]) {
AppEventSubscription sub {};
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
WindowId window = window_manager_create(appInstanceId, createWidgets, nullptr);
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);
@@ -115,8 +115,9 @@ extern const ::AppManifest manifest = {
.id = "tactility.settings",
.name = "Settings",
.category = APP_CATEGORY_SYSTEM,
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
.flags = APP_MANIFEST_FLAG_HIDDEN,
.stack = { .depth = 2400, .desired_memory_capability = 0 },
};
} // namespace
+1 -5
View File
@@ -159,11 +159,7 @@ void onContinueClicked(lv_event_t* event) {
break;
case Phase::Done: {
markCompleted();
// Async, non-blocking - must NOT call app_manager_stop() directly here: this
// callback runs ON the LVGL task, and app-lifecycle transitions must happen on this
// app's own thread (woken via app_event_poll()), which closes by returning.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
break;
}
}
@@ -305,12 +305,7 @@ void updateTasks(Context* ctx) {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void createWidgets(lv_obj_t* parent, void* userData) {
@@ -34,12 +34,7 @@ struct Context {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onTimeFormatChanged(lv_event_t* event) {
+2 -8
View File
@@ -109,8 +109,7 @@ void createListItem(Context* ctx, lv_obj_t* list, const std::string& title, size
lastCode = entry.code;
ctx->result = 0; // Ok
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}, LV_EVENT_SHORT_CLICKED, buttonCtx);
}
@@ -186,12 +185,7 @@ void updateList(Context* ctx) {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void createWidgets(lv_obj_t* parent, void* userData) {
@@ -174,12 +174,7 @@ void onPress(lv_event_t* event) {
return;
}
// Async, non-blocking - must NOT call app_manager_stop() directly here: this callback runs
// ON the LVGL task, and app-lifecycle transitions must happen on this app's own thread
// (woken up via app_event_poll() below), which closes by returning. The result (Ok/Error)
// is reported by appMain() itself when it returns, based on ctx.calibrationApplied.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void createWidgets(lv_obj_t* parent, void* userData) {
@@ -72,12 +72,7 @@ struct Context {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void applyLive(Context* ctx) {
@@ -27,12 +27,7 @@ struct Context {
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onRebootMassStorageSdmmc(lv_event_t* event) {
@@ -54,12 +54,7 @@ void createWidgets(lv_obj_t* parent, void* userData);
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onWifiModeChanged(lv_event_t* e) {
@@ -47,11 +47,7 @@ void updateViews(Context* ctx);
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// but this callback runs ON the LVGL task, which would deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onPressForget(lv_event_t* event) {
@@ -60,11 +60,7 @@ void setLoading(Context* ctx, bool loading);
void onBackPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// but this callback runs ON the LVGL task, which would deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
void onWifiEvent(Context* ctx, WifiEvent event) {
@@ -89,8 +85,7 @@ void onWifiEvent(Context* ctx, WifiEvent event) {
lvgl_unlock();
if (shouldClose) {
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(ctx->appInstanceId, &closeEvent);
app_event_emit_close(ctx->appInstanceId);
}
}
+2 -8
View File
@@ -6,7 +6,6 @@
#include <Tactility/app/wifimanage/View.h>
#include <Tactility/app/wifimanage/WifiManagePrivate.h>
#include <Tactility/lvgl/Style.h>
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/service/wifi/WifiSettings.h>
#include <Tactility/Tactility.h>
@@ -23,12 +22,7 @@ constexpr auto* TAG = "WifiManageView";
static void onBackPressed(lv_event_t* event) {
auto* appInstanceId = static_cast<uint32_t*>(lv_event_get_user_data(event));
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
// deadlock against itself.
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
app_event_emit(*appInstanceId, &closeEvent);
app_event_emit_close(*appInstanceId);
}
static uint8_t mapRssiToPercentage(int rssi) {
@@ -40,7 +34,7 @@ static uint8_t mapRssiToPercentage(int rssi) {
}
auto percentage = (float)(90U - abs_rssi) / 60.f * 100.f;
return (uint8_t)percentage;
return static_cast<uint8_t>(percentage);
}
static void onEnableSwitchChanged(lv_event_t* event) {