Changed app manifest

This commit is contained in:
Ken Van Hoeylandt 2025-09-21 22:41:27 +02:00
parent c645dde8e2
commit bda21e3128
56 changed files with 452 additions and 313 deletions

View File

@ -17,7 +17,7 @@ class HelloWorldApp : public App {
}; };
extern const AppManifest hello_world_app = { extern const AppManifest hello_world_app = {
.id = "HelloWorld", .appId = "HelloWorld",
.name = "Hello World", .appName = "Hello World",
.createApp = create<HelloWorldApp> .createApp = create<HelloWorldApp>
}; };

View File

@ -5,9 +5,6 @@ sdk=0.6.0-SNAPSHOT1
platforms=esp32,esp32s3 platforms=esp32,esp32s3
[app] [app]
id=one.tactility.calculator id=one.tactility.calculator
version=0.1.0 versionName=0.1.0
versionCode=1
name=Calculator name=Calculator
description=Math is cool
[author]
name=Tactility
website=https://tactility.one

View File

@ -14,7 +14,7 @@ import shutil
import configparser import configparser
ttbuild_path = ".tactility" ttbuild_path = ".tactility"
ttbuild_version = "2.1.1" ttbuild_version = "2.2.0"
ttbuild_cdn = "https://cdn.tactility.one" ttbuild_cdn = "https://cdn.tactility.one"
ttbuild_sdk_json_validity = 3600 # seconds ttbuild_sdk_json_validity = 3600 # seconds
ttport = 6666 ttport = 6666
@ -242,19 +242,12 @@ def validate_manifest(manifest):
exit_with_error("Invalid manifest format: [app] not found") exit_with_error("Invalid manifest format: [app] not found")
if not "id" in manifest["app"]: if not "id" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] id not found") exit_with_error("Invalid manifest format: [app] id not found")
if not "version" in manifest["app"]: if not "versionName" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] version not found") exit_with_error("Invalid manifest format: [app] versionName not found")
if not "versionCode" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] versionCode not found")
if not "name" in manifest["app"]: if not "name" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] name not found") exit_with_error("Invalid manifest format: [app] name not found")
if not "description" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] description not found")
# [author]
if not "author" in manifest:
exit_with_error("Invalid manifest format: [author] not found")
if not "name" in manifest["author"]:
exit_with_error("Invalid manifest format: [author] name not found")
if not "website" in manifest["author"]:
exit_with_error("Invalid manifest format: [author] website not found")
def is_valid_manifest_platform(manifest, platform): def is_valid_manifest_platform(manifest, platform):
manifest_platforms = manifest["target"]["platforms"].split(",") manifest_platforms = manifest["target"]["platforms"].split(",")

View File

@ -5,9 +5,6 @@ sdk=0.6.0-SNAPSHOT1
platforms=esp32,esp32s3 platforms=esp32,esp32s3
[app] [app]
id=one.tactility.graphicsdemo id=one.tactility.graphicsdemo
version=0.1.0 versionName=0.1.0
versionCode=1
name=Graphics Demo name=Graphics Demo
description=A graphics and touch driver demonstration
[author]
name=Tactility
website=https://tactility.one

View File

@ -14,7 +14,7 @@ import shutil
import configparser import configparser
ttbuild_path = ".tactility" ttbuild_path = ".tactility"
ttbuild_version = "2.1.1" ttbuild_version = "2.2.0"
ttbuild_cdn = "https://cdn.tactility.one" ttbuild_cdn = "https://cdn.tactility.one"
ttbuild_sdk_json_validity = 3600 # seconds ttbuild_sdk_json_validity = 3600 # seconds
ttport = 6666 ttport = 6666
@ -242,19 +242,12 @@ def validate_manifest(manifest):
exit_with_error("Invalid manifest format: [app] not found") exit_with_error("Invalid manifest format: [app] not found")
if not "id" in manifest["app"]: if not "id" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] id not found") exit_with_error("Invalid manifest format: [app] id not found")
if not "version" in manifest["app"]: if not "versionName" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] version not found") exit_with_error("Invalid manifest format: [app] versionName not found")
if not "versionCode" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] versionCode not found")
if not "name" in manifest["app"]: if not "name" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] name not found") exit_with_error("Invalid manifest format: [app] name not found")
if not "description" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] description not found")
# [author]
if not "author" in manifest:
exit_with_error("Invalid manifest format: [author] not found")
if not "name" in manifest["author"]:
exit_with_error("Invalid manifest format: [author] name not found")
if not "website" in manifest["author"]:
exit_with_error("Invalid manifest format: [author] website not found")
def is_valid_manifest_platform(manifest, platform): def is_valid_manifest_platform(manifest, platform):
manifest_platforms = manifest["target"]["platforms"].split(",") manifest_platforms = manifest["target"]["platforms"].split(",")

View File

@ -5,9 +5,6 @@ sdk=0.6.0-SNAPSHOT1
platforms=esp32,esp32s3 platforms=esp32,esp32s3
[app] [app]
id=one.tactility.helloworld id=one.tactility.helloworld
version=0.1.0 versionName=0.1.0
name=Hello World versionCode=1
description=A demonstration app that says hi name=Hello World
[author]
name=Tactility
website=https://tactility.one

View File

@ -14,7 +14,7 @@ import shutil
import configparser import configparser
ttbuild_path = ".tactility" ttbuild_path = ".tactility"
ttbuild_version = "2.1.1" ttbuild_version = "2.2.0"
ttbuild_cdn = "https://cdn.tactility.one" ttbuild_cdn = "https://cdn.tactility.one"
ttbuild_sdk_json_validity = 3600 # seconds ttbuild_sdk_json_validity = 3600 # seconds
ttport = 6666 ttport = 6666
@ -242,19 +242,12 @@ def validate_manifest(manifest):
exit_with_error("Invalid manifest format: [app] not found") exit_with_error("Invalid manifest format: [app] not found")
if not "id" in manifest["app"]: if not "id" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] id not found") exit_with_error("Invalid manifest format: [app] id not found")
if not "version" in manifest["app"]: if not "versionName" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] version not found") exit_with_error("Invalid manifest format: [app] versionName not found")
if not "versionCode" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] versionCode not found")
if not "name" in manifest["app"]: if not "name" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] name not found") exit_with_error("Invalid manifest format: [app] name not found")
if not "description" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] description not found")
# [author]
if not "author" in manifest:
exit_with_error("Invalid manifest format: [author] not found")
if not "name" in manifest["author"]:
exit_with_error("Invalid manifest format: [author] name not found")
if not "website" in manifest["author"]:
exit_with_error("Invalid manifest format: [author] website not found")
def is_valid_manifest_platform(manifest, platform): def is_valid_manifest_platform(manifest, platform):
manifest_platforms = manifest["target"]["platforms"].split(",") manifest_platforms = manifest["target"]["platforms"].split(",")

View File

@ -94,8 +94,6 @@ std::shared_ptr<AppContext> _Nullable getCurrentAppContext();
/** @return the currently running app (it is only ever null before the splash screen is shown) */ /** @return the currently running app (it is only ever null before the splash screen is shown) */
std::shared_ptr<App> _Nullable getCurrentApp(); std::shared_ptr<App> _Nullable getCurrentApp();
bool isValidId(const std::string& id);
bool install(const std::string& path); bool install(const std::string& path);
bool uninstall(const std::string& appId); bool uninstall(const std::string& appId);

