Support for building and running external apps (#112)

This commit is contained in:
Ken Van Hoeylandt
2024-12-08 16:46:19 +01:00
committed by GitHub
parent 42e843b463
commit 415442e410
74 changed files with 2901 additions and 126 deletions
+5
View File
@@ -53,8 +53,11 @@ namespace app {
namespace wifiapsettings { extern const AppManifest manifest; }
namespace wificonnect { extern const AppManifest manifest; }
namespace wifimanage { extern const AppManifest manifest; }
extern const AppManifest elfWrapperManifest;
}
#ifndef ESP_PLATFORM
extern const app::AppManifest screenshot_app;
#endif
@@ -77,6 +80,8 @@ static const std::vector<const app::AppManifest*> system_apps = {
&app::wifimanage::manifest,
#ifndef ESP_PLATFORM
&app::screenshot::manifest, // Screenshots don't work yet on ESP32
#else
&app::elfWrapperManifest, // For hot-loading ELF apps
#endif
};
+6 -6
View File
@@ -57,32 +57,32 @@ struct AppManifest {
/**
* App type affects launch behaviour.
*/
const Type type = TypeUser;
Type type = TypeUser;
/**
* Non-blocking method to call when app is started.
*/
const AppOnStart onStart = nullptr;
AppOnStart onStart = nullptr;
/**
* Non-blocking method to call when app is stopped.
*/
const AppOnStop _Nullable onStop = nullptr;
AppOnStop _Nullable onStop = nullptr;
/**
* Non-blocking method to create the GUI
*/
const AppOnShow _Nullable onShow = nullptr;
AppOnShow _Nullable onShow = nullptr;
/**
* Non-blocking method, called before gui is destroyed
*/
const AppOnHide _Nullable onHide = nullptr;
AppOnHide _Nullable onHide = nullptr;
/**
* Handle the result for apps that are launched
*/
const AppOnResult _Nullable onResult = nullptr;
AppOnResult _Nullable onResult = nullptr;
};
struct {
+126
View File
@@ -0,0 +1,126 @@
#ifdef ESP_PLATFORM
#include "file/File.h"
#include "ElfApp.h"
#include "TactilityCore.h"
#include "esp_elf.h"
#include "service/loader/Loader.h"
namespace tt::app {
#define TAG "elf_app"
#define ELF_WRAPPER_APP_ID "ElfWrapper"
static size_t elfManifestSetCount = 0;
std::unique_ptr<uint8_t[]> elfFileData;
esp_elf_t elf;
bool startElfApp(const char* filePath) {
TT_LOG_I(TAG, "Starting ELF %s", filePath);
assert(elfFileData == nullptr);
size_t size = 0;
elfFileData = file::readBinary(filePath, size);
if (elfFileData == nullptr) {
return false;
}
if (esp_elf_init(&elf) < 0) {
TT_LOG_E(TAG, "Failed to initialize");
return false;
}
if (esp_elf_relocate(&elf, elfFileData.get()) < 0) {
TT_LOG_E(TAG, "Failed to load executable");
return false;
}
int argc = 0;
char* argv[] = {};
size_t manifest_set_count = elfManifestSetCount;
if (esp_elf_request(&elf, 0, argc, argv) < 0) {
TT_LOG_W(TAG, "Executable returned error code");
return false;
}
if (elfManifestSetCount > manifest_set_count) {
service::loader::startApp(ELF_WRAPPER_APP_ID);
} else {
TT_LOG_W(TAG, "App did not set manifest to run - cleaning up ELF");
esp_elf_deinit(&elf);
elfFileData = nullptr;
}
return true;
}
static void onStart(AppContext& app) {}
static void onStop(AppContext& app) {}
static void onShow(AppContext& app, lv_obj_t* parent) {}
static void onHide(AppContext& app) {}
static void onResult(AppContext& app, Result result, const Bundle& resultBundle) {}
AppManifest elfManifest = {
.id = "",
.name = "",
.type = TypeHidden,
.onStart = onStart,
.onStop = onStop,
.onShow = onShow,
.onHide = onHide,
.onResult = onResult
};
static void onStartWrapper(AppContext& app) {
elfManifest.onStart(app);
}
static void onStopWrapper(AppContext& app) {
elfManifest.onStop(app);
TT_LOG_I(TAG, "Cleaning up ELF");
esp_elf_deinit(&elf);
elfFileData = nullptr;
}
static void onShowWrapper(AppContext& app, lv_obj_t* parent) {
elfManifest.onShow(app, parent);
}
static void onHideWrapper(AppContext& app) {
elfManifest.onHide(app);
}
static void onResultWrapper(AppContext& app, Result result, const Bundle& bundle) {
elfManifest.onResult(app, result, bundle);
}
AppManifest elfWrapperManifest = {
.id = ELF_WRAPPER_APP_ID,
.name = "ELF Wrapper",
.type = TypeHidden,
.onStart = onStartWrapper,
.onStop = onStopWrapper,
.onShow = onShowWrapper,
.onHide = onHideWrapper,
.onResult = onResultWrapper
};
void setElfAppManifest(const AppManifest& manifest) {
elfManifest.id = manifest.id;
elfManifest.name = manifest.name;
elfWrapperManifest.name = manifest.name;
elfManifest.onStart = manifest.onStart;
elfManifest.onStop = manifest.onStop;
elfManifest.onShow = manifest.onShow;
elfManifest.onHide = manifest.onHide;
elfManifest.onResult = manifest.onResult;
elfManifestSetCount++;
}
} // namespace
#endif // ESP_PLATFORM
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include "AppManifest.h"
#ifdef ESP_PLATFORM
namespace tt::app {
bool startElfApp(const char* filePath);
void setElfAppManifest(const AppManifest& manifest);
}
#endif // ESP_PLATFORM
+17 -2
View File
@@ -1,15 +1,17 @@
#include "FilesData.h"
#include "Tactility.h"
#include "app/AppContext.h"
#include "Tactility.h"
#include "Assets.h"
#include "Check.h"
#include "FileUtils.h"
#include "StringUtils.h"
#include "app/ElfApp.h"
#include "app/imageviewer/ImageViewer.h"
#include "app/textviewer/TextViewer.h"
#include "lvgl.h"
#include "service/loader/Loader.h"
#include "lvgl/Toolbar.h"
#include <dirent.h>
#include <unistd.h>
#include <memory>
@@ -52,6 +54,15 @@ static bool hasFileExtension(const char* path, const char* extension) {
return true;
}
static bool isSupportedExecutableFile(const char* filename) {
#ifdef ESP_PLATFORM
// Currently only the PNG library is built into Tactility
return hasFileExtension(filename, ".elf");
#else
return false;
#endif
}
static bool isSupportedImageFile(const char* filename) {
// Currently only the PNG library is built into Tactility
return hasFileExtension(filename, ".png");
@@ -120,7 +131,11 @@ static void viewFile(const char* path, const char* filename) {
TT_LOG_I(TAG, "Clicked %s", filepath);
if (isSupportedImageFile(filename)) {
if (isSupportedExecutableFile(filename)) {
#ifdef ESP_PLATFORM
app::startElfApp(processed_filepath);
#endif
} else if (isSupportedImageFile(filename)) {
auto bundle = std::make_shared<Bundle>();
bundle->putString(IMAGE_VIEWER_FILE_ARGUMENT, processed_filepath);
service::loader::startApp("ImageViewer", false, bundle);
@@ -109,3 +109,15 @@ extern const AppManifest manifest = {
};
}
extern "C" {
extern void tt_app_selectiondialog_start2(const char* title, int argc, const char* argv[]) {
std::vector<std::string> list;
for (int i = 0; i < argc; i++) {
const char* item = argv[i];
list.push_back(item);
}
tt::app::selectiondialog::start(title, list);
}
}
@@ -19,4 +19,4 @@ namespace tt::app::selectiondialog {
/** App result data */
int32_t getResultIndex(const Bundle& bundle);
}
}
+3 -55
View File
@@ -1,65 +1,13 @@
#include "LabelUtils.h"
#include "TactilityCore.h"
#include "file/File.h"
namespace tt::lvgl {
#define TAG "tt_lv_label"
static long file_get_size(FILE* file) {
long original_offset = ftell(file);
if (fseek(file, 0, SEEK_END) != 0) {
TT_LOG_E(TAG, "fseek failed");
return -1;
}
long file_size = ftell(file);
if (file_size == -1) {
TT_LOG_E(TAG, "Could not get file length");
return -1;
}
if (fseek(file, original_offset, SEEK_SET) != 0) {
TT_LOG_E(TAG, "fseek Failed");
return -1;
}
return file_size;
}
static char* str_alloc_from_file(const char* filepath) {
FILE* file = fopen(filepath, "rb");
if (file == nullptr) {
TT_LOG_E(TAG, "Failed to open %s", filepath);
return nullptr;
}
long content_length = file_get_size(file);
auto* text_buffer = static_cast<char*>(malloc(content_length + 1));
if (text_buffer == nullptr) {
TT_LOG_E(TAG, "Insufficient memory. Failed to allocate %ldl bytes.", content_length);
return nullptr;
}
int buffer;
uint32_t buffer_offset = 0;
text_buffer[0] = 0;
while ((buffer = fgetc(file)) != EOF && buffer_offset < content_length) {
text_buffer[buffer_offset] = (char)buffer;
buffer_offset++;
}
text_buffer[buffer_offset] = 0;
fclose(file);
return text_buffer;
}
void label_set_text_file(lv_obj_t* label, const char* filepath) {
char* text = str_alloc_from_file(filepath);
lv_label_set_text(label, text);
free(text);
auto text = file::readString(filepath);
lv_label_set_text(label, (const char*)text.get());
}
} // namespace