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,6 +1,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
// Paired with -Wl,--wrap=read/write/close - see Tactility/CMakeLists.txt (POSIX) and the
|
||||
// top-level CMakeLists.txt (ESP32) for where that's applied. On a platform where it isn't
|
||||
// (currently: macOS, whose linker doesn't support --wrap), these are simply never called - real
|
||||
// read()/write()/close() calls go straight through unredirected.
|
||||
#include <app/io.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
@@ -21,4 +24,177 @@ int __wrap_close(int fd) {
|
||||
|
||||
}
|
||||
|
||||
#endif // ESP_PLATFORM
|
||||
// region glibc stdio wraps
|
||||
//
|
||||
// glibc's printf/fprintf/etc are compiled into libc.so and call an internal, non-exported write()
|
||||
// alias - --wrap=write (above) can't reach that call, only calls WE make to the public symbol.
|
||||
// These wraps instead redirect calls WE make to printf/fprintf/etc, the same trick as read/write/
|
||||
// close above. Newlib (ESP-IDF) doesn't have this gap - its stdio does call the wrappable syscall
|
||||
// stubs - so Tactility/CMakeLists.txt only applies the matching -Wl,--wrap= flags on POSIX.
|
||||
//
|
||||
// Scoped to the printf/getc families only: fread/fwrite take an arbitrary FILE* and are already
|
||||
// used sitewide for real file I/O (e.g. File.cpp's readBinaryInternal), so wrapping them would
|
||||
// route every such call through this file's stdin/stdout check - a correctness risk for unrelated
|
||||
// code that isn't worth taking here. putc/getc are excluded too since glibc defines them as
|
||||
// macros, not real calls, so wrapping those symbols wouldn't reliably intercept them.
|
||||
|
||||
#if !defined(ESP_PLATFORM) && !defined(__APPLE__)
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <unistd.h>
|
||||
|
||||
extern "C" {
|
||||
int __real_vfprintf(FILE* stream, const char* format, va_list args);
|
||||
int __real_fputs(const char* s, FILE* stream);
|
||||
int __real_fputc(int c, FILE* stream);
|
||||
int __real_fgetc(FILE* stream);
|
||||
char* __real_fgets(char* buffer, int size, FILE* stream);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void writeAllToStdout(const void* data, size_t size) {
|
||||
const auto* bytes = static_cast<const char*>(data);
|
||||
size_t remaining = size;
|
||||
while (remaining > 0) {
|
||||
ssize_t written = app_io_write(STDOUT_FILENO, bytes, remaining);
|
||||
if (written <= 0) {
|
||||
break;
|
||||
}
|
||||
bytes += written;
|
||||
remaining -= static_cast<size_t>(written);
|
||||
}
|
||||
}
|
||||
|
||||
// Formats into stdout via app_io_write() rather than through a FILE*'s own buffering, since that
|
||||
// buffering is exactly what glibc's internal write() call sidesteps --wrap for in the first place.
|
||||
int formatToStdout(const char* format, va_list args) {
|
||||
char stackBuffer[256];
|
||||
va_list argsForStack;
|
||||
va_copy(argsForStack, args);
|
||||
int needed = vsnprintf(stackBuffer, sizeof(stackBuffer), format, argsForStack);
|
||||
va_end(argsForStack);
|
||||
if (needed < 0) {
|
||||
return needed;
|
||||
}
|
||||
if (static_cast<size_t>(needed) < sizeof(stackBuffer)) {
|
||||
writeAllToStdout(stackBuffer, static_cast<size_t>(needed));
|
||||
return needed;
|
||||
}
|
||||
auto heapBuffer = std::make_unique<char[]>(static_cast<size_t>(needed) + 1);
|
||||
va_list argsForHeap;
|
||||
va_copy(argsForHeap, args);
|
||||
vsnprintf(heapBuffer.get(), static_cast<size_t>(needed) + 1, format, argsForHeap);
|
||||
va_end(argsForHeap);
|
||||
writeAllToStdout(heapBuffer.get(), static_cast<size_t>(needed));
|
||||
return needed;
|
||||
}
|
||||
|
||||
int readOneFromStdin(char& out) {
|
||||
return static_cast<int>(app_io_read(STDIN_FILENO, &out, 1));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
int __wrap_vprintf(const char* format, va_list args) {
|
||||
return formatToStdout(format, args);
|
||||
}
|
||||
|
||||
int __wrap_printf(const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int result = formatToStdout(format, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
int __wrap_vfprintf(FILE* stream, const char* format, va_list args) {
|
||||
if (stream == stdout) {
|
||||
return formatToStdout(format, args);
|
||||
}
|
||||
return __real_vfprintf(stream, format, args);
|
||||
}
|
||||
|
||||
int __wrap_fprintf(FILE* stream, const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int result = (stream == stdout) ? formatToStdout(format, args) : __real_vfprintf(stream, format, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
int __wrap_puts(const char* s) {
|
||||
writeAllToStdout(s, strlen(s));
|
||||
writeAllToStdout("\n", 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __wrap_fputs(const char* s, FILE* stream) {
|
||||
if (stream == stdout) {
|
||||
writeAllToStdout(s, strlen(s));
|
||||
return 0;
|
||||
}
|
||||
return __real_fputs(s, stream);
|
||||
}
|
||||
|
||||
int __wrap_putchar(int c) {
|
||||
auto ch = static_cast<char>(c);
|
||||
writeAllToStdout(&ch, 1);
|
||||
return c;
|
||||
}
|
||||
|
||||
int __wrap_fputc(int c, FILE* stream) {
|
||||
if (stream == stdout) {
|
||||
return __wrap_putchar(c);
|
||||
}
|
||||
return __real_fputc(c, stream);
|
||||
}
|
||||
|
||||
int __wrap_getchar() {
|
||||
char c;
|
||||
return readOneFromStdin(c) == 1 ? static_cast<unsigned char>(c) : EOF;
|
||||
}
|
||||
|
||||
int __wrap_fgetc(FILE* stream) {
|
||||
if (stream == stdin) {
|
||||
return __wrap_getchar();
|
||||
}
|
||||
return __real_fgetc(stream);
|
||||
}
|
||||
|
||||
char* __wrap_fgets(char* buffer, int size, FILE* stream) {
|
||||
if (stream != stdin) {
|
||||
return __real_fgets(buffer, size, stream);
|
||||
}
|
||||
if (size <= 0) {
|
||||
return nullptr;
|
||||
}
|
||||
int i = 0;
|
||||
for (; i < size - 1; ++i) {
|
||||
char c;
|
||||
if (readOneFromStdin(c) != 1) {
|
||||
break;
|
||||
}
|
||||
buffer[i] = c;
|
||||
if (c == '\n') {
|
||||
++i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
buffer[i] = '\0';
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // !ESP_PLATFORM && !__APPLE__
|
||||
|
||||
// endregion
|
||||
|
||||
@@ -52,11 +52,7 @@ std::string getUserDataRootPath() {
|
||||
}
|
||||
|
||||
std::string getDataPath() {
|
||||
#ifdef ESP_PLATFORM
|
||||
return getUserDataRootPath() + "/tactility";
|
||||
#else
|
||||
return "data";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string getTempPath() {
|
||||
|
||||
@@ -18,7 +18,11 @@ std::vector<dirent> getFileSystemDirents() {
|
||||
if (!file_system_is_mounted(fs)) return true;
|
||||
char path[128];
|
||||
if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
|
||||
auto mount_name = std::string(path).substr(1);
|
||||
// ESP32 mount paths are short names ("/system"); POSIX file systems can return a full
|
||||
// absolute host path instead, so take the last path component either way.
|
||||
auto path_str = std::string(path);
|
||||
auto slash_pos = path_str.find_last_of('/');
|
||||
auto mount_name = slash_pos == std::string::npos ? path_str : path_str.substr(slash_pos + 1);
|
||||
if (!config::SHOW_SYSTEM_PARTITION && mount_name.starts_with(SYSTEM_PARTITION_NAME)) return true;
|
||||
auto dir_entry = dirent {
|
||||
.d_ino = 2,
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
#ifndef ESP_PLATFORM
|
||||
|
||||
#include <Tactility/PartitionsPosix.h>
|
||||
#include <Tactility/MountPoints.h>
|
||||
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/filesystem/file_system.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace tt {
|
||||
|
||||
constexpr auto* TAG = "Partitions";
|
||||
|
||||
// region file_system stub
|
||||
|
||||
// A plain host directory has no real mount/unmount step to perform - "mounted" here just tracks
|
||||
// whether file_system_remove()'s precondition (must be unmounted first) has been satisfied.
|
||||
struct DirectoryFsData {
|
||||
char path[PATH_MAX];
|
||||
bool mounted;
|
||||
};
|
||||
|
||||
static DirectoryFsData system_fs_data;
|
||||
static DirectoryFsData data_fs_data;
|
||||
static FileSystem* system_fs = nullptr;
|
||||
static FileSystem* data_fs = nullptr;
|
||||
|
||||
static error_t mount(void* data) {
|
||||
static_cast<DirectoryFsData*>(data)->mounted = true;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t unmount(void* data) {
|
||||
static_cast<DirectoryFsData*>(data)->mounted = false;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static bool is_mounted(void* data) {
|
||||
return static_cast<DirectoryFsData*>(data)->mounted;
|
||||
}
|
||||
|
||||
static error_t get_path(void* data, char* out_path, size_t out_path_size) {
|
||||
auto* fs_data = static_cast<DirectoryFsData*>(data);
|
||||
if (strlen(fs_data->path) >= out_path_size) {
|
||||
return ERROR_BUFFER_OVERFLOW;
|
||||
}
|
||||
strcpy(out_path, fs_data->path);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static const FileSystemApi directory_fs_api = {
|
||||
.mount = mount,
|
||||
.unmount = unmount,
|
||||
.is_mounted = is_mounted,
|
||||
.get_path = get_path,
|
||||
};
|
||||
|
||||
// endregion file_system stub
|
||||
|
||||
// relative_path is resolved against the process' current working directory (the simulator is
|
||||
// expected to run with Data/ as its working directory, so file::SYSTEM_PARTITION_NAME/
|
||||
// DATA_PARTITION_NAME here match file::MOUNT_POINT_SYSTEM/MOUNT_POINT_DATA).
|
||||
static FileSystem* registerDirectoryFs(const char* relativePath, DirectoryFsData* outData) {
|
||||
if (realpath(relativePath, outData->path) == nullptr) {
|
||||
LOG_E(TAG, "Failed to resolve '%s' to an absolute path: %s", relativePath, strerror(errno));
|
||||
return nullptr;
|
||||
}
|
||||
outData->mounted = true;
|
||||
return file_system_add(&directory_fs_api, outData);
|
||||
}
|
||||
|
||||
static void unregisterDirectoryFs(FileSystem* fs) {
|
||||
if (fs == nullptr) {
|
||||
return;
|
||||
}
|
||||
file_system_unmount(fs);
|
||||
file_system_remove(fs);
|
||||
}
|
||||
|
||||
bool initPartitionsPosix() {
|
||||
system_fs = registerDirectoryFs(file::SYSTEM_PARTITION_NAME, &system_fs_data);
|
||||
if (system_fs == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data_fs = registerDirectoryFs(file::DATA_PARTITION_NAME, &data_fs_data);
|
||||
if (data_fs == nullptr) {
|
||||
unregisterDirectoryFs(system_fs);
|
||||
system_fs = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // ESP_PLATFORM
|
||||
@@ -4,13 +4,27 @@
|
||||
#include <app_esp32/module.h>
|
||||
#endif
|
||||
|
||||
#if __has_include(<unistd.h>) && not defined(ESP_PLATFORM)
|
||||
#define TT_IS_POSIX 1
|
||||
#else
|
||||
#define TT_IS_POSIX 0
|
||||
#endif
|
||||
|
||||
#if TT_IS_POSIX or defined(ESP_PLATFORM) // esp-idf supports certain posix symbols
|
||||
#include <posix_symbols/module.h>
|
||||
#endif
|
||||
|
||||
#if TT_IS_POSIX
|
||||
#include <app_posix/module.h>
|
||||
#include <Tactility/PartitionsPosix.h>
|
||||
#endif
|
||||
|
||||
#include <format>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/install.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/module.h>
|
||||
@@ -19,7 +33,6 @@
|
||||
|
||||
#include <Tactility/CpuAffinity.h>
|
||||
#include <Tactility/DeprecatedPaths.h>
|
||||
#include <Tactility/LogMessages.h>
|
||||
#include <Tactility/MountPoints.h>
|
||||
#include <Tactility/TactilityConfig.h>
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
@@ -35,7 +48,10 @@
|
||||
#include <Tactility/service/audio/Audio.h>
|
||||
#include <Tactility/settings/DisplaySettings.h>
|
||||
#include <Tactility/settings/TimePrivate.h>
|
||||
|
||||
#ifdef CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED
|
||||
#include <Tactility/settings/TouchCalibrationSettings.h>
|
||||
#endif
|
||||
|
||||
#include <c_symbols/module.h>
|
||||
#include <cpp_symbols/module.h>
|
||||
@@ -47,7 +63,6 @@
|
||||
#include <gps_meshtastic/module.h>
|
||||
#include <http/module.h>
|
||||
#include <mbedtls/module.h>
|
||||
#include <posix_symbols/module.h>
|
||||
#include <pthread/module.h>
|
||||
|
||||
#include <crypt/module.h>
|
||||
@@ -501,7 +516,9 @@ void run(Module* const dtsModules[], const DtsDevice dtsDevices[]) {
|
||||
|
||||
// C/C++/Posix symbols
|
||||
check(module_ensure_started(&c_symbols_module) == ERROR_NONE);
|
||||
#if TT_IS_POSIX or defined(ESP_PLATFORM) // esp-idf supports certain posix symbols
|
||||
check(module_ensure_started(&posix_symbols_module) == ERROR_NONE);
|
||||
#endif
|
||||
check(module_ensure_started(&cpp_symbols_module) == ERROR_NONE);
|
||||
// OS level symbols
|
||||
check(module_ensure_started(&freertos_module) == ERROR_NONE);
|
||||
@@ -516,10 +533,14 @@ void run(Module* const dtsModules[], const DtsDevice dtsDevices[]) {
|
||||
check(module_ensure_started(&gps_meshtastic_module) == ERROR_NONE);
|
||||
#ifdef ESP_PLATFORM
|
||||
check(module_ensure_started(&app_esp32_module) == ERROR_NONE);
|
||||
#elif TT_IS_POSIX
|
||||
check(module_ensure_started(&app_posix_module) == ERROR_NONE);
|
||||
#endif
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
initEsp();
|
||||
#elif TT_IS_POSIX
|
||||
check(initPartitionsPosix(), "Failed to init partitions");
|
||||
#endif
|
||||
|
||||
settings::initTimeZone();
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <format>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace tt::app::files {
|
||||
@@ -174,43 +175,22 @@ static bool copyRecursive(const std::string& src, const std::string& dst) {
|
||||
|
||||
void View::viewFile(const std::string& path, const std::string& filename) {
|
||||
std::string file_path = path + "/" + filename;
|
||||
|
||||
// For PC we need to make the path relative to the current work directory,
|
||||
// because that's how LVGL maps its 'drive letter' to the file system.
|
||||
std::string processed_filepath;
|
||||
if (kernel::getPlatform() == kernel::PlatformSimulator) {
|
||||
char cwd[PATH_MAX];
|
||||
if (getcwd(cwd, sizeof(cwd)) == nullptr) {
|
||||
LOG_E(TAG, "Failed to get current working directory");
|
||||
return;
|
||||
}
|
||||
if (!file_path.starts_with(cwd)) {
|
||||
LOG_E(TAG, "Can only work with files in working directory %s", cwd);
|
||||
return;
|
||||
}
|
||||
processed_filepath = file_path.substr(strlen(cwd));
|
||||
} else {
|
||||
processed_filepath = file_path;
|
||||
}
|
||||
|
||||
LOG_I(TAG, "Clicked %s", file_path.c_str());
|
||||
|
||||
if (isSupportedAppFile(filename)) {
|
||||
#ifdef ESP_PLATFORM
|
||||
// install(filename);
|
||||
auto message = std::format("Do you want to install {}?", filename);
|
||||
installAppPath = processed_filepath;
|
||||
installAppPath = file_path;
|
||||
auto choices = std::vector<std::string> {"Yes", "No"};
|
||||
installDialogId = alertdialog::start(appInstanceId, "Install?", message, choices);
|
||||
#endif
|
||||
} else if (isSupportedImageFile(filename)) {
|
||||
imageviewer::start(processed_filepath);
|
||||
imageviewer::start(file_path);
|
||||
} else if (isSupportedTextFile(filename)) {
|
||||
if (kernel::getPlatform() == kernel::PlatformEsp) {
|
||||
notes::start(processed_filepath);
|
||||
notes::start(file_path);
|
||||
} else {
|
||||
// Remove forward slash, because we need a relative path
|
||||
notes::start(processed_filepath.substr(1));
|
||||
notes::start(file_path.substr(1));
|
||||
}
|
||||
} else {
|
||||
LOG_W(TAG, "Opening files of this type is not supported");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include <Tactility/app/notes/Notes.h>
|
||||
|
||||
#include "Tactility/app/alertdialog/AlertDialog.h"
|
||||
|
||||
#include <Tactility/app/fileselection/FileSelection.h>
|
||||
#include <Tactility/file/File.h>
|
||||
|
||||
@@ -6,6 +9,7 @@
|
||||
#include <app/manager.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
#include <app/stream.h>
|
||||
|
||||
#include <lvgl_window_manager/window_manager.h>
|
||||
|
||||
@@ -25,6 +29,7 @@ namespace {
|
||||
|
||||
struct Context {
|
||||
uint32_t appInstanceId;
|
||||
TaskEventGroup* eventGroup = nullptr;
|
||||
|
||||
lv_obj_t* uiCurrentFileName = nullptr;
|
||||
lv_obj_t* uiDropDownMenu = nullptr;
|
||||
@@ -35,6 +40,10 @@ struct Context {
|
||||
|
||||
uint32_t loadFileLaunchId = 0;
|
||||
uint32_t saveFileLaunchId = 0;
|
||||
AppStream loadResultStream {};
|
||||
uint8_t loadResultBuffer[256] {};
|
||||
AppStream saveResultStream {};
|
||||
uint8_t saveResultBuffer[256] {};
|
||||
};
|
||||
|
||||
|
||||
@@ -90,11 +99,11 @@ void appNotesEventCb(lv_event_t* e) {
|
||||
lvgl_lock();
|
||||
ctx->saveBuffer = lv_textarea_get_text(ctx->uiNoteText);
|
||||
lvgl_unlock();
|
||||
ctx->saveFileLaunchId = fileselection::startForExistingOrNewFile(ctx->appInstanceId);
|
||||
ctx->saveFileLaunchId = fileselection::startForExistingOrNewFile(ctx->appInstanceId, ctx->saveResultStream, ctx->saveResultBuffer, sizeof(ctx->saveResultBuffer), ctx->eventGroup);
|
||||
LOG_I(TAG, "launched with id %u", ctx->saveFileLaunchId);
|
||||
break;
|
||||
case 3: // Load
|
||||
ctx->loadFileLaunchId = fileselection::startForExistingFile(ctx->appInstanceId);
|
||||
ctx->loadFileLaunchId = fileselection::startForExistingFile(ctx->appInstanceId, ctx->loadResultStream, ctx->loadResultBuffer, sizeof(ctx->loadResultBuffer), ctx->eventGroup);
|
||||
LOG_I(TAG, "launched with id %u", ctx->loadFileLaunchId);
|
||||
break;
|
||||
}
|
||||
@@ -102,7 +111,7 @@ void appNotesEventCb(lv_event_t* e) {
|
||||
auto* cont = lv_event_get_current_target_obj(e);
|
||||
if (obj == cont) return;
|
||||
if (lv_obj_get_child(cont, 1)) {
|
||||
ctx->saveFileLaunchId = fileselection::startForExistingOrNewFile(ctx->appInstanceId);
|
||||
ctx->saveFileLaunchId = fileselection::startForExistingOrNewFile(ctx->appInstanceId, ctx->saveResultStream, ctx->saveResultBuffer, sizeof(ctx->saveResultBuffer), ctx->eventGroup);
|
||||
LOG_I(TAG, "launched with id %u", ctx->saveFileLaunchId);
|
||||
} else { //Reset
|
||||
resetFileContent(ctx);
|
||||
@@ -189,6 +198,7 @@ int32_t appMain(int argc, char* argv[]) {
|
||||
|
||||
TaskEventGroup event_group {};
|
||||
task_event_group_construct(&event_group);
|
||||
ctx.eventGroup = &event_group;
|
||||
|
||||
AppEventSubscription sub {};
|
||||
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
|
||||
@@ -206,23 +216,35 @@ int32_t appMain(int argc, char* argv[]) {
|
||||
shouldClose = true;
|
||||
break;
|
||||
case APP_EVENT_RESULT:
|
||||
LOG_I(TAG, "Result for launch id %u", event.result.launch_id);
|
||||
LOG_I(TAG, "Result for launch id %u = %u", event.result.launch_id, event.result.result);
|
||||
if (event.result.launch_id == ctx.loadFileLaunchId) {
|
||||
ctx.loadFileLaunchId = 0;
|
||||
if (event.result.result == 0 /* Ok */) {
|
||||
auto path = fileselection::getLastPath();
|
||||
char destination[sizeof(ctx.loadResultBuffer)];
|
||||
size_t length = app_stream_read(&ctx.loadResultStream, destination, sizeof(destination));
|
||||
app_stream_unsubscribe(&ctx.loadResultStream);
|
||||
auto path = std::string(destination, length);
|
||||
LOG_I(TAG, "Path: '%s'", path.c_str());
|
||||
if (!path.empty()) {
|
||||
openFile(&ctx, path);
|
||||
}
|
||||
} else {
|
||||
app_stream_unsubscribe(&ctx.loadResultStream);
|
||||
}
|
||||
} else if (event.result.launch_id == ctx.saveFileLaunchId) {
|
||||
ctx.saveFileLaunchId = 0;
|
||||
if (event.result.result == 0 /* Ok */) {
|
||||
auto path = fileselection::getLastPath();
|
||||
char destination[sizeof(ctx.saveResultBuffer)];
|
||||
size_t length = app_stream_read(&ctx.saveResultStream, destination, sizeof(destination));
|
||||
app_stream_unsubscribe(&ctx.saveResultStream);
|
||||
auto path = std::string(destination, length);
|
||||
// Must re-open file, because the UI was cleared after opening the dialog.
|
||||
LOG_I(TAG, "Path: '%s'", path.c_str());
|
||||
if (!path.empty() && saveFile(&ctx, path)) {
|
||||
openFile(&ctx, path);
|
||||
}
|
||||
} else {
|
||||
app_stream_unsubscribe(&ctx.saveResultStream);
|
||||
}
|
||||
}
|
||||
app_manager_stop(event.result.launch_id);
|
||||
|
||||
@@ -66,7 +66,6 @@ int scandir(
|
||||
ScandirFilter filterMethod,
|
||||
ScandirSort sortMethod
|
||||
) {
|
||||
LOG_I(TAG, "scandir start");
|
||||
DIR* dir = opendir(path.c_str());
|
||||
if (dir == nullptr) {
|
||||
LOG_E(TAG, "Failed to open dir %s", path.c_str());
|
||||
@@ -86,7 +85,6 @@ int scandir(
|
||||
std::ranges::sort(outList, sortMethod);
|
||||
}
|
||||
|
||||
LOG_I(TAG, "scandir finish");
|
||||
return outList.size();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user