View File

@ -63,39 +63,45 @@ struct AppManifest {
constexpr static uint32_t Hidden = 1 << 1; constexpr static uint32_t Hidden = 1 << 1;
}; };
/** The version of the manifest file format */
std::string manifestVersion = {};
/** The SDK version that was used to compile this app. (e.g. "0.6.0") */
std::string targetSdk = {};
/** Comma-separated list of platforms, e.g. "esp32,esp32s3" */
std::string targetPlatforms = {};
/** The identifier by which the app is launched by the system and other apps. */ /** The identifier by which the app is launched by the system and other apps. */
std::string id = {}; std::string appId = {};
/** The user-readable name of the app. Used in UI. */ /** The user-readable name of the app. Used in UI. */
std::string name = {}; std::string appName = {};
/** Optional icon. */ /** Optional icon. */
std::string icon = {}; std::string appIcon = {};
/** Optional description */ /** The version as it is displayed to the user (e.g. "1.2.0") */
std::string description = {}; std::string appVersionName = {};
/** Optional author name */ /** The technical version (must be incremented with new releases of the app */
std::string authorName = {}; uint64_t appVersionCode = {};
/** Optional author website */
std::string authorWebsite = {};
/** App category helps with listing apps in Launcher, app list or settings apps. */ /** App category helps with listing apps in Launcher, app list or settings apps. */
Category category = Category::User; Category appCategory = Category::User;
/** Where the app is located */ /** Where the app is located */
Location location = Location::internal(); Location appLocation = Location::internal();
/** Controls various settings */ /** Controls various settings */
uint32_t flags = Flags::None; uint32_t appFlags = Flags::None;
/** Create the instance of the app */ /** Create the instance of the app */
CreateApp createApp = nullptr; CreateApp createApp = nullptr;
}; };
struct { struct {
bool operator()(const std::shared_ptr<AppManifest>& left, const std::shared_ptr<AppManifest>& right) const { return left->name < right->name; } bool operator()(const std::shared_ptr<AppManifest>& left, const std::shared_ptr<AppManifest>& right) const { return left->appName < right->appName; }
} SortAppManifestByName; } SortAppManifestByName;
} // namespace } // namespace

View File

