Support for building and running external apps (#112)
This commit is contained in:
committed by
GitHub
parent
42e843b463
commit
415442e410
@@ -10,11 +10,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
|
||||
SRCS ${SOURCE_FILES}
|
||||
INCLUDE_DIRS "Source/"
|
||||
PRIV_INCLUDE_DIRS "Private/"
|
||||
REQUIRES TactilityHeadless lvgl driver
|
||||
)
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB}
|
||||
PUBLIC lv_screenshot
|
||||
REQUIRES TactilityHeadless lvgl driver elf_loader lv_screenshot
|
||||
)
|
||||
|
||||
add_definitions(-DESP_PLATFORM)
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user