Implement posix app loading (#643)
- Added POSIX desktop support for SDK builds, application packaging, and integration testing. - Added POSIX filesystem partitions and improved application path handling. - Added simulator support for loading and running applications dynamically. - Improved simulator task stack handling and display startup reliability. - Improved simulator display scaling, resizing, high-DPI support, and pointer accuracy. - Fixed LVGL timers and input polling on POSIX. (fixes simulator with Linux on some Intel graphics platforms) - Standardized data paths across platforms. - Logging now works via separate task: this allows apps to write to log without it affecting their stdout (for apps that output text as relevant date for other apps, like the File Selection app) - Fixes for app stdio
This commit is contained in:
committed by
GitHub
parent
d3656bcd3d
commit
643cbc3806
@@ -1,18 +1,23 @@
|
||||
#include "Tactility/app/fileselection/FileSelection.h"
|
||||
#include "Tactility/app/fileselection/FileSelectionPrivate.h"
|
||||
#include "Tactility/app/fileselection/View.h"
|
||||
#include "Tactility/app/fileselection/State.h"
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/io.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
#include <app/stream.h>
|
||||
|
||||
#include <lvgl_window_manager/window_manager.h>
|
||||
|
||||
#include <tactility/check.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace tt::app::fileselection {
|
||||
|
||||
@@ -27,20 +32,11 @@ struct Context {
|
||||
Mode mode;
|
||||
std::shared_ptr<State> state;
|
||||
std::unique_ptr<View> 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
|
||||
std::string resultPath;
|
||||
int32_t resultCode = 1; // 1 means Cancelled
|
||||
};
|
||||
|
||||
|
||||
// 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<Context*>(userData);
|
||||
ctx->view->init(parent, ctx->mode);
|
||||
@@ -52,16 +48,11 @@ int32_t appMain(int argc, char* argv[]) {
|
||||
|
||||
Context ctx {};
|
||||
ctx.appInstanceId = appInstanceId;
|
||||
ctx.mode = (argc > 0 && std::string(argv[0]) == "existing_or_new") ? Mode::ExistingOrNew : Mode::Existing;
|
||||
ctx.mode = (argc > 0 && std::string(argv[0]) == "--existing-or-new") ? Mode::ExistingOrNew : Mode::Existing;
|
||||
ctx.state = std::make_shared<State>();
|
||||
ctx.view = std::make_unique<View>(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;
|
||||
ctx.resultPath = path;
|
||||
ctx.resultCode = 0;
|
||||
app_event_emit_close(appInstanceId);
|
||||
});
|
||||
|
||||
@@ -91,27 +82,42 @@ int32_t appMain(int argc, char* argv[]) {
|
||||
check(app_event_unsubscribe(&sub) == ERROR_NONE);
|
||||
task_event_group_destruct(&event_group);
|
||||
|
||||
return ctx.result;
|
||||
if (ctx.resultCode == 0) {
|
||||
// The parent captures this via an AppStream bound to our stdout (see startWithMode()) -
|
||||
// see AppStdioWrap.cpp for how printf() itself gets routed there on POSIX.
|
||||
LOG_I(TAG, "Result: %s", ctx.resultPath.c_str());
|
||||
printf("%s", ctx.resultPath.c_str());
|
||||
}
|
||||
|
||||
return ctx.resultCode;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string getLastPath() {
|
||||
return lastPath;
|
||||
}
|
||||
namespace {
|
||||
|
||||
uint32_t startForExistingFile(uint32_t callerAppInstanceId) {
|
||||
const char* argv[] = { "existing" };
|
||||
uint32_t startWithMode(const char* modeArg, uint32_t callerAppInstanceId, AppStream& stream, void* buffer, size_t bufferCapacity, TaskEventGroup* eventGroup) {
|
||||
const char* argv[] = { modeArg };
|
||||
AppStreamBinding binding = {
|
||||
.producer_fd = STDOUT_FILENO,
|
||||
.stream = &stream,
|
||||
.buffer = buffer,
|
||||
.buffer_capacity = bufferCapacity,
|
||||
.event_group = eventGroup,
|
||||
};
|
||||
uint32_t instanceId = 0;
|
||||
app_manager_start_for_result(manifest.id, callerAppInstanceId, 1, argv, &instanceId);
|
||||
app_manager_start_for_result_with_streams(manifest.id, callerAppInstanceId, 1, argv, &binding, 1, &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;
|
||||
} // namespace
|
||||
|
||||
uint32_t startForExistingFile(uint32_t callerAppInstanceId, AppStream& stream, void* buffer, size_t bufferCapacity, TaskEventGroup* eventGroup) {
|
||||
return startWithMode("--existing", callerAppInstanceId, stream, buffer, bufferCapacity, eventGroup);
|
||||
}
|
||||
|
||||
uint32_t startForExistingOrNewFile(uint32_t callerAppInstanceId, AppStream& stream, void* buffer, size_t bufferCapacity, TaskEventGroup* eventGroup) {
|
||||
return startWithMode("--existing-or-new", callerAppInstanceId, stream, buffer, bufferCapacity, eventGroup);
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
|
||||
@@ -33,7 +33,7 @@ std::string State::getSelectedChildPath() const {
|
||||
}
|
||||
|
||||
bool State::setEntriesForPath(const std::string& path) {
|
||||
LOG_I(TAG, "Changing path: %s -> %s", current_path.c_str(), path.c_str());
|
||||
LOG_D(TAG, "Changing path: %s -> %s", current_path.c_str(), path.c_str());
|
||||
|
||||
auto lock = mutex.asScopedLock();
|
||||
if (!lock.lock(100)) {
|
||||
@@ -47,7 +47,6 @@ bool State::setEntriesForPath(const std::string& path) {
|
||||
*/
|
||||
bool show_custom_root = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/");
|
||||
if (show_custom_root) {
|
||||
LOG_I(TAG, "Setting custom root");
|
||||
dir_entries = file::getFileSystemDirents();
|
||||
current_path = path;
|
||||
selected_child_entry = "";
|
||||
@@ -56,7 +55,6 @@ bool State::setEntriesForPath(const std::string& path) {
|
||||
dir_entries.clear();
|
||||
int count = file::scandir(path, dir_entries, &file::direntFilterDotEntries, file::direntSortAlphaAndType);
|
||||
if (count >= 0) {
|
||||
LOG_I(TAG, "%s has %d entries", path.c_str(), count);
|
||||
current_path = path;
|
||||
selected_child_entry = "";
|
||||
return true;
|
||||
@@ -69,7 +67,7 @@ bool State::setEntriesForPath(const std::string& path) {
|
||||
|
||||
bool State::setEntriesForChildPath(const std::string& childPath) {
|
||||
auto path = file::getChildPath(current_path, childPath);
|
||||
LOG_I(TAG, "Navigating from %s to %s", current_path.c_str(), path.c_str());
|
||||
LOG_D(TAG, "Navigating from %s to %s", current_path.c_str(), path.c_str());
|
||||
return setEntriesForPath(path);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,12 +60,15 @@ void View::onTapFile(const std::string& path, const std::string& filename) {
|
||||
LOG_E(TAG, "Can only work with files in working directory %s", cwd);
|
||||
return;
|
||||
}
|
||||
processed_filepath = file_path.substr(strlen(cwd));
|
||||
// MountPoints.h's MOUNT_POINT_DATA/MOUNT_POINT_SYSTEM have no leading slash on POSIX
|
||||
// (fopen() resolves relative to cwd there), unlike ESP32's real VFS mount points - so
|
||||
// strip the separator too, not just cwd itself.
|
||||
processed_filepath = file_path.substr(strlen(cwd) + 1);
|
||||
} else {
|
||||
processed_filepath = file_path;
|
||||
}
|
||||
|
||||
LOG_I(TAG, "Clicked %s", processed_filepath.c_str());
|
||||
LOG_D(TAG, "Clicked %s", processed_filepath.c_str());
|
||||
|
||||
lv_textarea_set_text(path_textarea, processed_filepath.c_str());
|
||||
}
|
||||
@@ -73,7 +76,7 @@ void View::onTapFile(const std::string& path, const std::string& filename) {
|
||||
void View::onDirEntryPressed(uint32_t index) {
|
||||
dirent dir_entry;
|
||||
if (state->getDirent(index, dir_entry)) {
|
||||
LOG_I(TAG, "Pressed %s %d", dir_entry.d_name, (int)dir_entry.d_type);
|
||||
LOG_D(TAG, "Pressed %s %d", dir_entry.d_name, (int)dir_entry.d_type);
|
||||
state->setSelectedChildEntry(dir_entry.d_name);
|
||||
using namespace tt::file;
|
||||
switch (dir_entry.d_type) {
|
||||
@@ -146,7 +149,7 @@ void View::createDirEntryWidget(lv_obj_t* list, dirent& dir_entry) {
|
||||
|
||||
void View::onNavigateUpPressed() {
|
||||
if (state->getCurrentPath() != "/") {
|
||||
LOG_I(TAG, "Navigating upwards");
|
||||
LOG_D(TAG, "Navigating upwards");
|
||||
std::string new_absolute_path;
|
||||
if (string::getPathParent(state->getCurrentPath(), new_absolute_path)) {
|
||||
state->setEntriesForPath(new_absolute_path);
|
||||
|
||||
Reference in New Issue
Block a user