@ -41,10 +41,10 @@ class AppInstance : public AppContext {
static std::shared_ptr<App> createApp( static std::shared_ptr<App> createApp(
const std::shared_ptr<AppManifest>& manifest const std::shared_ptr<AppManifest>& manifest
) { ) {
if (manifest->location.isInternal()) { if (manifest->appLocation.isInternal()) {
assert(manifest->createApp != nullptr); assert(manifest->createApp != nullptr);
return manifest->createApp(); return manifest->createApp();
} else if (manifest->location.isExternal()) { } else if (manifest->appLocation.isExternal()) {
if (manifest->createApp != nullptr) { if (manifest->createApp != nullptr) {
TT_LOG_W("", "Manifest specifies createApp, but this is not used with external apps"); TT_LOG_W("", "Manifest specifies createApp, but this is not used with external apps");
} }

View File

@ -0,0 +1,14 @@
#pragma once
#include <Tactility/app/AppManifest.h>
#include <map>
#include <string>
namespace tt::app {
bool isValidId(const std::string& id);
bool parseManifest(const std::map<std::string, std::string>& map, AppManifest& manifest);
}

View File

@ -1,9 +1,11 @@
#include <Tactility/Paths.h> #include <Tactility/Paths.h>
#include <Tactility/app/App.h> #include <Tactility/app/AppManifestParsing.h>
#include <Tactility/MountPoints.h> #include <Tactility/MountPoints.h>
#include <Tactility/hal/sdcard/SdCardDevice.h> #include <Tactility/hal/sdcard/SdCardDevice.h>
#include <format>
namespace tt { namespace tt {
bool findFirstMountedSdCardPath(std::string& path) { bool findFirstMountedSdCardPath(std::string& path) {

View File

@ -176,10 +176,10 @@ static void registerInstalledApp(std::string path) {
} }
app::addApp({ app::addApp({
.id = app_id_entry->second, .appId = app_id_entry->second,
.name = app_name_entry->second, .appName = app_name_entry->second,
.category = app::Category::User, .appCategory = app::Category::User,
.location = app::Location::external(path) .appLocation = app::Location::external(path)
}); });
} }
@ -289,7 +289,7 @@ void run(const Configuration& config) {
TT_LOG_I(TAG, "Starting boot app"); TT_LOG_I(TAG, "Starting boot app");
// The boot app takes care of registering system apps, user services and user apps // The boot app takes care of registering system apps, user services and user apps
addApp(app::boot::manifest); addApp(app::boot::manifest);
service::loader::startApp(app::boot::manifest.id); service::loader::startApp(app::boot::manifest.appId);
TT_LOG_I(TAG, "Main dispatcher ready"); TT_LOG_I(TAG, "Main dispatcher ready");
while (true) { while (true) {

View File

@ -1,4 +1,3 @@
#include <regex>
#include <Tactility/app/App.h> #include <Tactility/app/App.h>
#include <Tactility/service/loader/Loader.h> #include <Tactility/service/loader/Loader.h>
@ -20,9 +19,4 @@ std::shared_ptr<App> _Nullable getCurrentApp() {
return service::loader::getCurrentApp(); return service::loader::getCurrentApp();
} }
bool isValidId(const std::string& id) {
const auto pattern = std::regex("^[a-zA-Z0-9_-\\.]+$");
return std::regex_match(id, pattern);
}
} }

View File

@ -1,8 +1,8 @@
#include "Tactility/Paths.h" #include "Tactility/Paths.h"
#include <Tactility/app/App.h> #include <Tactility/app/App.h>
#include <Tactility/app/AppManifestParsing.h>
#include <Tactility/MountPoints.h>
#include <Tactility/app/AppManifest.h> #include <Tactility/app/AppManifest.h>
#include <Tactility/app/AppRegistration.h> #include <Tactility/app/AppRegistration.h>
#include <Tactility/file/File.h> #include <Tactility/file/File.h>
@ -105,92 +105,103 @@ static bool untar(const std::string& tarPath, const std::string& destinationPath
return success; return success;
} }
bool install(const std::string& path) { void cleanupInstallDirectory(const std::string& path) {
// TODO: Make better: lock for each path type properly (source vs target) const auto lock = file::getLock(path);
lock->lock();
if (!file::deleteRecursively(path)) {
TT_LOG_W(TAG, "Failed to delete existing installation at %s", path.c_str());
}
lock->unlock();
}
bool install(const std::string& path) {
// We lock and unlock frequently because SPI SD card devices share // We lock and unlock frequently because SPI SD card devices share
// the lock with the display. We don't want to lock the display for very long. // the lock with the display. We don't want to lock the display for very long.
auto app_parent_path = getAppInstallPath(); auto app_parent_path = getAppInstallPath();
TT_LOG_I(TAG, "Installing app %s to %s", path.c_str(), app_parent_path.c_str()); TT_LOG_I(TAG, "Installing app %s to %s", path.c_str(), app_parent_path.c_str());
auto lock = file::getLock(app_parent_path)->asScopedLock(); auto target_path_lock = file::getLock(app_parent_path)->asScopedLock();
lock.lock(); target_path_lock.lock();
auto filename = file::getLastPathSegment(path); auto filename = file::getLastPathSegment(path);
const std::string app_target_path = std::format("{}/{}", app_parent_path, filename); const std::string app_target_path = std::format("{}/{}", app_parent_path, filename);
if (file::isDirectory(app_target_path) && !file::deleteRecursively(app_target_path)) { if (file::isDirectory(app_target_path) && !file::deleteRecursively(app_target_path)) {
TT_LOG_W(TAG, "Failed to delete %s", app_target_path.c_str()); TT_LOG_W(TAG, "Failed to delete %s", app_target_path.c_str());
} }
lock.unlock(); target_path_lock.unlock();
lock.lock(); target_path_lock.lock();
if (!file::findOrCreateDirectory(app_target_path, 0777)) { if (!file::findOrCreateDirectory(app_target_path, 0777)) {
TT_LOG_I(TAG, "Failed to create directory %s", app_target_path.c_str()); TT_LOG_I(TAG, "Failed to create directory %s", app_target_path.c_str());
return false; return false;
} }
lock.unlock(); target_path_lock.unlock();
lock.lock(); auto source_path_lock = file::getLock(path)->asScopedLock();
target_path_lock.lock();
source_path_lock.lock();
TT_LOG_I(TAG, "Extracting app from %s to %s", path.c_str(), app_target_path.c_str()); TT_LOG_I(TAG, "Extracting app from %s to %s", path.c_str(), app_target_path.c_str());
if (!untar(path, app_target_path)) { if (!untar(path, app_target_path)) {
TT_LOG_E(TAG, "Failed to extract"); TT_LOG_E(TAG, "Failed to extract");
return false; return false;
} }
lock.unlock(); source_path_lock.unlock();
target_path_lock.unlock();
lock.lock(); target_path_lock.lock();
auto manifest_path = app_target_path + "/manifest.properties"; auto manifest_path = app_target_path + "/manifest.properties";
if (!file::isFile(manifest_path)) { if (!file::isFile(manifest_path)) {
TT_LOG_E(TAG, "Manifest not found at %s", manifest_path.c_str()); TT_LOG_E(TAG, "Manifest not found at %s", manifest_path.c_str());
cleanupInstallDirectory(app_target_path);
return false; return false;
} }
lock.unlock(); target_path_lock.unlock();
lock.lock(); target_path_lock.lock();
std::map<std::string, std::string> properties; std::map<std::string, std::string> properties;
if (!file::loadPropertiesFile(manifest_path, properties)) { if (!file::loadPropertiesFile(manifest_path, properties)) {
TT_LOG_E(TAG, "Failed to load manifest at %s", manifest_path.c_str()); TT_LOG_E(TAG, "Failed to load manifest at %s", manifest_path.c_str());
cleanupInstallDirectory(app_target_path);
return false; return false;
} }
lock.unlock(); target_path_lock.unlock();
auto app_id_iterator = properties.find("[app]id"); AppManifest manifest;
if (app_id_iterator == properties.end()) { if (!parseManifest(properties, manifest)) {
TT_LOG_E(TAG, "Failed to find app id in manifest"); TT_LOG_W(TAG, "Invalid manifest");
cleanupInstallDirectory(app_target_path);
return false; return false;
} }
auto app_name_entry = properties.find("[app]name"); TT_LOG_I(TAG, "1");
if (app_name_entry == properties.end()) {
TT_LOG_E(TAG, "Failed to find app name in manifest");
return false;
}
lock.lock(); target_path_lock.lock();
const std::string renamed_target_path = std::format("{}/{}", app_parent_path, app_id_iterator->second); const std::string renamed_target_path = std::format("{}/{}", app_parent_path, manifest.appId);
if (file::isDirectory(renamed_target_path)) { if (file::isDirectory(renamed_target_path)) {
if (!file::deleteRecursively(renamed_target_path)) { if (!file::deleteRecursively(renamed_target_path)) {
TT_LOG_W(TAG, "Failed to delete existing installation at %s", renamed_target_path.c_str()); TT_LOG_W(TAG, "Failed to delete existing installation at %s", renamed_target_path.c_str());
cleanupInstallDirectory(app_target_path);
return false; return false;
} }
} }
lock.unlock(); target_path_lock.unlock();
lock.lock(); TT_LOG_I(TAG, "2");
target_path_lock.lock();
if (rename(app_target_path.c_str(), renamed_target_path.c_str()) != 0) { if (rename(app_target_path.c_str(), renamed_target_path.c_str()) != 0) {
TT_LOG_E(TAG, "Failed to rename %s to %s", app_target_path.c_str(), app_id_iterator->second.c_str()); TT_LOG_E(TAG, "Failed to rename \"%s\" to \"%s\"", app_target_path.c_str(), manifest.appId.c_str());
cleanupInstallDirectory(app_target_path);
return false; return false;
} }
lock.unlock(); target_path_lock.unlock();
addApp({ manifest.appLocation = Location::external(renamed_target_path);
.id = app_id_iterator->second,
.name = app_name_entry->second,
.category = Category::User,
.location = Location::external(renamed_target_path)
});
TT_LOG_I(TAG, "3");
addApp(manifest);
TT_LOG_I(TAG, "4");
return true; return true;
} }

View File

@ -12,38 +12,38 @@
namespace tt::app { namespace tt::app {
std::string AppInstancePaths::getDataDirectory() const { std::string AppInstancePaths::getDataDirectory() const {
return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.id; return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.appId;
} }
std::string AppInstancePaths::getDataDirectoryLvgl() const { std::string AppInstancePaths::getDataDirectoryLvgl() const {
return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.id; return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.appId;
} }
std::string AppInstancePaths::getDataPath(const std::string& childPath) const { std::string AppInstancePaths::getDataPath(const std::string& childPath) const {
assert(!childPath.starts_with('/')); assert(!childPath.starts_with('/'));
return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath; return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.appId + '/' + childPath;
} }
std::string AppInstancePaths::getDataPathLvgl(const std::string& childPath) const { std::string AppInstancePaths::getDataPathLvgl(const std::string& childPath) const {
assert(!childPath.starts_with('/')); assert(!childPath.starts_with('/'));
return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath; return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.appId + '/' + childPath;
} }
std::string AppInstancePaths::getSystemDirectory() const { std::string AppInstancePaths::getSystemDirectory() const {
return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.id; return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.appId;
} }
std::string AppInstancePaths::getSystemDirectoryLvgl() const { std::string AppInstancePaths::getSystemDirectoryLvgl() const {
return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.id; return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.appId;
} }
std::string AppInstancePaths::getSystemPath(const std::string& childPath) const { std::string AppInstancePaths::getSystemPath(const std::string& childPath) const {
assert(!childPath.starts_with('/')); assert(!childPath.starts_with('/'));
return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath; return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.appId + '/' + childPath;
} }
std::string AppInstancePaths::getSystemPathLvgl(const std::string& childPath) const { std::string AppInstancePaths::getSystemPathLvgl(const std::string& childPath) const {
return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath; return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.appId + '/' + childPath;
} }
} }

View File

@ -0,0 +1,144 @@
#include <Tactility/app/AppManifestParsing.h>
#include <regex>
namespace tt::app {
constexpr auto* TAG = "App";
static bool validateString(const std::string& value, const std::function<bool(const char)>& isValidChar) {
for (const auto& c : value) {
if (!isValidChar(c)) {
return false;
}
}
return true;
}
static bool getValueFromManifest(const std::map<std::string, std::string>& map, const std::string& key, std::string& output) {
const auto iterator = map.find(key);
if (iterator == map.end()) {
TT_LOG_E(TAG, "Failed to find %s in manifest", key.c_str());
return false;
}
output = iterator->second;
return true;
}
bool isValidId(const std::string& id) {
return id.size() >= 5 && validateString(id, [](const char c) {
return std::isalnum(c) != 0 || c == '.';
});
}
static bool isValidManifestVersion(const std::string& version) {
return version.size() > 0 && validateString(version, [](const char c) {
return std::isalnum(c) != 0 || c == '.';
});
}
static bool isValidAppVersionName(const std::string& version) {
return version.size() > 0 && validateString(version, [](const char c) {
return std::isalnum(c) != 0 || c == '.' || c == '-' || c == '_';
});
}
static bool isValidAppVersionCode(const std::string& version) {
return version.size() > 0 && validateString(version, [](const char c) {
return std::isdigit(c) != 0;
});
}
static bool isValidName(const std::string& name) {
return name.size() >= 2 && validateString(name, [](const char c) {
return std::isalnum(c) != 0 || c == ' ' || c == '-';
});
}
bool parseManifest(const std::map<std::string, std::string>& map, AppManifest& manifest) {
TT_LOG_I(TAG, "Parsing manifest");
// [manifest]
if (!getValueFromManifest(map, "[manifest]version", manifest.manifestVersion)) {
return false;
}
TT_LOG_I(TAG, "a");
if (!isValidManifestVersion(manifest.manifestVersion)) {
TT_LOG_E(TAG, "Invalid version");
return false;
}
TT_LOG_I(TAG, "b");
// [app]
if (!getValueFromManifest(map, "[app]id", manifest.appId)) {
return false;
}
TT_LOG_I(TAG, "c");
if (!isValidId(manifest.appId)) {
TT_LOG_E(TAG, "Invalid app id");
return false;
}
TT_LOG_I(TAG, "d");
if (!getValueFromManifest(map, "[app]name", manifest.appName)) {
return false;
}
TT_LOG_I(TAG, "e");
if (!isValidName(manifest.appName)) {
TT_LOG_I(TAG, "Invalid app name");
return false;
}
TT_LOG_I(TAG, "f");
if (!getValueFromManifest(map, "[app]versionName", manifest.appVersionName)) {
return false;
}
TT_LOG_I(TAG, "g");
if (!isValidAppVersionName(manifest.appVersionName)) {
TT_LOG_E(TAG, "Invalid app version name");
return false;
}
TT_LOG_I(TAG, "h");
std::string version_code_string;
if (!getValueFromManifest(map, "[app]versionCode", version_code_string)) {
return false;
}
TT_LOG_I(TAG, "i");
if (!isValidAppVersionCode(version_code_string)) {
TT_LOG_E(TAG, "Invalid app version code");
return false;
}
manifest.appVersionCode = std::stoull(version_code_string);
// [target]
TT_LOG_I(TAG, "j");
if (!getValueFromManifest(map, "[target]sdk", manifest.targetSdk)) {
return false;
}
TT_LOG_I(TAG, "k");
if (!getValueFromManifest(map, "[target]platforms", manifest.targetPlatforms)) {
return false;
}
TT_LOG_I(TAG, "l");
// Defaults
manifest.appCategory = Category::User;
manifest.appLocation = Location::external("");
return true;
}
}

View File

@ -16,15 +16,15 @@ static AppManifestMap app_manifest_map;
static Mutex hash_mutex(Mutex::Type::Normal); static Mutex hash_mutex(Mutex::Type::Normal);
void addApp(const AppManifest& manifest) { void addApp(const AppManifest& manifest) {
TT_LOG_I(TAG, "Registering manifest %s", manifest.id.c_str()); TT_LOG_I(TAG, "Registering manifest %s", manifest.appId.c_str());
hash_mutex.lock(); hash_mutex.lock();
if (app_manifest_map.contains(manifest.id)) { if (app_manifest_map.contains(manifest.appId)) {
TT_LOG_W(TAG, "Overwriting existing manifest for %s", manifest.id.c_str()); TT_LOG_W(TAG, "Overwriting existing manifest for %s", manifest.appId.c_str());
} }
app_manifest_map[manifest.id] = std::make_shared<AppManifest>(manifest); app_manifest_map[manifest.appId] = std::make_shared<AppManifest>(manifest);
hash_mutex.unlock(); hash_mutex.unlock();
} }

View File

@ -223,8 +223,8 @@ void setElfAppParameters(
std::shared_ptr<App> createElfApp(const std::shared_ptr<AppManifest>& manifest) { std::shared_ptr<App> createElfApp(const std::shared_ptr<AppManifest>& manifest) {
TT_LOG_I(TAG, "createElfApp"); TT_LOG_I(TAG, "createElfApp");
assert(manifest != nullptr); assert(manifest != nullptr);
assert(manifest->location.isExternal()); assert(manifest->appLocation.isExternal());
return std::make_shared<ElfApp>(manifest->location.getPath()); return std::make_shared<ElfApp>(manifest->appLocation.getPath());
} }
} // namespace } // namespace

View File

@ -172,16 +172,16 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "AddGps", .appId = "AddGps",
.name = "Add GPS", .appName = "Add GPS",
.icon = LV_SYMBOL_GPS, .appIcon = LV_SYMBOL_GPS,
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<AddGpsApp> .createApp = create<AddGpsApp>
}; };
void start() { void start() {
app::start(manifest.id); app::start(manifest.appId);
} }
} // namespace } // namespace

View File

@ -28,7 +28,7 @@ LaunchId start(const std::string& title, const std::string& message, const std::
bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title); bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title);
bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message); bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message);
bundle->putString(PARAMETER_BUNDLE_KEY_BUTTON_LABELS, items_joined); bundle->putString(PARAMETER_BUNDLE_KEY_BUTTON_LABELS, items_joined);
return service::loader::startApp(manifest.id, bundle); return service::loader::startApp(manifest.appId, bundle);
} }
LaunchId start(const std::string& title, const std::string& message) { LaunchId start(const std::string& title, const std::string& message) {
@ -36,7 +36,7 @@ LaunchId start(const std::string& title, const std::string& message) {
bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title); bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title);
bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message); bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message);
bundle->putString(PARAMETER_BUNDLE_KEY_BUTTON_LABELS, "OK"); bundle->putString(PARAMETER_BUNDLE_KEY_BUTTON_LABELS, "OK");
return service::loader::startApp(manifest.id, bundle); return service::loader::startApp(manifest.appId, bundle);
} }
int32_t getResultIndex(const Bundle& bundle) { int32_t getResultIndex(const Bundle& bundle) {
@ -126,10 +126,10 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "AlertDialog", .appId = "AlertDialog",
.name = "Alert Dialog", .appName = "Alert Dialog",
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<AlertDialogApp> .createApp = create<AlertDialogApp>
}; };

