#include "Tactility/app/fileselection/FileSelectionPrivate.h" #include "Tactility/app/fileselection/View.h" #include "Tactility/app/fileselection/State.h" #include #include #include #include #include #include namespace tt::app::fileselection { extern const ::AppManifest manifest; constexpr auto* TAG = "FileSelection"; namespace { struct Context { uint32_t appInstanceId; Mode mode; std::shared_ptr state; std::unique_ptr view; // The eventual appMain() return value - see AlertDialog.cpp's Context::result for why this // is a plain (non-atomic) field safely shared between the LVGL thread (writer, before // emitting APP_EVENT_CLOSE) and this app's own thread (reader, after waking from it). int32_t result = 1; // Cancelled - safety-net default if closed without picking a file }; // The last picked path. Static rather than per-instance: simple, and in practice only one // FileSelection dialog is ever open at a time. Written on the LVGL thread (View's select-button // callback, before emitting APP_EVENT_CLOSE); read by the parent via getLastPath() after // receiving that event - safe without a lock for the same reason Context::result is (see // AlertDialog.cpp). std::string lastPath; void createWidgets(lv_obj_t* parent, void* userData) { auto* ctx = static_cast(userData); ctx->view->init(parent, ctx->mode); } int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) { // argv layout: [0]="existing" or "existing_or_new". Context ctx {}; ctx.appInstanceId = appInstanceId; ctx.mode = (argc > 0 && std::string(argv[0]) == "existing_or_new") ? Mode::ExistingOrNew : Mode::Existing; ctx.state = std::make_shared(); ctx.view = std::make_unique(appInstanceId, ctx.state, [&ctx, appInstanceId](const std::string& path) { // Runs on the LVGL task (View::onSelectButtonPressed) - must NOT call app_manager_stop() // 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. The caller reaps this instance via // 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); }); AppEventSubscription sub {}; sub.app_instance_id = appInstanceId; app_event_subscribe(&sub); WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx); while (true) { AppEvent event {}; if (app_event_await(&sub, &event, portMAX_DELAY) != ERROR_NONE) { break; } if (event.type == APP_EVENT_CLOSE) { break; } } window_manager_remove(window); app_event_unsubscribe(&sub); return ctx.result; } } // namespace std::string getLastPath() { return lastPath; } uint32_t startForExistingFile(uint32_t callerAppInstanceId) { const char* argv[] = { "existing" }; uint32_t instanceId = 0; app_manager_start_for_result(manifest.id, callerAppInstanceId, 1, argv, &instanceId); return instanceId; } uint32_t startForExistingOrNewFile(uint32_t callerAppInstanceId) { const char* argv[] = { "existing_or_new" }; uint32_t instanceId = 0; app_manager_start_for_result(manifest.id, callerAppInstanceId, 1, argv, &instanceId); return instanceId; } extern const ::AppManifest manifest = { .id = "tactility.fileselection", .name = "File Selection", .category = APP_CATEGORY_SYSTEM, .location = { APP_LOCATION_MEMORY, reinterpret_cast(appMain) }, .flags = APP_MANIFEST_FLAG_HIDDEN, }; } // namespace