From b283c3ba4d7736cc77fe865b0cdc6224f1092977 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Wed, 9 Sep 2026 18:34:08 +0200 Subject: [PATCH] Fixes and improvements --- Modules/app-module/include/app/manager.h | 43 +++++++ Modules/app-module/include/app/manifest.h | 2 +- .../app-module/private/app/private/ledger.h | 12 ++ Modules/app-module/source/install.cpp | 39 +++++-- Modules/app-module/source/manager.cpp | 100 ++++++++++++++-- Modules/app-module/source/module.cpp | 16 ++- .../source/package_manifest_parsing.cpp | 11 +- .../source/package_manifest_parsing_v3.cpp | 32 ++++++ Modules/app-module/source/paths.cpp | 28 ++++- .../Tactility/app/appdetails/AppDetails.h | 9 -- .../app/apppackagedetails/AppPackageDetails.h | 9 ++ Tactility/Source/Tactility.cpp | 8 +- .../AppPackageDetails.cpp} | 108 +++++++++++------- .../AppPackageList.cpp} | 48 ++++---- Tests/SdkIntegration/tactility.py | 3 + 15 files changed, 360 insertions(+), 108 deletions(-) delete mode 100644 Tactility/Private/Tactility/app/appdetails/AppDetails.h create mode 100644 Tactility/Private/Tactility/app/apppackagedetails/AppPackageDetails.h rename Tactility/Source/app/{appdetails/AppDetails.cpp => apppackagedetails/AppPackageDetails.cpp} (54%) rename Tactility/Source/app/{appsettings/AppSettings.cpp => apppackagelist/AppPackageList.cpp} (73%) diff --git a/Modules/app-module/include/app/manager.h b/Modules/app-module/include/app/manager.h index 95ac8b9eb..e4984f01f 100644 --- a/Modules/app-module/include/app/manager.h +++ b/Modules/app-module/include/app/manager.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -44,6 +45,48 @@ error_t app_manager_find_manifest(const char* id, struct AppManifest* out_manife typedef void (*AppManifestVisitorFn)(const struct AppManifest* manifest, void* context); void app_manager_for_each_manifest(AppManifestVisitorFn visitor, void* context); +/** + * Registers a package for enumeration via app_manager_for_each_package(). Separate from + * registering its apps - the caller still calls app_manager_add() for each AppManifest. + * @param[in] app_ids the ids of the AppManifest(s) this package registered + * @retval ERROR_INVALID_ARGUMENT a package with the same id is already registered + * @retval ERROR_NONE on success + */ +error_t app_manager_add_package(const struct PackageManifest* package, const char* const* app_ids, size_t app_id_count); + +/** + * Unregisters a previously-added package. Does not touch its apps' own registrations. + * @retval ERROR_NOT_FOUND no package with this id is registered + * @retval ERROR_NONE on success + */ +error_t app_manager_remove_package(const char* package_id); + +/** + * @param[out] out_package set to a copy of the package on success + * @retval ERROR_NOT_FOUND no package with this id is registered + * @retval ERROR_NONE on success + */ +error_t app_manager_find_package(const char* package_id, struct PackageManifest* out_package); + +/** One registered package, handed to AppPackageVisitorFn - see app_manager_for_each_package(). */ +struct AppPackage { + struct PackageManifest package; + /** How many entries @a app_ids points to. */ + size_t app_id_count; + /** Valid only for the duration of the app_manager_for_each_package() call that produced + * this - copy out what's needed before returning from the visitor. */ + const char* const* app_ids; +}; + +typedef void (*AppPackageVisitorFn)(const struct AppPackage* pkg, void* context); + +/** + * Calls @a visitor once for every registered package. Iteration order is unspecified. + * @warning Same threading contract as app_manager_for_each_manifest(): runs with an internal + * lock held - do not call any app_manager_*() function from inside @a visitor. + */ +void app_manager_for_each_package(AppPackageVisitorFn visitor, void* context); + /** One fd-to-stream binding for app_start_with_streams() (app/start.h). Every field is passed * through to app_stream_subscribe() as-is; see its own doc for the ownership contracts. */ struct AppStreamBinding { diff --git a/Modules/app-module/include/app/manifest.h b/Modules/app-module/include/app/manifest.h index 2ff2fb05a..e365d52ff 100644 --- a/Modules/app-module/include/app/manifest.h +++ b/Modules/app-module/include/app/manifest.h @@ -14,7 +14,7 @@ extern "C" { #define APP_MANIFEST_ID_LENGTH 32 // Character count, excluding null terminator -#define APP_MANIFEST_NAME_LENGTH 24 +#define APP_MANIFEST_NAME_LENGTH 32 /** Broad classification of an app, used for grouping/launcher presentation. */ enum AppCategory { diff --git a/Modules/app-module/private/app/private/ledger.h b/Modules/app-module/private/app/private/ledger.h index eab9b3dcf..eead3df14 100644 --- a/Modules/app-module/private/app/private/ledger.h +++ b/Modules/app-module/private/app/private/ledger.h @@ -3,8 +3,11 @@ #include #include +#include #include +#include + #include #include #include @@ -13,6 +16,7 @@ #include #include #include +#include /** * A dedicated completion signal(1) for one app instance's task, given as the @@ -57,8 +61,16 @@ struct AppInstanceRecord { AppFdTable fd_table {}; }; +/** A registered installed package - see app_manager_add_package() (app/manager.h). */ +struct AppPackageRecord { + struct PackageManifest package; + std::vector app_ids; +}; + struct AppLedger { std::unordered_map manifests; + // OptExternalAllocator: bigger entries than `manifests`, and unlike `instances` isn't on the app start/stop hot path. + std::unordered_map, std::equal_to, tt::OptExternalAllocator>> packages; std::unordered_map instances; uint32_t next_instance_id = 1; Mutex mutex {}; diff --git a/Modules/app-module/source/install.cpp b/Modules/app-module/source/install.cpp index 234631f31..d8bd7a266 100644 --- a/Modules/app-module/source/install.cpp +++ b/Modules/app-module/source/install.cpp @@ -251,9 +251,9 @@ InstallRegistry& install_registry() { } // Registers every AppManifest in @a bindings (@a count of them) with app_manager_add(), taking -// ownership of their backing location strings. +// ownership of their backing location strings, then registers @a package itself. // @warning Caller must hold install_registry().mutex. -error_t register_installed_package_locked(const std::string& package_id, const std::string& install_path, const AppManifestBinding* bindings, size_t count) { +error_t register_installed_package_locked(const PackageManifest& package, const std::string& install_path, const AppManifestBinding* bindings, size_t count) { auto& registry = install_registry(); auto record = std::make_unique(); @@ -274,7 +274,11 @@ error_t register_installed_package_locked(const std::string& package_id, const s // (e.g. left over from app_manager_install_path_scan()'s separate registry), // but that call happens before the package is even extracted / the binaries moved into place; remove once more // right before add, so a duplicate id can never turn a filesystem-level install success into a reported failure. - app_manager_remove(record->manifests[i].id); + // Only if installed (APP_LOCATION_PATH) - never steal a built-in's id. + AppManifest existing {}; + if (app_manager_find_manifest(record->manifests[i].id, &existing) == ERROR_NONE && existing.location.type == APP_LOCATION_PATH) { + app_manager_remove(record->manifests[i].id); + } error_t add_result = app_manager_add(&record->manifests[i]); if (add_result != ERROR_NONE) { @@ -287,7 +291,22 @@ error_t register_installed_package_locked(const std::string& package_id, const s } } - registry.apps[package_id] = std::move(record); + std::vector app_id_ptrs; + app_id_ptrs.reserve(count); + for (size_t i = 0; i < count; i++) { + app_id_ptrs.push_back(record->manifests[i].id); + } + app_manager_remove_package(package.id); + error_t add_package_result = app_manager_add_package(&package, app_id_ptrs.data(), app_id_ptrs.size()); + if (add_package_result != ERROR_NONE) { + LOG_E(TAG, "Failed to register package '%s': %s", package.id, error_to_string(add_package_result)); + for (size_t i = 0; i < count; i++) { + app_manager_remove(record->manifests[i].id); + } + return add_package_result; + } + + registry.apps[package.id] = std::move(record); return ERROR_NONE; } @@ -321,15 +340,14 @@ error_t uninstall_locked(const std::string& package_id) { } for (const auto& manifest : iterator->second->manifests) { - // Can't uninstall in-memory apps - always false in practice (every manifest here was - // built by register_installed_package_locked(), always APP_LOCATION_PATH), kept as a - // guard rather than an assumption. + // Can't uninstall in-memory apps if (manifest.location.type != APP_LOCATION_PATH) { continue; } stop_all_instances_of(&manifest); app_manager_remove(manifest.id); } + app_manager_remove_package(package_id.c_str()); delete_recursively(iterator->second->path); registry.apps.erase(iterator); @@ -401,10 +419,6 @@ error_t app_install(const char* source_path) { } PackageManifest package {}; - // Heap-allocated (not a stack array - too large for a typical app task's stack, e.g. Boot's, - // which calls this indirectly via registerInstalledAppsFromFileSystems()) and sized to fit - // exactly, not pre-allocated to some fixed maximum (see app_package_manifest_parse_into()). - // OptExternalAllocator prefers PSRAM for this transient buffer, freeing up scarce internal RAM. std::vector> app_bindings; if (app_package_manifest_parse_into(manifest_path.c_str(), package, app_bindings) != ERROR_NONE) { LOG_E(TAG, "Install failed: invalid manifest"); @@ -434,7 +448,8 @@ error_t app_install(const char* source_path) { } release_staging_lock(staging_path); - error_t add_result = register_installed_package_locked(package.id, final_path, app_bindings.data(), package.app_manifest_count); + // app_bindings.size(), not package.app_manifest_count - the safe bound to index by. + error_t add_result = register_installed_package_locked(package, final_path, app_bindings.data(), app_bindings.size()); mutex_unlock(®istry.mutex); return add_result; diff --git a/Modules/app-module/source/manager.cpp b/Modules/app-module/source/manager.cpp index bd350e8e3..f9766312b 100644 --- a/Modules/app-module/source/manager.cpp +++ b/Modules/app-module/source/manager.cpp @@ -76,6 +76,67 @@ void app_manager_for_each_manifest(AppManifestVisitorFn visitor, void* context) mutex_unlock(&ledger.mutex); } +error_t app_manager_add_package(const PackageManifest* package, const char* const* app_ids, size_t app_id_count) { + auto& ledger = app_ledger(); + mutex_lock(&ledger.mutex); + if (ledger.packages.contains(package->id)) { + mutex_unlock(&ledger.mutex); + LOG_E(TAG, "Package with id '%s' is already registered", package->id); + return ERROR_INVALID_ARGUMENT; + } + AppPackageRecord record { .package = *package }; + record.app_ids.reserve(app_id_count); + for (size_t i = 0; i < app_id_count; i++) { + record.app_ids.emplace_back(app_ids[i]); + } + ledger.packages[package->id] = std::move(record); + mutex_unlock(&ledger.mutex); + + return ERROR_NONE; +} + +error_t app_manager_remove_package(const char* package_id) { + auto& ledger = app_ledger(); + mutex_lock(&ledger.mutex); + auto iterator = ledger.packages.find(package_id); + if (iterator == ledger.packages.end()) { + mutex_unlock(&ledger.mutex); + return ERROR_NOT_FOUND; + } + ledger.packages.erase(iterator); + mutex_unlock(&ledger.mutex); + + return ERROR_NONE; +} + +error_t app_manager_find_package(const char* package_id, PackageManifest* out_package) { + auto& ledger = app_ledger(); + mutex_lock(&ledger.mutex); + auto iterator = ledger.packages.find(package_id); + if (iterator == ledger.packages.end()) { + mutex_unlock(&ledger.mutex); + return ERROR_NOT_FOUND; + } + *out_package = iterator->second.package; + mutex_unlock(&ledger.mutex); + return ERROR_NONE; +} + +void app_manager_for_each_package(AppPackageVisitorFn visitor, void* context) { + auto& ledger = app_ledger(); + mutex_lock(&ledger.mutex); + for (auto& [id, record] : ledger.packages) { + std::vector app_id_ptrs; + app_id_ptrs.reserve(record.app_ids.size()); + for (const auto& app_id : record.app_ids) { + app_id_ptrs.push_back(app_id.c_str()); + } + AppPackage pkg { .package = record.package, .app_id_count = app_id_ptrs.size(), .app_ids = app_id_ptrs.data() }; + visitor(&pkg, context); + } + mutex_unlock(&ledger.mutex); +} + error_t app_manager_start_internal(const AppManifest* manifest, AppLocation location, AppStackConfig stack, AppInstanceId parent_instance_id, int argc, const char* const argv_in[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) { char** argv = app_arguments_copy(argc, argv_in); if (argc > 0 && argv == nullptr) { @@ -274,6 +335,7 @@ void app_manager_install_path_scan(void) { // Parses without registry.mutex held; filesystem IO is slow. std::vector> new_records; std::vector new_package_ids; + std::vector new_packages; for (const auto& app_dir : found_app_dirs) { auto manifest_path = app_dir + "/manifest.properties"; if (!app_fs_is_file(manifest_path)) { @@ -300,14 +362,15 @@ void app_manager_install_path_scan(void) { record->path = app_dir; // Sized once, up front: manifests[i].location.location points into locations[i].c_str(), // which would dangle if either vector reallocated afterward. - record->manifests.resize(package.app_manifest_count); - record->locations.resize(package.app_manifest_count); - for (size_t i = 0; i < package.app_manifest_count; i++) { + record->manifests.resize(app_bindings.size()); + record->locations.resize(app_bindings.size()); + for (size_t i = 0; i < app_bindings.size(); i++) { record->manifests[i] = app_bindings[i].manifest; record->locations[i] = app_resolve_binary_path(app_dir, app_bindings[i].binary); record->manifests[i].location = { APP_LOCATION_PATH, const_cast(record->locations[i].c_str()) }; } new_package_ids.emplace_back(package.id); + new_packages.push_back(package); new_records.push_back(std::move(record)); } @@ -325,11 +388,19 @@ void app_manager_install_path_scan(void) { for (const auto& manifest_id : known_packages.at(id).manifest_ids) { app_manager_remove(manifest_id.c_str()); } + app_manager_remove_package(id.c_str()); } std::vector> added_records; std::vector added_package_ids; for (size_t r = 0; r < new_records.size(); r++) { auto& record = new_records[r]; + + // known_packages is only a pre-scan snapshot - two new directories can still share an id. + if (std::ranges::find(added_package_ids, new_package_ids[r]) != added_package_ids.end()) { + LOG_W(TAG, "Skipping duplicate package id %s found in this scan", new_package_ids[r].c_str()); + continue; + } + size_t added_count = 0; for (; added_count < record->manifests.size(); added_count++) { if (app_manager_add(&record->manifests[added_count]) != ERROR_NONE) { @@ -337,15 +408,29 @@ void app_manager_install_path_scan(void) { break; } } - if (added_count == record->manifests.size()) { - added_package_ids.push_back(new_package_ids[r]); - added_records.push_back(std::move(record)); - } else { + if (added_count != record->manifests.size()) { // All-or-nothing: unregister whatever this package already added before failing. for (size_t j = 0; j < added_count; j++) { app_manager_remove(record->manifests[j].id); } + continue; } + + std::vector app_id_ptrs; + app_id_ptrs.reserve(record->manifests.size()); + for (const auto& app_manifest : record->manifests) { + app_id_ptrs.push_back(app_manifest.id); + } + if (app_manager_add_package(&new_packages[r], app_id_ptrs.data(), app_id_ptrs.size()) != ERROR_NONE) { + LOG_E(TAG, "Failed to register package %s (duplicate id?)", new_packages[r].id); + for (const auto& app_manifest : record->manifests) { + app_manager_remove(app_manifest.id); + } + continue; + } + + added_package_ids.push_back(new_package_ids[r]); + added_records.push_back(std::move(record)); } mutex_lock(®istry.mutex); @@ -399,6 +484,7 @@ error_t app_manager_install_path_uninstall(const char* app_id) { for (const auto& manifest : *manifests) { app_manager_remove(manifest.id); } + app_manager_remove_package(app_id); // Delete before erasing the scan record, so a failed deletion still leaves the // entry discoverable for a retry. diff --git a/Modules/app-module/source/module.cpp b/Modules/app-module/source/module.cpp index e8a975e76..03544e532 100644 --- a/Modules/app-module/source/module.cpp +++ b/Modules/app-module/source/module.cpp @@ -47,18 +47,19 @@ static const ModuleSymbol SYMBOLS[] = { DEFINE_MODULE_SYMBOL(app_manager_for_each_manifest), DEFINE_MODULE_SYMBOL(app_manager_add), DEFINE_MODULE_SYMBOL(app_manager_remove), + DEFINE_MODULE_SYMBOL(app_manager_add_package), + DEFINE_MODULE_SYMBOL(app_manager_remove_package), + DEFINE_MODULE_SYMBOL(app_manager_find_package), + DEFINE_MODULE_SYMBOL(app_manager_for_each_package), DEFINE_MODULE_SYMBOL(app_manager_get_topmost_instance_id), DEFINE_MODULE_SYMBOL(app_manager_get_topmost_app_id), DEFINE_MODULE_SYMBOL(app_manager_install_path_add), DEFINE_MODULE_SYMBOL(app_manager_install_path_scan), DEFINE_MODULE_SYMBOL(app_manager_install_path_uninstall), - // app/start - DEFINE_MODULE_SYMBOL(app_start), - DEFINE_MODULE_SYMBOL(app_start_for_result), - DEFINE_MODULE_SYMBOL(app_start_with_streams), - DEFINE_MODULE_SYMBOL(app_start_for_result_with_streams), // app/manifest DEFINE_MODULE_SYMBOL(app_manifest_id_is_valid), + DEFINE_MODULE_SYMBOL(app_manifest_name_is_valid), + DEFINE_MODULE_SYMBOL(app_manifest_stack_size_is_valid), // app/package_manifest DEFINE_MODULE_SYMBOL(app_package_manifest_parse), // app/paths @@ -68,6 +69,11 @@ static const ModuleSymbol SYMBOLS[] = { DEFINE_MODULE_SYMBOL(app_paths_get_assets_path), // app/scheduler DEFINE_MODULE_SYMBOL(app_scheduler_current_app_id), + // app/start + DEFINE_MODULE_SYMBOL(app_start), + DEFINE_MODULE_SYMBOL(app_start_for_result), + DEFINE_MODULE_SYMBOL(app_start_with_streams), + DEFINE_MODULE_SYMBOL(app_start_for_result_with_streams), // app/stream DEFINE_MODULE_SYMBOL(app_stream_subscribe), DEFINE_MODULE_SYMBOL(app_stream_unsubscribe), diff --git a/Modules/app-module/source/package_manifest_parsing.cpp b/Modules/app-module/source/package_manifest_parsing.cpp index 11cf64465..b2f9851ba 100644 --- a/Modules/app-module/source/package_manifest_parsing.cpp +++ b/Modules/app-module/source/package_manifest_parsing.cpp @@ -188,5 +188,14 @@ error_t app_package_manifest_parse_into(const char* path, PackageManifest& out_p return result; } out_bindings.resize(out_package.app_manifest_count); - return app_package_manifest_parse(path, &out_package, out_bindings.data(), out_bindings.size()); + result = app_package_manifest_parse(path, &out_package, out_bindings.data(), out_bindings.size()); + if (result != ERROR_NONE) { + return result; + } + // Manifest could have changed between the two parses above. + if (out_package.app_manifest_count != out_bindings.size()) { + LOG_E(TAG, "Manifest at %s changed while being parsed", path); + return ERROR_INVALID_ARGUMENT; + } + return ERROR_NONE; } diff --git a/Modules/app-module/source/package_manifest_parsing_v3.cpp b/Modules/app-module/source/package_manifest_parsing_v3.cpp index 6a04ebf99..e03739abd 100644 --- a/Modules/app-module/source/package_manifest_parsing_v3.cpp +++ b/Modules/app-module/source/package_manifest_parsing_v3.cpp @@ -7,6 +7,7 @@ #include #include +#include #include @@ -14,6 +15,27 @@ constexpr auto* TAG = "app_metadata_v3"; namespace { +// Numeric index out of an "app.." key, or nullopt if it doesn't match that shape. +std::optional parse_app_key_index(const std::string& key) { + constexpr auto* prefix = "app."; + constexpr size_t prefix_len = 4; + if (!key.starts_with(prefix)) { + return std::nullopt; + } + auto dot = key.find('.', prefix_len); + if (dot == std::string::npos || dot == prefix_len) { + return std::nullopt; + } + size_t index = 0; + const auto* first = key.data() + prefix_len; + const auto* last = key.data() + dot; + auto result = std::from_chars(first, last, index); + if (result.ec != std::errc {} || result.ptr != last) { + return std::nullopt; + } + return index; +} + // Parses one "app..*" block into @a out_binding. error_t parse_app_manifest(const std::map& properties, size_t index, AppManifestBinding& out_binding) { auto prefix = std::format("app.{}.", index); @@ -187,6 +209,16 @@ error_t package_manifest_parse_v3(const std::map& prop } out_package.app_manifest_count = static_cast(count); + + // Catch a gap the count loop above would otherwise silently drop, e.g. app.0.* + app.3.*. + for (const auto& [key, value] : properties) { + auto index = parse_app_key_index(key); + if (index.has_value() && *index >= count) { + LOG_E(TAG, "Manifest declares %s but only %zu contiguous app(s) starting at app.0 were found", key.c_str(), count); + return ERROR_INVALID_ARGUMENT; + } + } + if (count == 0 || bindings_capacity == 0) { return ERROR_NONE; } diff --git a/Modules/app-module/source/paths.cpp b/Modules/app-module/source/paths.cpp index da7f0a4ec..8d3586f5c 100644 --- a/Modules/app-module/source/paths.cpp +++ b/Modules/app-module/source/paths.cpp @@ -6,6 +6,27 @@ #include #include +#include + +namespace { + +// manifest.location.location is the fully-resolved binary file path +// ({install_dir}/bin//.{elf,so} - see app_resolve_binary_path()), not the +// install directory itself - strip that fixed 3-segment suffix to recover it. +bool app_get_install_dir_from_binary_path(const char* binary_path, std::string& out_install_dir) { + std::string path = binary_path; + for (int i = 0; i < 3; i++) { + auto separator = path.find_last_of('/'); + if (separator == std::string::npos) { + return false; + } + path = path.substr(0, separator); + } + out_install_dir = path; + return true; +} + +} // namespace extern "C" { @@ -46,7 +67,12 @@ error_t app_paths_get_assets_directory(const char* app_id, char* out_path, size_ return ERROR_NOT_FOUND; } - int written = std::snprintf(out_path, out_path_size, "%s/assets", static_cast(manifest.location.location)); + std::string install_dir; + if (!app_get_install_dir_from_binary_path(static_cast(manifest.location.location), install_dir)) { + return ERROR_NOT_FOUND; + } + + int written = std::snprintf(out_path, out_path_size, "%s/assets", install_dir.c_str()); if (written < 0 || (size_t)written >= out_path_size) { return ERROR_BUFFER_OVERFLOW; } diff --git a/Tactility/Private/Tactility/app/appdetails/AppDetails.h b/Tactility/Private/Tactility/app/appdetails/AppDetails.h deleted file mode 100644 index 8ddb22f9e..000000000 --- a/Tactility/Private/Tactility/app/appdetails/AppDetails.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include - -namespace tt::app::appdetails { - -void start(const std::string& appId); - -} // namespace diff --git a/Tactility/Private/Tactility/app/apppackagedetails/AppPackageDetails.h b/Tactility/Private/Tactility/app/apppackagedetails/AppPackageDetails.h new file mode 100644 index 000000000..40a9a326e --- /dev/null +++ b/Tactility/Private/Tactility/app/apppackagedetails/AppPackageDetails.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace tt::app::apppackagedetails { + +void start(const std::string& packageId); + +} // namespace diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index c96235153..d5d132ef5 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -150,9 +150,9 @@ namespace app { namespace alertdialog { extern const ::AppManifest manifest; } namespace apphub { extern const ::AppManifest manifest; } namespace apphubdetails { extern const ::AppManifest manifest; } - namespace appdetails { extern const ::AppManifest manifest; } + namespace apppackagedetails { extern const ::AppManifest manifest; } namespace applist { extern const ::AppManifest manifest; } - namespace appsettings { extern const ::AppManifest manifest; } + namespace apppackagelist { extern const ::AppManifest manifest; } namespace audiosettings { extern const ::AppManifest manifest; } namespace boot { extern const ::AppManifest manifest; } namespace development { extern const ::AppManifest manifest; } @@ -212,11 +212,11 @@ static void registerInternalApps() { LOG_I(TAG, "Registering internal apps"); app_manager_add(&app::alertdialog::manifest); - app_manager_add(&app::appdetails::manifest); + app_manager_add(&app::apppackagedetails::manifest); app_manager_add(&app::apphub::manifest); app_manager_add(&app::apphubdetails::manifest); app_manager_add(&app::applist::manifest); - app_manager_add(&app::appsettings::manifest); + app_manager_add(&app::apppackagelist::manifest); if (service::audio::isAvailable()) { app_manager_add(&app::audiosettings::manifest); } diff --git a/Tactility/Source/app/appdetails/AppDetails.cpp b/Tactility/Source/app/apppackagedetails/AppPackageDetails.cpp similarity index 54% rename from Tactility/Source/app/appdetails/AppDetails.cpp rename to Tactility/Source/app/apppackagedetails/AppPackageDetails.cpp index 1087c9fc4..a901f9937 100644 --- a/Tactility/Source/app/appdetails/AppDetails.cpp +++ b/Tactility/Source/app/apppackagedetails/AppPackageDetails.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -12,17 +13,15 @@ #include -#include #include -#include #include #include #include -constexpr auto* TAG = "AppDetails"; +constexpr auto* TAG = "AppPackageDetails"; -namespace tt::app::appdetails { +namespace tt::app::apppackagedetails { extern const ::AppManifest manifest; @@ -30,13 +29,34 @@ namespace { struct Context { uint32_t appInstanceId; - std::string targetAppId; - // findAppManifestById() returns the old-model registry's AppManifest type - AppDetails - // shows details for apps in that registry regardless of which system they run under. - AppManifest targetManifest = { }; + std::string targetPackageId; + PackageManifest targetPackage = {}; + std::vector appIds; uint32_t pendingUninstallDialogId = 0; }; +struct FindPackageContext { + const std::string* packageId; + PackageManifest* outPackage; + std::vector* outAppIds; + bool found = false; +}; + +void onVisitPackage(const ::AppPackage* pkg, void* context) { + auto* findContext = static_cast(context); + if (findContext->found || *findContext->packageId != pkg->package.id) { + return; + } + *findContext->outPackage = pkg->package; + findContext->outAppIds->assign(pkg->app_ids, pkg->app_ids + pkg->app_id_count); + findContext->found = true; +} + +bool findPackage(const std::string& packageId, PackageManifest& outPackage, std::vector& outAppIds) { + FindPackageContext findContext { &packageId, &outPackage, &outAppIds }; + app_manager_for_each_package(onVisitPackage, &findContext); + return findContext.found; +} void onPressUninstall(lv_event_t* event) { auto* ctx = static_cast(lv_event_get_user_data(event)); @@ -44,7 +64,7 @@ void onPressUninstall(lv_event_t* event) { ctx->pendingUninstallDialogId = alertdialog::start( ctx->appInstanceId, "Confirmation", - std::format("Uninstall {}?", ctx->targetManifest.name), + std::format("Uninstall {}?", ctx->targetPackage.id), choices ); } @@ -59,7 +79,7 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); - auto title = std::format("{} details", ctx->targetManifest.name); + auto title = std::format("{} details", ctx->targetPackage.id); auto* toolbar = lvgl_toolbar_create(parent, title.c_str()); // The global toolbar nav callback only knows how to stop old-model apps. lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx); @@ -71,35 +91,39 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_set_style_border_width(wrapper, 0, LV_STATE_DEFAULT); lvgl::obj_set_style_bg_invisible(wrapper); - auto identifier = std::format("Identifier: {}", ctx->targetManifest.id); + auto identifier = std::format("Identifier: {}", ctx->targetPackage.id); auto* identifier_label = lv_label_create(wrapper); lv_label_set_text(identifier_label, identifier.c_str()); - auto* location_label = lv_label_create(wrapper); - std::string location; - bool is_internal = ctx->targetManifest.location.type == APP_LOCATION_MEMORY; - bool is_external = ctx->targetManifest.location.type == APP_LOCATION_PATH; - if (is_internal) { - location = "internal"; - } else if (is_external) { - if (!string::getPathParent(static_cast(ctx->targetManifest.location.location), location)) { - location = "external"; - } - } else { - LOG_E(TAG, "Unknown app location type %d", ctx->targetManifest.location.type); - return; - } - std::string location_label_text = std::format("Location: {}", location); - lv_label_set_text(location_label, location_label_text.c_str()); + auto version = std::format("Version: {} ({})", ctx->targetPackage.version_name, ctx->targetPackage.version_code); + auto* version_label = lv_label_create(wrapper); + lv_label_set_text(version_label, version.c_str()); - if (is_external) { - auto* uninstall_button = lv_button_create(wrapper); - lv_obj_set_width(uninstall_button, LV_PCT(100)); - lv_obj_add_event_cb(uninstall_button, onPressUninstall, LV_EVENT_SHORT_CLICKED, ctx); - auto* uninstall_label = lv_label_create(uninstall_button); - lv_obj_align(uninstall_label, LV_ALIGN_CENTER, 0, 0); - lv_label_set_text(uninstall_label, "Uninstall"); + char install_path[192]; + std::string location = "unknown"; + if (app_get_install_path(ctx->targetPackage.id, install_path, sizeof(install_path)) == ERROR_NONE) { + location = install_path; } + auto location_text = std::format("Location: {}", location); + auto* location_label = lv_label_create(wrapper); + lv_label_set_text(location_label, location_text.c_str()); + + std::string apps; + for (const auto& appId : ctx->appIds) { + AppManifest appManifest {}; + const char* label = app_manager_find_manifest(appId.c_str(), &appManifest) == ERROR_NONE ? appManifest.name : appId.c_str(); + apps += apps.empty() ? label : std::format(", {}", label); + } + auto apps_text = std::format("Apps: {}", apps); + auto* apps_label = lv_label_create(wrapper); + lv_label_set_text(apps_label, apps_text.c_str()); + + auto* uninstall_button = lv_button_create(wrapper); + lv_obj_set_width(uninstall_button, LV_PCT(100)); + lv_obj_add_event_cb(uninstall_button, onPressUninstall, LV_EVENT_SHORT_CLICKED, ctx); + auto* uninstall_label = lv_label_create(uninstall_button); + lv_obj_align(uninstall_label, LV_ALIGN_CENTER, 0, 0); + lv_label_set_text(uninstall_label, "Uninstall"); } int32_t appMain(int argc, char* argv[]) { @@ -107,9 +131,9 @@ int32_t appMain(int argc, char* argv[]) { Context ctx {}; ctx.appInstanceId = appInstanceId; - ctx.targetAppId = (argc > 0) ? argv[0] : std::string(); - if (app_manager_find_manifest(ctx.targetAppId.c_str(), &ctx.targetManifest) != ERROR_NONE) { - LOG_W(TAG, "App %s not found", ctx.targetAppId.c_str()); + ctx.targetPackageId = (argc > 0) ? argv[0] : std::string(); + if (!findPackage(ctx.targetPackageId, ctx.targetPackage, ctx.appIds)) { + LOG_W(TAG, "Package %s not found", ctx.targetPackageId.c_str()); return 0; } @@ -134,7 +158,7 @@ int32_t appMain(int argc, char* argv[]) { case APP_EVENT_RESULT: if (event.result.launch_id == ctx.pendingUninstallDialogId) { if (event.result.result == 0) { // 0 = Yes - app_uninstall(ctx.targetManifest.id); + app_uninstall(ctx.targetPackage.id); shouldClose = true; } app_manager_stop(event.result.launch_id); @@ -156,15 +180,15 @@ int32_t appMain(int argc, char* argv[]) { } // namespace -void start(const std::string& appId) { - const char* argv[] = { appId.c_str() }; +void start(const std::string& packageId) { + const char* argv[] = { packageId.c_str() }; uint32_t instanceId = 0; app_start(manifest.id, 1, argv, &instanceId); } extern const ::AppManifest manifest = { - .id = "tactility.appdetails", - .name = "App Details", + .id = "tactility.apppackagedetails", + .name = "Package Details", .category = APP_CATEGORY_SYSTEM, .location = { APP_LOCATION_MEMORY, reinterpret_cast(appMain) }, .flags = APP_MANIFEST_FLAG_HIDDEN, diff --git a/Tactility/Source/app/appsettings/AppSettings.cpp b/Tactility/Source/app/apppackagelist/AppPackageList.cpp similarity index 73% rename from Tactility/Source/app/appsettings/AppSettings.cpp rename to Tactility/Source/app/apppackagelist/AppPackageList.cpp index acede4e39..d5c4d1348 100644 --- a/Tactility/Source/app/appsettings/AppSettings.cpp +++ b/Tactility/Source/app/apppackagelist/AppPackageList.cpp @@ -1,11 +1,12 @@ #include #include -#include +#include #include #include #include +#include #include #include @@ -18,7 +19,7 @@ #include #include -namespace tt::app::appsettings { +namespace tt::app::apppackagelist { extern const ::AppManifest manifest; @@ -26,11 +27,13 @@ namespace { struct Context { uint32_t appInstanceId; + // Must outlive the widgets - button user-data points into this, not a createWidgets()-local vector. + std::vector packageIds; }; -void onAppPressed(lv_event_t* e) { - const auto* target_manifest = static_cast(lv_event_get_user_data(e)); - appdetails::start(target_manifest->id); +void onPackagePressed(lv_event_t* e) { + auto* packageId = static_cast(lv_event_get_user_data(e)); + apppackagedetails::start(packageId); } void onBackPressed(lv_event_t* event) { @@ -38,18 +41,16 @@ void onBackPressed(lv_event_t* event) { app_event_emit_close(ctx->appInstanceId); } -void createAppWidget(const ::AppManifest* target_manifest, lv_obj_t* list) { - // The new AppManifest has no per-app icon - use a shared generic one for every entry, same - // fallback AppList.cpp uses. - lv_obj_t* btn = lv_list_add_button(list, LVGL_ICON_SHARED_TOOLBAR, target_manifest->name); +void createPackageWidget(const char* packageId, lv_obj_t* list) { + lv_obj_t* btn = lv_list_add_button(list, LVGL_ICON_SHARED_TOOLBAR, packageId); lv_obj_t* image = lv_obj_get_child(btn, 0); lv_obj_set_style_text_font(image, lvgl_get_shared_icon_font(), LV_PART_MAIN); - lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, const_cast<::AppManifest*>(target_manifest)); + lv_obj_add_event_cb(btn, &onPackagePressed, LV_EVENT_SHORT_CLICKED, const_cast(packageId)); } -void collectManifest(const ::AppManifest* manifest, void* context) { - auto* manifests = static_cast*>(context); - manifests->push_back(manifest); +void collectPackageId(const ::AppPackage* pkg, void* context) { + auto* packageIds = static_cast*>(context); + packageIds->emplace_back(pkg->package.id); } void createWidgets(lv_obj_t* parent, void* userData) { @@ -68,21 +69,16 @@ void createWidgets(lv_obj_t* parent, void* userData) { lv_obj_set_width(list, LV_PCT(100)); lv_obj_set_flex_grow(list, 1); - std::vector manifests; - app_manager_for_each_manifest(collectManifest, &manifests); - std::ranges::sort(manifests, [](const ::AppManifest* a, const ::AppManifest* b) { - return strcmp(a->name, b->name) < 0; - }); + // createWidgets() can rerun for this same Context (window rebuild-on-remove). + ctx->packageIds.clear(); + app_manager_for_each_package(collectPackageId, &ctx->packageIds); + std::ranges::sort(ctx->packageIds); - size_t app_count = 0; - for (const auto* target_manifest: manifests) { - if (target_manifest->location.type == APP_LOCATION_PATH) { - app_count++; - createAppWidget(target_manifest, list); - } + for (const auto& packageId : ctx->packageIds) { + createPackageWidget(packageId.c_str(), list); } - if (app_count == 0) { + if (ctx->packageIds.empty()) { // lv_obj_align() is ignored for children of a flex-managed parent, so the empty-state // label needs its own flex-growing wrapper to center within; the (empty) list is hidden // rather than deleted so the wrapper can just take its place in the flex flow. @@ -139,7 +135,7 @@ int32_t appMain(int argc, char* argv[]) { } // namespace extern const ::AppManifest manifest = { - .id = "tactility.appsettings", + .id = "tactility.apppackagelist", .name = "Apps", .category = APP_CATEGORY_SETTINGS, .location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast(appMain) }, diff --git a/Tests/SdkIntegration/tactility.py b/Tests/SdkIntegration/tactility.py index 10ec23b67..ef8c5f521 100644 --- a/Tests/SdkIntegration/tactility.py +++ b/Tests/SdkIntegration/tactility.py @@ -450,6 +450,9 @@ def get_binary_dirs(manifest): index += 1 if len(binaries) == 1: return [(binaries[0][0], ".")] + for binary, directory in binaries: + if not os.path.isdir(directory): + exit_with_error(f"Binary directory not found for '{binary}': {directory}") return binaries else: return [(manifest["app.id"], ".")]