View File

@ -13,12 +13,12 @@ class AppListApp : public App {
static void onAppPressed(lv_event_t* e) { static void onAppPressed(lv_event_t* e) {
const auto* manifest = static_cast<const AppManifest*>(lv_event_get_user_data(e)); const auto* manifest = static_cast<const AppManifest*>(lv_event_get_user_data(e));
service::loader::startApp(manifest->id); service::loader::startApp(manifest->appId);
} }
static void createAppWidget(const std::shared_ptr<AppManifest>& manifest, lv_obj_t* list) { static void createAppWidget(const std::shared_ptr<AppManifest>& manifest, lv_obj_t* list) {
const void* icon = !manifest->icon.empty() ? manifest->icon.c_str() : TT_ASSETS_APP_ICON_FALLBACK; const void* icon = !manifest->appIcon.empty() ? manifest->appIcon.c_str() : TT_ASSETS_APP_ICON_FALLBACK;
lv_obj_t* btn = lv_list_add_button(list, icon, manifest->name.c_str()); lv_obj_t* btn = lv_list_add_button(list, icon, manifest->appName.c_str());
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, manifest.get()); lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, manifest.get());
} }
@ -40,8 +40,8 @@ public:
std::ranges::sort(manifests, SortAppManifestByName); std::ranges::sort(manifests, SortAppManifestByName);
for (const auto& manifest: manifests) { for (const auto& manifest: manifests) {
bool is_valid_category = (manifest->category == Category::User) || (manifest->category == Category::System); bool is_valid_category = (manifest->appCategory == Category::User) || (manifest->appCategory == Category::System);
bool is_visible = (manifest->flags & AppManifest::Flags::Hidden) == 0u; bool is_visible = (manifest->appFlags & AppManifest::Flags::Hidden) == 0u;
if (is_valid_category && is_visible) { if (is_valid_category && is_visible) {
createAppWidget(manifest, list); createAppWidget(manifest, list);
} }
@ -50,10 +50,10 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "AppList", .appId = "AppList",
.name = "Apps", .appName = "Apps",
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<AppListApp>, .createApp = create<AppListApp>,
}; };

View File

@ -174,10 +174,10 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Boot", .appId = "Boot",
.name = "Boot", .appName = "Boot",
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden,
.createApp = create<BootApp> .createApp = create<BootApp>
}; };

View File

@ -224,9 +224,9 @@ class CalculatorApp : public App {
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Calculator", .appId = "Calculator",
.name = "Calculator", .appName = "Calculator",
.icon = TT_ASSETS_APP_ICON_CALCULATOR, .appIcon = TT_ASSETS_APP_ICON_CALCULATOR,
.createApp = create<CalculatorApp> .createApp = create<CalculatorApp>
}; };

View File

@ -136,9 +136,9 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Chat", .appId = "Chat",
.name = "Chat", .appName = "Chat",
.icon = TT_ASSETS_APP_ICON_CHAT, .appIcon = TT_ASSETS_APP_ICON_CHAT,
.createApp = create<ChatApp> .createApp = create<ChatApp>
}; };

View File

@ -122,15 +122,15 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "CrashDiagnostics", .appId = "CrashDiagnostics",
.name = "Crash Diagnostics", .appName = "Crash Diagnostics",
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<CrashDiagnosticsApp> .createApp = create<CrashDiagnosticsApp>
}; };
void start() { void start() {
service::loader::startApp(manifest.id); service::loader::startApp(manifest.appId);
} }
} // namespace } // namespace

View File

@ -160,14 +160,14 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Development", .appId = "Development",
.name = "Development", .appName = "Development",
.category = Category::Settings, .appCategory = Category::Settings,
.createApp = create<DevelopmentApp> .createApp = create<DevelopmentApp>
}; };
void start() { void start() {
app::start(manifest.id); app::start(manifest.appId);
} }
} // namespace } // namespace

View File

@ -164,10 +164,10 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Display", .appId = "Display",
.name = "Display", .appName = "Display",
.icon = TT_ASSETS_APP_ICON_DISPLAY_SETTINGS, .appIcon = TT_ASSETS_APP_ICON_DISPLAY_SETTINGS,
.category = Category::Settings, .appCategory = Category::Settings,
.createApp = create<DisplayApp> .createApp = create<DisplayApp>
}; };

View File

@ -33,16 +33,16 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Files", .appId = "Files",
.name = "Files", .appName = "Files",
.icon = TT_ASSETS_APP_ICON_FILES, .appIcon = TT_ASSETS_APP_ICON_FILES,
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<FilesApp> .createApp = create<FilesApp>
}; };
void start() { void start() {
service::loader::startApp(manifest.id); service::loader::startApp(manifest.appId);
} }
} // namespace } // namespace

View File

@ -56,24 +56,24 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "FileSelection", .appId = "FileSelection",
.name = "File Selection", .appName = "File Selection",
.icon = TT_ASSETS_APP_ICON_FILES, .appIcon = TT_ASSETS_APP_ICON_FILES,
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<FileSelection> .createApp = create<FileSelection>
}; };
LaunchId startForExistingFile() { LaunchId startForExistingFile() {
auto bundle = std::make_shared<Bundle>(); auto bundle = std::make_shared<Bundle>();
setMode(*bundle, Mode::Existing); setMode(*bundle, Mode::Existing);
return service::loader::startApp(manifest.id, bundle); return service::loader::startApp(manifest.appId, bundle);
} }
LaunchId startForExistingOrNewFile() { LaunchId startForExistingOrNewFile() {
auto bundle = std::make_shared<Bundle>(); auto bundle = std::make_shared<Bundle>();
setMode(*bundle, Mode::ExistingOrNew); setMode(*bundle, Mode::ExistingOrNew);
return service::loader::startApp(manifest.id, bundle); return service::loader::startApp(manifest.appId, bundle);
} }
} // namespace } // namespace

View File

@ -188,10 +188,10 @@ void GpioApp::onHide(AppContext& app) {
} }
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Gpio", .appId = "Gpio",
.name = "GPIO", .appName = "GPIO",
.icon = TT_ASSETS_APP_ICON_GPIO, .appIcon = TT_ASSETS_APP_ICON_GPIO,
.category = Category::System, .appCategory = Category::System,
.createApp = create<GpioApp> .createApp = create<GpioApp>
}; };

View File

@ -58,7 +58,7 @@ class GpsSettingsApp final : public App {
} }
void onAddGps() { void onAddGps() {
app::start(addgps::manifest.id); app::start(addgps::manifest.appId);
} }
void startReceivingUpdates() { void startReceivingUpdates() {
@ -344,15 +344,15 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "GpsSettings", .appId = "GpsSettings",
.name = "GPS", .appName = "GPS",
.icon = LV_SYMBOL_GPS, .appIcon = LV_SYMBOL_GPS,
.category = Category::Settings, .appCategory = Category::Settings,
.createApp = create<GpsSettingsApp> .createApp = create<GpsSettingsApp>
}; };
void start() { void start() {
app::start(manifest.id); app::start(manifest.appId);
} }
} // namespace } // namespace

View File

@ -69,7 +69,7 @@ public:
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */ /** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
std::shared_ptr<I2cScannerApp> _Nullable optApp() { std::shared_ptr<I2cScannerApp> _Nullable optApp() {
auto appContext = getCurrentAppContext(); auto appContext = getCurrentAppContext();
if (appContext != nullptr && appContext->getManifest().id == manifest.id) { if (appContext != nullptr && appContext->getManifest().appId == manifest.appId) {
return std::static_pointer_cast<I2cScannerApp>(appContext->getApp()); return std::static_pointer_cast<I2cScannerApp>(appContext->getApp());
} else { } else {
return nullptr; return nullptr;
@ -403,15 +403,15 @@ void I2cScannerApp::onScanTimerFinished() {
} }
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "I2cScanner", .appId = "I2cScanner",
.name = "I2C Scanner", .appName = "I2C Scanner",
.icon = TT_ASSETS_APP_ICON_I2C_SETTINGS, .appIcon = TT_ASSETS_APP_ICON_I2C_SETTINGS,
.category = Category::System, .appCategory = Category::System,
.createApp = create<I2cScannerApp> .createApp = create<I2cScannerApp>
}; };
void start() { void start() {
service::loader::startApp(manifest.id); service::loader::startApp(manifest.appId);
} }
} // namespace } // namespace

View File

@ -93,10 +93,10 @@ class I2cSettingsApp : public App {
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "I2cSettings", .appId = "I2cSettings",
.name = "I2C", .appName = "I2C",
.icon = TT_ASSETS_APP_ICON_I2C_SETTINGS, .appIcon = TT_ASSETS_APP_ICON_I2C_SETTINGS,
.category = Category::Settings, .appCategory = Category::Settings,
.createApp = create<I2cSettingsApp> .createApp = create<I2cSettingsApp>
}; };

View File

@ -60,17 +60,17 @@ class ImageViewerApp : public App {
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "ImageViewer", .appId = "ImageViewer",
.name = "Image Viewer", .appName = "Image Viewer",
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<ImageViewerApp> .createApp = create<ImageViewerApp>
}; };
void start(const std::string& file) { void start(const std::string& file) {
auto parameters = std::make_shared<Bundle>(); auto parameters = std::make_shared<Bundle>();
parameters->putString(IMAGE_VIEWER_FILE_ARGUMENT, file); parameters->putString(IMAGE_VIEWER_FILE_ARGUMENT, file);
service::loader::startApp(manifest.id, parameters); service::loader::startApp(manifest.appId, parameters);
} }
} // namespace } // namespace

View File

@ -26,7 +26,7 @@ void start(const std::string& title, const std::string& message, const std::stri
bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title); bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title);
bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message); bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message);
bundle->putString(PARAMETER_BUNDLE_KEY_PREFILLED, prefilled); bundle->putString(PARAMETER_BUNDLE_KEY_PREFILLED, prefilled);
service::loader::startApp(manifest.id, bundle); service::loader::startApp(manifest.appId, bundle);
} }
std::string getResult(const Bundle& bundle) { std::string getResult(const Bundle& bundle) {
@ -118,10 +118,10 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "InputDialog", .appId = "InputDialog",
.name = "Input Dialog", .appName = "Input Dialog",
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<InputDialogApp> .createApp = create<InputDialogApp>
}; };

View File

@ -136,15 +136,15 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Launcher", .appId = "Launcher",
.name = "Launcher", .appName = "Launcher",
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<LauncherApp> .createApp = create<LauncherApp>
}; };
void start() { void start() {
service::loader::startApp(manifest.id); service::loader::startApp(manifest.appId);
} }
} // namespace } // namespace

View File

@ -162,15 +162,15 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "LocaleSettings", .appId = "LocaleSettings",
.name = "Region & Language", .appName = "Region & Language",
.icon = TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS, .appIcon = TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS,
.category = Category::Settings, .appCategory = Category::Settings,
.createApp = create<LocaleSettingsApp> .createApp = create<LocaleSettingsApp>
}; };
void start() { void start() {
service::loader::startApp(manifest.id); service::loader::startApp(manifest.appId);
} }
} // namespace } // namespace

View File

@ -120,10 +120,10 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Log", .appId = "Log",
.name = "Log", .appName = "Log",
.icon = LV_SYMBOL_LIST, .appIcon = LV_SYMBOL_LIST,
.category = Category::System, .appCategory = Category::System,
.createApp = create<LogApp> .createApp = create<LogApp>
}; };

View File

@ -207,15 +207,15 @@ class NotesApp : public App {
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Notes", .appId = "Notes",
.name = "Notes", .appName = "Notes",
.icon = TT_ASSETS_APP_ICON_NOTES, .appIcon = TT_ASSETS_APP_ICON_NOTES,
.createApp = create<NotesApp> .createApp = create<NotesApp>
}; };
void start(const std::string& filePath) { void start(const std::string& filePath) {
auto parameters = std::make_shared<Bundle>(); auto parameters = std::make_shared<Bundle>();
parameters->putString(NOTES_FILE_ARGUMENT, filePath); parameters->putString(NOTES_FILE_ARGUMENT, filePath);
service::loader::startApp(manifest.id, parameters); service::loader::startApp(manifest.appId, parameters);
} }
} // namespace tt::app::notes } // namespace tt::app::notes

View File

@ -22,7 +22,7 @@ class PowerApp;
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */ /** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
std::shared_ptr<PowerApp> _Nullable optApp() { std::shared_ptr<PowerApp> _Nullable optApp() {
auto appContext = getCurrentAppContext(); auto appContext = getCurrentAppContext();
if (appContext != nullptr && appContext->getManifest().id == manifest.id) { if (appContext != nullptr && appContext->getManifest().appId == manifest.appId) {
return std::static_pointer_cast<PowerApp>(appContext->getApp()); return std::static_pointer_cast<PowerApp>(appContext->getApp());
} else { } else {
return nullptr; return nullptr;
@ -189,10 +189,10 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Power", .appId = "Power",
.name = "Power", .appName = "Power",
.icon = TT_ASSETS_APP_ICON_POWER_SETTINGS, .appIcon = TT_ASSETS_APP_ICON_POWER_SETTINGS,
.category = Category::Settings, .appCategory = Category::Settings,
.createApp = create<PowerApp> .createApp = create<PowerApp>
}; };

View File

@ -48,7 +48,7 @@ public:
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */ /** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
std::shared_ptr<ScreenshotApp> _Nullable optApp() { std::shared_ptr<ScreenshotApp> _Nullable optApp() {
auto appContext = getCurrentAppContext(); auto appContext = getCurrentAppContext();
if (appContext != nullptr && appContext->getManifest().id == manifest.id) { if (appContext != nullptr && appContext->getManifest().appId == manifest.appId) {
return std::static_pointer_cast<ScreenshotApp>(appContext->getApp()); return std::static_pointer_cast<ScreenshotApp>(appContext->getApp());
} else { } else {
return nullptr; return nullptr;
@ -278,10 +278,10 @@ void ScreenshotApp::onShow(AppContext& appContext, lv_obj_t* parent) {
} }
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Screenshot", .appId = "Screenshot",
.name = "Screenshot", .appName = "Screenshot",
.icon = LV_SYMBOL_IMAGE, .appIcon = LV_SYMBOL_IMAGE,
.category = Category::System, .appCategory = Category::System,
.createApp = create<ScreenshotApp> .createApp = create<ScreenshotApp>
}; };

View File

@ -26,7 +26,7 @@ void start(const std::string& title, const std::vector<std::string>& items) {
auto bundle = std::make_shared<Bundle>(); auto bundle = std::make_shared<Bundle>();
bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title); bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title);
bundle->putString(PARAMETER_BUNDLE_KEY_ITEMS, items_joined); bundle->putString(PARAMETER_BUNDLE_KEY_ITEMS, items_joined);
service::loader::startApp(manifest.id, bundle); service::loader::startApp(manifest.appId, bundle);
} }
int32_t getResultIndex(const Bundle& bundle) { int32_t getResultIndex(const Bundle& bundle) {
@ -112,10 +112,10 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "SelectionDialog", .appId = "SelectionDialog",
.name = "Selection Dialog", .appName = "Selection Dialog",
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<SelectionDialogApp> .createApp = create<SelectionDialogApp>
}; };

View File

@ -85,10 +85,10 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "SerialConsole", .appId = "SerialConsole",
.name = "Serial Console", .appName = "Serial Console",
.icon = LV_SYMBOL_LIST, .appIcon = LV_SYMBOL_LIST,
.category = Category::System, .appCategory = Category::System,
.createApp = create<SerialConsoleApp> .createApp = create<SerialConsoleApp>
}; };

View File

@ -12,14 +12,14 @@ namespace tt::app::settings {
static void onAppPressed(lv_event_t* e) { static void onAppPressed(lv_event_t* e) {
const auto* manifest = static_cast<const AppManifest*>(lv_event_get_user_data(e)); const auto* manifest = static_cast<const AppManifest*>(lv_event_get_user_data(e));
service::loader::startApp(manifest->id); service::loader::startApp(manifest->appId);
} }
static void createWidget(const std::shared_ptr<AppManifest>& manifest, void* parent) { static void createWidget(const std::shared_ptr<AppManifest>& manifest, void* parent) {
tt_check(parent); tt_check(parent);
auto* list = (lv_obj_t*)parent; auto* list = (lv_obj_t*)parent;
const void* icon = !manifest->icon.empty() ? manifest->icon.c_str() : TT_ASSETS_APP_ICON_FALLBACK; const void* icon = !manifest->appIcon.empty() ? manifest->appIcon.c_str() : TT_ASSETS_APP_ICON_FALLBACK;
auto* btn = lv_list_add_button(list, icon, manifest->name.c_str()); auto* btn = lv_list_add_button(list, icon, manifest->appName.c_str());
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)manifest.get()); lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)manifest.get());
} }
@ -38,7 +38,7 @@ class SettingsApp : public App {
auto manifests = getApps(); auto manifests = getApps();
std::sort(manifests.begin(), manifests.end(), SortAppManifestByName); std::sort(manifests.begin(), manifests.end(), SortAppManifestByName);
for (const auto& manifest: manifests) { for (const auto& manifest: manifests) {
if (manifest->category == Category::Settings) { if (manifest->appCategory == Category::Settings) {
createWidget(manifest, list); createWidget(manifest, list);
} }
} }
@ -46,11 +46,11 @@ class SettingsApp : public App {
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Settings", .appId = "Settings",
.name = "Settings", .appName = "Settings",
.icon = TT_ASSETS_APP_ICON_SETTINGS, .appIcon = TT_ASSETS_APP_ICON_SETTINGS,
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<SettingsApp> .createApp = create<SettingsApp>
}; };

View File

@ -292,10 +292,10 @@ class SystemInfoApp final : public App {
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "SystemInfo", .appId = "SystemInfo",
.name = "System Info", .appName = "System Info",
.icon = TT_ASSETS_APP_ICON_SYSTEM_INFO, .appIcon = TT_ASSETS_APP_ICON_SYSTEM_INFO,
.category = Category::System, .appCategory = Category::System,
.createApp = create<SystemInfoApp> .createApp = create<SystemInfoApp>
}; };

View File

@ -57,15 +57,15 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "TimeDateSettings", .appId = "TimeDateSettings",
.name = "Time & Date", .appName = "Time & Date",
.icon = TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS, .appIcon = TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS,
.category = Category::Settings, .appCategory = Category::Settings,
.createApp = create<TimeDateSettingsApp> .createApp = create<TimeDateSettingsApp>
}; };
void start() { void start() {
service::loader::startApp(manifest.id); service::loader::startApp(manifest.appId);
} }
} // namespace } // namespace

View File

@ -226,15 +226,15 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "TimeZone", .appId = "TimeZone",
.name = "Select timezone", .appName = "Select timezone",
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<TimeZoneApp> .createApp = create<TimeZoneApp>
}; };
void start() { void start() {
service::loader::startApp(manifest.id); service::loader::startApp(manifest.appId);
} }
} }

View File

@ -42,10 +42,10 @@ class UsbSettingsApp : public App {
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "UsbSettings", .appId = "UsbSettings",
.name = "USB", .appName = "USB",
.icon = LV_SYMBOL_USB, .appIcon = LV_SYMBOL_USB,
.category = Category::Settings, .appCategory = Category::Settings,
.createApp = create<UsbSettingsApp> .createApp = create<UsbSettingsApp>
}; };

View File

@ -21,7 +21,7 @@ extern const AppManifest manifest;
void start(const std::string& ssid) { void start(const std::string& ssid) {
auto bundle = std::make_shared<Bundle>(); auto bundle = std::make_shared<Bundle>();
bundle->putString("ssid", ssid); bundle->putString("ssid", ssid);
app::start(manifest.id, bundle); app::start(manifest.appId, bundle);
} }
class WifiApSettings : public App { class WifiApSettings : public App {
@ -241,11 +241,11 @@ public:
}; };
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "WifiApSettings", .appId = "WifiApSettings",
.name = "Wi-Fi AP Settings", .appName = "Wi-Fi AP Settings",
.icon = LV_SYMBOL_WIFI, .appIcon = LV_SYMBOL_WIFI,
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<WifiApSettings> .createApp = create<WifiApSettings>
}; };

View File

@ -94,11 +94,11 @@ void WifiConnect::onHide(TT_UNUSED AppContext& app) {
} }
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "WifiConnect", .appId = "WifiConnect",
.name = "Wi-Fi Connect", .appName = "Wi-Fi Connect",
.icon = LV_SYMBOL_WIFI, .appIcon = LV_SYMBOL_WIFI,
.category = Category::System, .appCategory = Category::System,
.flags = AppManifest::Flags::Hidden, .appFlags = AppManifest::Flags::Hidden,
.createApp = create<WifiConnect> .createApp = create<WifiConnect>
}; };
@ -106,7 +106,7 @@ void start(const std::string& ssid, const std::string& password) {
auto parameters = std::make_shared<Bundle>(); auto parameters = std::make_shared<Bundle>();
parameters->putString(WIFI_CONNECT_PARAM_SSID, ssid); parameters->putString(WIFI_CONNECT_PARAM_SSID, ssid);
parameters->putString(WIFI_CONNECT_PARAM_PASSWORD, password); parameters->putString(WIFI_CONNECT_PARAM_PASSWORD, password);
service::loader::startApp(manifest.id, parameters); service::loader::startApp(manifest.appId, parameters);
} }
bool optSsidParameter(const std::shared_ptr<const Bundle>& bundle, std::string& ssid) { bool optSsidParameter(const std::shared_ptr<const Bundle>& bundle, std::string& ssid) {

View File

@ -133,15 +133,15 @@ void WifiManage::onHide(TT_UNUSED AppContext& app) {
} }
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "WifiManage", .appId = "WifiManage",
.name = "Wi-Fi", .appName = "Wi-Fi",
.icon = LV_SYMBOL_WIFI, .appIcon = LV_SYMBOL_WIFI,
.category = Category::Settings, .appCategory = Category::Settings,
.createApp = create<WifiManage> .createApp = create<WifiManage>
}; };
void start() { void start() {
service::loader::startApp(manifest.id); service::loader::startApp(manifest.appId);
} }
} // namespace } // namespace

View File

@ -144,7 +144,7 @@ lv_obj_t* toolbar_create(lv_obj_t* parent, const std::string& title) {
} }
lv_obj_t* toolbar_create(lv_obj_t* parent, const app::AppContext& app) { lv_obj_t* toolbar_create(lv_obj_t* parent, const app::AppContext& app) {
return toolbar_create(parent, app.getManifest().name); return toolbar_create(parent, app.getManifest().appName);
} }
void toolbar_set_title(lv_obj_t* obj, const std::string& title) { void toolbar_set_title(lv_obj_t* obj, const std::string& title) {

View File

@ -193,7 +193,7 @@ void GuiService::requestDraw() {
void GuiService::showApp(std::shared_ptr<app::AppContext> app) { void GuiService::showApp(std::shared_ptr<app::AppContext> app) {
lock(); lock();
if (!isStarted) { if (!isStarted) {
TT_LOG_W(TAG, "Failed to show app %s: GUI not started", app->getManifest().id.c_str()); TT_LOG_W(TAG, "Failed to show app %s: GUI not started", app->getManifest().appId.c_str());
} else { } else {
// Ensure previous app triggers onHide() logic // Ensure previous app triggers onHide() logic
if (appToRender != nullptr) { if (appToRender != nullptr) {

View File

@ -103,7 +103,7 @@ void LoaderService::onStartAppMessage(const std::string& id, app::LaunchId launc
auto previous_app = !appStack.empty() ? appStack.top() : nullptr; auto previous_app = !appStack.empty() ? appStack.top() : nullptr;
auto new_app = std::make_shared<app::AppInstance>(app_manifest, launchId, parameters); auto new_app = std::make_shared<app::AppInstance>(app_manifest, launchId, parameters);
new_app->mutableFlags().hideStatusbar = (app_manifest->flags & app::AppManifest::Flags::HideStatusBar); new_app->mutableFlags().hideStatusbar = (app_manifest->appFlags & app::AppManifest::Flags::HideStatusBar);
appStack.push(new_app); appStack.push(new_app);
transitionAppToState(new_app, app::State::Initial); transitionAppToState(new_app, app::State::Initial);
@ -136,12 +136,12 @@ void LoaderService::onStopAppMessage(const std::string& id) {
// Stop current app // Stop current app
auto app_to_stop = appStack.top(); auto app_to_stop = appStack.top();
if (app_to_stop->getManifest().id != id) { if (app_to_stop->getManifest().appId != id) {
TT_LOG_E(TAG, "Stop app: id mismatch (wanted %s but found %s on top of stack)", id.c_str(), app_to_stop->getManifest().id.c_str()); TT_LOG_E(TAG, "Stop app: id mismatch (wanted %s but found %s on top of stack)", id.c_str(), app_to_stop->getManifest().appId.c_str());
return; return;
} }
if (original_stack_size == 1 && app_to_stop->getManifest().name != "Boot") { if (original_stack_size == 1 && app_to_stop->getManifest().appName != "Boot") {
TT_LOG_E(TAG, "Stop app: can't stop root app"); TT_LOG_E(TAG, "Stop app: can't stop root app");
return; return;
} }
@ -162,12 +162,12 @@ void LoaderService::onStopAppMessage(const std::string& id) {
// We only expect the app to be referenced within the current scope // We only expect the app to be referenced within the current scope
if (app_to_stop.use_count() > 1) { if (app_to_stop.use_count() > 1) {
TT_LOG_W(TAG, "Memory leak: Stopped %s, but use count is %ld", app_to_stop->getManifest().id.c_str(), app_to_stop.use_count() - 1); TT_LOG_W(TAG, "Memory leak: Stopped %s, but use count is %ld", app_to_stop->getManifest().appId.c_str(), app_to_stop.use_count() - 1);
} }
// Refcount is expected to be 2: 1 within app_to_stop and 1 within the current scope // Refcount is expected to be 2: 1 within app_to_stop and 1 within the current scope
if (app_to_stop->getApp().use_count() > 2) { if (app_to_stop->getApp().use_count() > 2) {
TT_LOG_W(TAG, "Memory leak: Stopped %s, but use count is %ld", app_to_stop->getManifest().id.c_str(), app_to_stop->getApp().use_count() - 2); TT_LOG_W(TAG, "Memory leak: Stopped %s, but use count is %ld", app_to_stop->getManifest().appId.c_str(), app_to_stop->getApp().use_count() - 2);
} }
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
@ -224,7 +224,7 @@ void LoaderService::transitionAppToState(const std::shared_ptr<app::AppInstance>
TT_LOG_I( TT_LOG_I(
TAG, TAG,
"App \"%s\" state: %s -> %s", "App \"%s\" state: %s -> %s",
app_manifest.id.c_str(), app_manifest.appId.c_str(),
appStateToString(old_state), appStateToString(old_state),
appStateToString(state) appStateToString(state)
); );
@ -263,7 +263,7 @@ app::LaunchId LoaderService::startApp(const std::string& id, std::shared_ptr<con
void LoaderService::stopApp() { void LoaderService::stopApp() {
TT_LOG_I(TAG, "stopApp()"); TT_LOG_I(TAG, "stopApp()");
auto id = getCurrentAppContext()->getManifest().id; auto id = getCurrentAppContext()->getManifest().appId;
dispatcherThread->dispatch([this, id]() { dispatcherThread->dispatch([this, id]() {
onStopAppMessage(id); onStopAppMessage(id);
}); });

View File

@ -84,10 +84,10 @@ void ScreenshotTask::taskMain() {
auto appContext = app::getCurrentAppContext(); auto appContext = app::getCurrentAppContext();
if (appContext != nullptr) { if (appContext != nullptr) {
const app::AppManifest& manifest = appContext->getManifest(); const app::AppManifest& manifest = appContext->getManifest();
if (manifest.id != last_app_id) { if (manifest.appId != last_app_id) {
kernel::delayMillis(100); kernel::delayMillis(100);
last_app_id = manifest.id; last_app_id = manifest.appId;
auto filename = std::format("{}/screenshot-{}.png", work.path, manifest.id); auto filename = std::format("{}/screenshot-{}.png", work.path, manifest.appId);
makeScreenshot(filename); makeScreenshot(filename);
} }
} }