Run executables directly (#645)
- Apps can be launched directly from file paths (including Files app support) - Added executable detection to identify unsupported or invalid binaries before launch. - App startup is now streamlined through separate registered-app and direct-execution interfaces. - Existing app launch points were migrated to the updated startup APIs.
This commit is contained in:
committed by
GitHub
parent
643cbc3806
commit
a0b2ee7ebc
@@ -3,6 +3,7 @@
|
|||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <app/elf_check.h>
|
||||||
#include <app/loader.h>
|
#include <app/loader.h>
|
||||||
#include <app/location.h>
|
#include <app/location.h>
|
||||||
|
|
||||||
@@ -72,6 +73,27 @@ std::string resolve_elf_path(const std::string& path) {
|
|||||||
return path + "/elf/" + CONFIG_IDF_TARGET + ".elf";
|
return path + "/elf/" + CONFIG_IDF_TARGET + ".elf";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr ElfRequirements EXECUTABLE_REQUIREMENTS = {
|
||||||
|
.elf_class = ELF_CLASS_32,
|
||||||
|
.data = ELF_DATA_2LSB,
|
||||||
|
.type = ELF_TYPE_DYN,
|
||||||
|
#if defined(__XTENSA__)
|
||||||
|
.machine = ELF_MACHINE_XTENSA,
|
||||||
|
#elif defined(__riscv)
|
||||||
|
.machine = ELF_MACHINE_RISCV,
|
||||||
|
#else
|
||||||
|
#error "Unsupported ESP32 architecture for ELF machine check"
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validates an already-resolved binary path (see resolve_elf_path()) before it's handed to
|
||||||
|
// esp_elf_relocate(), which performs no header validation of its own: the extension check is a
|
||||||
|
// cheap string comparison, so the file is only opened as a last resort.
|
||||||
|
bool is_executable_file(const std::string& resolved_path) {
|
||||||
|
return resolved_path.ends_with(".elf")
|
||||||
|
&& elf_check_file(resolved_path.c_str(), &EXECUTABLE_REQUIREMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
error_t api_load(AppLocation location, AppRuntime* out_runtime) {
|
error_t api_load(AppLocation location, AppRuntime* out_runtime) {
|
||||||
if (location.type != APP_LOCATION_PATH) {
|
if (location.type != APP_LOCATION_PATH) {
|
||||||
LOG_E(TAG, "Out of memory");
|
LOG_E(TAG, "Out of memory");
|
||||||
@@ -88,6 +110,12 @@ error_t api_load(AppLocation location, AppRuntime* out_runtime) {
|
|||||||
|
|
||||||
auto elf_path = resolve_elf_path(static_cast<const char*>(location.location));
|
auto elf_path = resolve_elf_path(static_cast<const char*>(location.location));
|
||||||
|
|
||||||
|
if (!is_executable_file(elf_path)) {
|
||||||
|
LOG_E(TAG, "Not executable: %s", elf_path.c_str());
|
||||||
|
delete runtime;
|
||||||
|
return ERROR_NOT_ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
error_t read_result = read_file(elf_path.c_str(), &runtime->file_data, &size);
|
error_t read_result = read_file(elf_path.c_str(), &runtime->file_data, &size);
|
||||||
if (read_result != ERROR_NONE) {
|
if (read_result != ERROR_NONE) {
|
||||||
@@ -127,10 +155,20 @@ void api_unload(AppRuntime runtime_ptr) {
|
|||||||
delete runtime;
|
delete runtime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool api_is_executable(AppLocation location) {
|
||||||
|
if (location.type != APP_LOCATION_PATH) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto elf_path = resolve_elf_path(static_cast<const char*>(location.location));
|
||||||
|
return is_executable_file(elf_path);
|
||||||
|
}
|
||||||
|
|
||||||
AppLoaderApi loader_api = {
|
AppLoaderApi loader_api = {
|
||||||
.load = api_load,
|
.load = api_load,
|
||||||
.run = api_run,
|
.run = api_run,
|
||||||
.unload = api_unload,
|
.unload = api_unload,
|
||||||
|
.is_executable = api_is_executable,
|
||||||
};
|
};
|
||||||
|
|
||||||
void* create_service(const ServiceManifest*) {
|
void* create_service(const ServiceManifest*) {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// <elf.h> is not guaranteed to exist under the ESP32 newlib toolchain
|
||||||
|
#define ELF_CLASS_32 1
|
||||||
|
#define ELF_CLASS_64 2
|
||||||
|
#define ELF_DATA_2LSB 1
|
||||||
|
#define ELF_TYPE_DYN 3
|
||||||
|
#define ELF_MACHINE_XTENSA 94
|
||||||
|
#define ELF_MACHINE_RISCV 243
|
||||||
|
#define ELF_MACHINE_X86_64 62
|
||||||
|
#define ELF_MACHINE_AARCH64 183
|
||||||
|
|
||||||
|
/** What a loader needs an ELF file's header to say before it will try to load it. */
|
||||||
|
struct ElfRequirements {
|
||||||
|
uint8_t elf_class; /**< ELF_CLASS_32 / ELF_CLASS_64 */
|
||||||
|
uint8_t data; /**< ELF_DATA_2LSB */
|
||||||
|
uint16_t type; /**< ELF_TYPE_DYN */
|
||||||
|
uint16_t machine; /**< ELF_MACHINE_XTENSA / _RISCV / _X86_64 / _AARCH64 */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the first 20 bytes of @a path (e_ident, e_type, e_machine; identical offsets for
|
||||||
|
* ELF32 and ELF64) and checks the magic number and every field in @a requirements match.
|
||||||
|
* @return false if @a path can't be opened, is too short, or doesn't match
|
||||||
|
*/
|
||||||
|
bool elf_check_file(const char* path, const struct ElfRequirements* requirements);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -24,8 +24,8 @@ struct AppResultEventData {
|
|||||||
uint32_t launch_id;
|
uint32_t launch_id;
|
||||||
/** The child app instance's own AppMainFn/AppLoaderApi::run() return value. By convention:
|
/** The child app instance's own AppMainFn/AppLoaderApi::run() return value. By convention:
|
||||||
* 0 = Ok, 1 = Cancelled, 2 = Error. Apps that need to hand back more than this (e.g. picked
|
* 0 = Ok, 1 = Cancelled, 2 = Error. Apps that need to hand back more than this (e.g. picked
|
||||||
* text, a path) expose their own "get last result" getter instead - see e.g.
|
* text, a path) write it to their own stdout instead, for the caller to read via an
|
||||||
* tt::app::inputdialog::getLastText(). */
|
* AppStream bound to it. See e.g. tt::app::inputdialog::start(). */
|
||||||
int32_t result;
|
int32_t result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains functions to start and run apps.
|
||||||
|
* It differs from start.h by running apps directly from the specified location,
|
||||||
|
* instead of having to register them first via an AppManifest and the app manager.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <app/manager.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an app directly from @a location, without it having to be pre-registered via
|
||||||
|
* app_manager_add() first.
|
||||||
|
* Performs no checks of its own beyond what AppLoaderApi::load() itself rejects.
|
||||||
|
* @warning It's advised to validate @a location with app_is_executable() first
|
||||||
|
* @param[in] stack stack allocation config for the app's task; all-zero uses the scheduler's
|
||||||
|
* default depth/capability, same as an AppManifest that leaves AppManifest::stack zeroed
|
||||||
|
* @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t app_execute(
|
||||||
|
struct AppLocation location,
|
||||||
|
struct AppStackConfig stack,
|
||||||
|
int argc,
|
||||||
|
const char* const argv[],
|
||||||
|
AppInstanceId* out_app_instance_id
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as app_execute(), but as a modal child of @a parent_instance_id.
|
||||||
|
* See app_start_for_result()'s own doc for the result-delivery contract.
|
||||||
|
* @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t app_execute_for_result(
|
||||||
|
struct AppLocation location,
|
||||||
|
struct AppStackConfig stack,
|
||||||
|
int argc,
|
||||||
|
const char* const argv[],
|
||||||
|
AppInstanceId parent_instance_id,
|
||||||
|
AppInstanceId* out_app_instance_id
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as app_execute(), but installs @a bindings into the new instance's fd table before its
|
||||||
|
* task begins executing. See app_start_with_streams()'s own doc for stream ownership.
|
||||||
|
* @param[in] bindings see app_start_with_streams()
|
||||||
|
* @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type
|
||||||
|
* @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range
|
||||||
|
* @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t app_execute_with_streams(
|
||||||
|
struct AppLocation location,
|
||||||
|
struct AppStackConfig stack,
|
||||||
|
int argc,
|
||||||
|
const char* const argv[],
|
||||||
|
const struct AppStreamBinding* bindings,
|
||||||
|
size_t binding_count,
|
||||||
|
AppInstanceId* out_app_instance_id
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines app_execute_for_result() and app_execute_with_streams().
|
||||||
|
* @param[in] bindings see app_start_with_streams()
|
||||||
|
* @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type
|
||||||
|
* @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range
|
||||||
|
* @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t app_execute_for_result_with_streams(
|
||||||
|
struct AppLocation location,
|
||||||
|
struct AppStackConfig stack,
|
||||||
|
int argc,
|
||||||
|
const char* const argv[],
|
||||||
|
const struct AppStreamBinding* bindings,
|
||||||
|
size_t binding_count,
|
||||||
|
AppInstanceId parent_instance_id,
|
||||||
|
AppInstanceId* out_app_instance_id
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports whether @a location is runnable on this target: the extension and header a loader
|
||||||
|
* requires (e.g. an ELF's class/data/type/machine), not whether it lives anywhere in particular.
|
||||||
|
* Any executable app is runnable from any path. Cheap enough to call while listing a directory.
|
||||||
|
* @return false if @a location can't be run, or if no AppLoaderApi is registered for its type
|
||||||
|
*/
|
||||||
|
bool app_is_executable(struct AppLocation location);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "location.h"
|
#include "location.h"
|
||||||
|
|
||||||
@@ -18,15 +19,8 @@ extern "C" {
|
|||||||
* APP_LOCATION_PATH must register under. Implemented by a platform module (e.g. app-esp32-module). */
|
* APP_LOCATION_PATH must register under. Implemented by a platform module (e.g. app-esp32-module). */
|
||||||
#define APP_LOADER_PATH_SERVICE_ID "app-loader-path"
|
#define APP_LOADER_PATH_SERVICE_ID "app-loader-path"
|
||||||
|
|
||||||
/**
|
/** Entry point signature for an APP_LOCATION_MEMORY app.
|
||||||
* Entry point signature for an APP_LOCATION_MEMORY app: a function linked directly into this
|
* AppManifest::location.location holds this cast to void*. */
|
||||||
* firmware binary. Called on the dedicated task app-module's scheduler spawns for this instance,
|
|
||||||
* blocking for the app's whole lifetime - same contract as an external app's main(). Use
|
|
||||||
* app_scheduler_current_app_id() to identify this running instance (e.g. with
|
|
||||||
* app_event_subscribe()/window_manager_create()/etc.). The instance closes when this function
|
|
||||||
* returns - no separate call is needed.
|
|
||||||
* AppManifest::location.location holds this cast to void*.
|
|
||||||
*/
|
|
||||||
typedef int32_t (*AppMainFn)(int argc, char* argv[]);
|
typedef int32_t (*AppMainFn)(int argc, char* argv[]);
|
||||||
|
|
||||||
typedef void* AppRuntime;
|
typedef void* AppRuntime;
|
||||||
@@ -53,6 +47,11 @@ struct AppLoaderApi {
|
|||||||
|
|
||||||
/** Releases whatever load() allocated. Called after run() returns. */
|
/** Releases whatever load() allocated. Called after run() returns. */
|
||||||
void (*unload)(AppRuntime runtime);
|
void (*unload)(AppRuntime runtime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports whether this loader could load and run whatever @a location points at, without actually loading it.
|
||||||
|
*/
|
||||||
|
bool (*is_executable)(struct AppLocation location);
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@@ -44,51 +44,8 @@ error_t app_manager_find_manifest(const char* id, struct AppManifest* out_manife
|
|||||||
typedef void (*AppManifestVisitorFn)(const struct AppManifest* manifest, void* context);
|
typedef void (*AppManifestVisitorFn)(const struct AppManifest* manifest, void* context);
|
||||||
void app_manager_for_each_manifest(AppManifestVisitorFn visitor, void* context);
|
void app_manager_for_each_manifest(AppManifestVisitorFn visitor, void* context);
|
||||||
|
|
||||||
/**
|
/** One fd-to-stream binding for app_start_with_streams() (app/start.h). Every field is passed
|
||||||
* Starts a new instance of the app registered under @a id. Every app instance gets its own
|
* through to app_stream_subscribe() as-is; see its own doc for the ownership contracts. */
|
||||||
* dedicated task for its entire lifetime - starting an app never asks any other app to give up
|
|
||||||
* its task, and multiple instances (of the same or different apps) can be Active at once.
|
|
||||||
* @param[in] id the manifest id to start
|
|
||||||
* @param[out] out_app_instance_id the id of the new app instance
|
|
||||||
* @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered
|
|
||||||
* @retval ERROR_NONE on success
|
|
||||||
*/
|
|
||||||
error_t app_manager_start(const char* id, AppInstanceId* out_app_instance_id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as app_manager_start(), but also passes @a argc/@a argv to the new instance's own main
|
|
||||||
* function (see app/loader.h's AppMainFn) - modelled on a C program's main(argc, argv). For
|
|
||||||
* regular (non-modal) navigations that need to pass data to the target app (e.g. "show details
|
|
||||||
* for this app id") without expecting a result back.
|
|
||||||
* @param[in] argv @a argc strings; app-module makes its own deep copy before returning, so
|
|
||||||
* @a argv and the strings it points to may be freed/go out of scope immediately after this call
|
|
||||||
* returns (e.g. safe to pass a stack-local array of a caller's own std::string::c_str()s).
|
|
||||||
*/
|
|
||||||
error_t app_manager_start_with_parameters(const char* id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts @a id as a modal child of @a parent_instance_id, for the purpose of receiving a
|
|
||||||
* result. The parent keeps running (window_manager's own multi-window stack handles burying its
|
|
||||||
* window while the child is shown).
|
|
||||||
*
|
|
||||||
* When the child's task exits, an APP_EVENT_RESULT is delivered to @a parent_instance_id -
|
|
||||||
* result is whatever the child's AppMainFn/AppLoaderApi::run() returned - unless
|
|
||||||
* @a parent_instance_id is 0, in which case no result is delivered (fire-and-forget, for
|
|
||||||
* callers with no app_instance_id of their own). The parent is then responsible for calling
|
|
||||||
* app_manager_stop() on the child's instance id to fully reap it. Children that need to hand
|
|
||||||
* back more than an int32_t (e.g. picked text, a path) expose their own "get last result"
|
|
||||||
* getter for the parent to call after receiving the event - see e.g.
|
|
||||||
* tt::app::inputdialog::getLastText().
|
|
||||||
* @param[in] argv @a argc strings; app-module makes its own deep copy before returning (same as
|
|
||||||
* app_manager_start_with_parameters()), so @a argv and the strings it points to may be
|
|
||||||
* freed/go out of scope immediately after this call returns.
|
|
||||||
* @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered
|
|
||||||
* @retval ERROR_NONE on success
|
|
||||||
*/
|
|
||||||
error_t app_manager_start_for_result(const char* id, AppInstanceId parent_instance_id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id);
|
|
||||||
|
|
||||||
/** One fd-to-stream binding for app_manager_start_with_streams(). Every field is passed through
|
|
||||||
* to app_stream_subscribe() as-is; see its own doc for the ownership contracts. */
|
|
||||||
struct AppStreamBinding {
|
struct AppStreamBinding {
|
||||||
int producer_fd;
|
int producer_fd;
|
||||||
struct AppStream* stream;
|
struct AppStream* stream;
|
||||||
@@ -97,37 +54,6 @@ struct AppStreamBinding {
|
|||||||
struct TaskEventGroup* event_group;
|
struct TaskEventGroup* event_group;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as app_manager_start(), but installs @a bindings into the new instance's fd table before
|
|
||||||
* its task begins executing (e.g. a child's stdio, piped through parent-owned AppStreams; see
|
|
||||||
* app/stream.h). Writes the new instance's id into each bound stream's producer_id itself, since
|
|
||||||
* the caller cannot know it in advance.
|
|
||||||
* @param[in] bindings @a binding_count entries; each stream and buffer must stay alive (see
|
|
||||||
* app_stream_subscribe()) until unsubscribed or the child exits.
|
|
||||||
* @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered
|
|
||||||
* @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range
|
|
||||||
* @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim
|
|
||||||
* @retval ERROR_NONE on success
|
|
||||||
*/
|
|
||||||
error_t app_manager_start_with_streams(const char* id, const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Combines app_manager_start_for_result() and app_manager_start_with_streams(): starts @a id as
|
|
||||||
* a modal child of @a parent_instance_id (see app_manager_start_for_result()'s own doc for the
|
|
||||||
* result-delivery contract) with @a bindings installed into its fd table before its task begins
|
|
||||||
* executing (see app_manager_start_with_streams()'s own doc for stream ownership). For a child
|
|
||||||
* that needs to hand back more than an int32_t (e.g. a path) via its own stdout instead of the
|
|
||||||
* "get last result" getter pattern (see app_manager_start_for_result()) - see e.g.
|
|
||||||
* tt::app::fileselection::startForExistingFile().
|
|
||||||
* @param[in] argv see app_manager_start_for_result().
|
|
||||||
* @param[in] bindings see app_manager_start_with_streams().
|
|
||||||
* @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered
|
|
||||||
* @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range
|
|
||||||
* @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim
|
|
||||||
* @retval ERROR_NONE on success
|
|
||||||
*/
|
|
||||||
error_t app_manager_start_for_result_with_streams(const char* id, AppInstanceId parent_instance_id, int argc, const char* const argv[], const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop an app instance permanently. Emits APP_EVENT_CLOSE and bound-waits for its task to exit
|
* Stop an app instance permanently. Emits APP_EVENT_CLOSE and bound-waits for its task to exit
|
||||||
* if it was running.
|
* if it was running.
|
||||||
@@ -143,7 +69,7 @@ AppInstanceState app_manager_get_state(AppInstanceId app_instance_id);
|
|||||||
/**
|
/**
|
||||||
* @param[out] out_app_instance_id set to the instance id of the topmost currently-Active app -
|
* @param[out] out_app_instance_id set to the instance id of the topmost currently-Active app -
|
||||||
* the most recently started of whichever instances are Active (a modal child launched via
|
* the most recently started of whichever instances are Active (a modal child launched via
|
||||||
* app_manager_start_for_result() stays Active alongside its parent while shown, so this
|
* app_start_for_result() (app/start.h) stays Active alongside its parent while shown, so this
|
||||||
* correctly picks the child, not the parent, while a dialog is up).
|
* correctly picks the child, not the parent, while a dialog is up).
|
||||||
* @retval ERROR_NOT_FOUND no app is Active
|
* @retval ERROR_NOT_FOUND no app is Active
|
||||||
* @retval ERROR_NONE on success
|
* @retval ERROR_NONE on success
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <app/manager.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains functions to start and run apps that were registered to the app manager.
|
||||||
|
* It differs from execute.h which runs executables from a specific path.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts @a id, a manifest already registered via app_manager_add(), passing @a argc/@a argv to
|
||||||
|
* the new instance's own main function (see app/loader.h's AppMainFn), modelled on a C program's
|
||||||
|
* main(argc, argv). For regular (non-modal) navigations that need to pass data to the target app
|
||||||
|
* (e.g. "show details for this app id") without expecting a result back.
|
||||||
|
* @param[in] argv @a argc strings; app-module makes its own deep copy before returning, so
|
||||||
|
* @a argv and the strings it points to may be freed/go out of scope immediately after this call
|
||||||
|
* returns (e.g. safe to pass a stack-local array of a caller's own std::string::c_str()s).
|
||||||
|
* @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t app_start(const char* id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts @a id as a child of @a parent_instance_id, for the purpose of receiving a result.
|
||||||
|
*
|
||||||
|
* When the child's task exits, an APP_EVENT_RESULT is delivered to @a parent_instance_id.
|
||||||
|
* The result is whatever the child's AppMainFn/AppLoaderApi::run() returned, unless
|
||||||
|
* @a parent_instance_id is 0, in which case no result is delivered (fire-and-forget, for
|
||||||
|
* callers with no app_instance_id of their own). The parent is then responsible for calling
|
||||||
|
* app_manager_stop() on the child's instance id to fully reap it.
|
||||||
|
*
|
||||||
|
* @param[in] argv @a argc strings; app-module makes its own deep copy before returning (same as
|
||||||
|
* app_start()), so @a argv and the strings it points to may be
|
||||||
|
* freed/go out of scope immediately after this call returns.
|
||||||
|
* @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t app_start_for_result(const char* id, int argc, const char* const argv[], AppInstanceId parent_instance_id, AppInstanceId* out_app_instance_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as app_start(), but installs @a bindings into the new instance's fd table before
|
||||||
|
* its task begins executing (e.g. a child's stdio, piped through parent-owned AppStreams; see
|
||||||
|
* app/stream.h). Writes the new instance's id into each bound stream's producer_id itself, since
|
||||||
|
* the caller cannot know it in advance.
|
||||||
|
* @param[in] bindings @a binding_count entries; each stream and buffer must stay alive (see
|
||||||
|
* app_stream_subscribe()) until unsubscribed or the child exits.
|
||||||
|
* @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered
|
||||||
|
* @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range
|
||||||
|
* @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t app_start_with_streams(const char* id, const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines app_start_for_result() and app_start_with_streams(): starts @a id as
|
||||||
|
* a modal child of @a parent_instance_id (see app_start_for_result()'s own doc for the
|
||||||
|
* result-delivery contract) with @a bindings installed into its fd table before its task begins
|
||||||
|
* executing (see app_start_with_streams()'s own doc for stream ownership).
|
||||||
|
* For a child that needs to hand back more than an int32_t (e.g. a path) via its own stdout.
|
||||||
|
* @param[in] argv see app_start_for_result().
|
||||||
|
* @param[in] bindings see app_start_with_streams().
|
||||||
|
* @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered
|
||||||
|
* @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range
|
||||||
|
* @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t app_start_for_result_with_streams(const char* id, int argc, const char* const argv[], const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId parent_instance_id, AppInstanceId* out_app_instance_id);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -37,7 +37,7 @@ struct AppStream {
|
|||||||
AppInstanceId producer_id;
|
AppInstanceId producer_id;
|
||||||
TaskHandle_t producer_task;
|
TaskHandle_t producer_task;
|
||||||
/** fd this stream is installed at in producer_id's fd table; set by
|
/** fd this stream is installed at in producer_id's fd table; set by
|
||||||
* app_stream_subscribe()/app_manager_start_with_streams(), used by
|
* app_stream_subscribe()/app_start_with_streams(), used by
|
||||||
* app_stream_unsubscribe() to find it again. */
|
* app_stream_unsubscribe() to find it again. */
|
||||||
int producer_fd;
|
int producer_fd;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <new>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep-copies @a argv (@a argc <= 0 => NULL, matching "no parameters"). Caller passes the result
|
||||||
|
* to app_scheduler_start(), which takes ownership regardless of outcome.
|
||||||
|
* @return NULL if @a argc <= 0 (no parameters), or if allocation failed. For @a argc > 0,
|
||||||
|
* these are the only cases that produce NULL, so a caller can tell them apart by its own
|
||||||
|
* already-known @a argc: NULL back from a positive @a argc always means allocation failed.
|
||||||
|
* All partial allocations are freed before returning NULL, so failure never leaks memory.
|
||||||
|
*/
|
||||||
|
inline char** app_arguments_copy(int argc, const char* const argv[]) {
|
||||||
|
if (argc <= 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* copy = new (std::nothrow) char*[argc + 1];
|
||||||
|
if (copy == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int copied = 0;
|
||||||
|
for (; copied < argc; copied++) {
|
||||||
|
size_t length = strlen(argv[copied]);
|
||||||
|
copy[copied] = new (std::nothrow) char[length + 1];
|
||||||
|
if (copy[copied] == nullptr) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
memcpy(copy[copied], argv[copied], length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copied < argc) {
|
||||||
|
for (int i = 0; i < copied; i++) {
|
||||||
|
delete[] copy[i];
|
||||||
|
}
|
||||||
|
delete[] copy;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy[argc] = nullptr;
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees a deep-copied argv previously built by app_arguments_copy(): each individually
|
||||||
|
* heap-allocated string, then the array itself. Safe to call with count == 0 / values == nullptr
|
||||||
|
* (no-op).
|
||||||
|
*/
|
||||||
|
inline void app_arguments_free(int count, char** values) {
|
||||||
|
if (values == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
delete[] values[i];
|
||||||
|
}
|
||||||
|
delete[] values;
|
||||||
|
}
|
||||||
@@ -38,21 +38,22 @@ struct AppCompletionSignal {
|
|||||||
/** A registered/running app instance, as tracked internally by app-module. */
|
/** A registered/running app instance, as tracked internally by app-module. */
|
||||||
struct AppInstanceRecord {
|
struct AppInstanceRecord {
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
|
/** NULL for an instance started via app_execute() (app/execute.h; no manifest involved). */
|
||||||
const AppManifest* manifest;
|
const AppManifest* manifest;
|
||||||
AppInstanceState state;
|
AppInstanceState state;
|
||||||
/** The FreeRTOS task currently executing AppLoaderApi::run() for this instance; NULL when not running. */
|
/** The FreeRTOS task currently executing AppLoaderApi::run() for this instance; NULL when not running. */
|
||||||
TaskHandle_t task;
|
TaskHandle_t task;
|
||||||
|
|
||||||
/** 0 for a top-level launch (app_manager_start()). Non-zero for a modal child launched via
|
/** 0 for a top-level launch (app_start()). Non-zero for a modal child launched via
|
||||||
* app_manager_start_for_result() - the instance that receives this child's APP_EVENT_RESULT. */
|
* app_start_for_result() - the instance that receives this child's APP_EVENT_RESULT. */
|
||||||
uint32_t parent_id = 0;
|
uint32_t parent_id = 0;
|
||||||
|
|
||||||
/** This instance's completion signal - see AppCompletionSignal. Set once by
|
/** This instance's completion signal - see AppCompletionSignal. Set once by
|
||||||
* app_scheduler_start(), never reassigned. */
|
* app_scheduler_start(), never reassigned. */
|
||||||
AppCompletionSignal* completion = nullptr;
|
AppCompletionSignal* completion = nullptr;
|
||||||
|
|
||||||
/** This instance's fd table. Constructed by start_internal() before insertion into
|
/** This instance's fd table. Constructed by app_manager_start_internal() before insertion
|
||||||
* AppLedger::instances, torn down (every open fd closed) when the instance's task exits. */
|
* into AppLedger::instances, torn down (every open fd closed) when the instance's task exits. */
|
||||||
AppFdTable fd_table {};
|
AppFdTable fd_table {};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,17 +71,3 @@ inline AppLedger& app_ledger() {
|
|||||||
static AppLedger ledger;
|
static AppLedger ledger;
|
||||||
return ledger;
|
return ledger;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees a deep-copied argv previously built by app_manager_start_with_parameters()/app_manager_start_for_result():
|
|
||||||
* each individually heap-allocated string, then the array itself. Safe to call with count == 0 values == nullptr (no-op).
|
|
||||||
*/
|
|
||||||
inline void app_ledger_free_arguments(int count, char** values) {
|
|
||||||
if (values == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
delete[] values[i];
|
|
||||||
}
|
|
||||||
delete[] values;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <app/manager.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared core behind every app_start*() (app/manager.h) and app_execute*()
|
||||||
|
* (app/execute.h) entry point: deep-copies @a argv, allocates an instance id, installs
|
||||||
|
* @a bindings into the new instance's fd table before app_scheduler_start() is called, then
|
||||||
|
* starts it. @a manifest may be NULL for a location-based start with no manifest at all.
|
||||||
|
* @retval ERROR_INVALID_ARGUMENT @a binding_count is nonzero but @a bindings is NULL
|
||||||
|
* @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type
|
||||||
|
* @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range
|
||||||
|
* @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t app_manager_start_internal(const struct AppManifest* manifest, struct AppLocation location, struct AppStackConfig stack, AppInstanceId parent_instance_id, int argc, const char* const argv[], const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#include <app/elf_check.h>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr size_t ELF_HEADER_PREFIX_SIZE = 20; // e_ident[16] + e_type(2) + e_machine(2)
|
||||||
|
constexpr uint8_t ELF_MAGIC[4] = { 0x7f, 'E', 'L', 'F' };
|
||||||
|
|
||||||
|
uint16_t read_le16(const uint8_t* bytes) {
|
||||||
|
return static_cast<uint16_t>(bytes[0] | (bytes[1] << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool elf_check_file(const char* path, const struct ElfRequirements* requirements) {
|
||||||
|
FILE* file = fopen(path, "rb");
|
||||||
|
if (file == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t header[ELF_HEADER_PREFIX_SIZE];
|
||||||
|
size_t read = fread(header, 1, sizeof(header), file);
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
if (read != sizeof(header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(header, ELF_MAGIC, sizeof(ELF_MAGIC)) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t elf_class = header[4]; // e_ident[EI_CLASS]
|
||||||
|
uint8_t data = header[5]; // e_ident[EI_DATA]
|
||||||
|
uint16_t type = read_le16(header + 16); // e_type
|
||||||
|
uint16_t machine = read_le16(header + 18); // e_machine
|
||||||
|
|
||||||
|
return elf_class == requirements->elf_class
|
||||||
|
&& data == requirements->data
|
||||||
|
&& type == requirements->type
|
||||||
|
&& machine == requirements->machine;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#include <app/execute.h>
|
||||||
|
|
||||||
|
#include <app/loader.h>
|
||||||
|
#include <app/location.h>
|
||||||
|
#include <app/private/manager_internal.h>
|
||||||
|
|
||||||
|
#include <service/instance.h>
|
||||||
|
#include <service/manager.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Same lookup as scheduler.cpp's own (private) find_loader_api() - duplicated rather than
|
||||||
|
// shared since it's a handful of lines and neither file depends on the other.
|
||||||
|
const char* loader_service_id_for(AppLocationType type) {
|
||||||
|
return (type == APP_LOCATION_MEMORY) ? APP_LOADER_MEMORY_SERVICE_ID : APP_LOADER_PATH_SERVICE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppLoaderApi* find_loader_api(AppLocationType type) {
|
||||||
|
ServiceInstance* instance = service_manager_find_instance(loader_service_id_for(type));
|
||||||
|
if (instance == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return static_cast<const AppLoaderApi*>(service_instance_get_data(instance));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
error_t app_execute(AppLocation location, AppStackConfig stack, int argc, const char* const argv[], AppInstanceId* out_app_instance_id) {
|
||||||
|
return app_manager_start_internal(nullptr, location, stack, 0, argc, argv, nullptr, 0, out_app_instance_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t app_execute_for_result(AppLocation location, AppStackConfig stack, int argc, const char* const argv[], AppInstanceId parent_instance_id, AppInstanceId* out_app_instance_id) {
|
||||||
|
return app_manager_start_internal(nullptr, location, stack, parent_instance_id, argc, argv, nullptr, 0, out_app_instance_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t app_execute_with_streams(AppLocation location, AppStackConfig stack, int argc, const char* const argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) {
|
||||||
|
return app_manager_start_internal(nullptr, location, stack, 0, argc, argv, bindings, binding_count, out_app_instance_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t app_execute_for_result_with_streams(AppLocation location, AppStackConfig stack, int argc, const char* const argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId parent_instance_id, AppInstanceId* out_app_instance_id) {
|
||||||
|
return app_manager_start_internal(nullptr, location, stack, parent_instance_id, argc, argv, bindings, binding_count, out_app_instance_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool app_is_executable(AppLocation location) {
|
||||||
|
const AppLoaderApi* loader = find_loader_api(location.type);
|
||||||
|
if (loader == nullptr || loader->is_executable == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return loader->is_executable(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
#include <app/loader.h>
|
#include <app/loader.h>
|
||||||
#include <app/manifest.h>
|
|
||||||
|
|
||||||
#include <service/instance.h>
|
#include <service/instance.h>
|
||||||
#include <service/manager.h>
|
#include <service/manager.h>
|
||||||
@@ -24,10 +23,15 @@ int32_t api_run(AppRuntime runtime, uint32_t /*app_instance_id*/, int argc, char
|
|||||||
void api_unload(AppRuntime /*unused*/) {
|
void api_unload(AppRuntime /*unused*/) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool api_is_executable(AppLocation location) {
|
||||||
|
return location.type == APP_LOCATION_MEMORY && location.location != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
AppLoaderApi memory_loader_api = {
|
AppLoaderApi memory_loader_api = {
|
||||||
.load = api_load,
|
.load = api_load,
|
||||||
.run = api_run,
|
.run = api_run,
|
||||||
.unload = api_unload,
|
.unload = api_unload,
|
||||||
|
.is_executable = api_is_executable,
|
||||||
};
|
};
|
||||||
|
|
||||||
void* create_service(const ServiceManifest*) {
|
void* create_service(const ServiceManifest*) {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
#include <app/metadata.h>
|
#include <app/metadata.h>
|
||||||
|
#include <app/private/arguments.h>
|
||||||
#include <app/private/fd_table.h>
|
#include <app/private/fd_table.h>
|
||||||
#include <app/private/fs.h>
|
#include <app/private/fs.h>
|
||||||
#include <app/private/ledger.h>
|
#include <app/private/ledger.h>
|
||||||
|
#include <app/private/manager_internal.h>
|
||||||
#include <app/private/scheduler.h>
|
#include <app/private/scheduler.h>
|
||||||
|
|
||||||
#include <tactility/concurrent/mutex.h>
|
#include <tactility/concurrent/mutex.h>
|
||||||
@@ -70,65 +72,37 @@ void app_manager_for_each_manifest(AppManifestVisitorFn visitor, void* context)
|
|||||||
mutex_unlock(&ledger.mutex);
|
mutex_unlock(&ledger.mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
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);
|
||||||
// Deep-copies argv (argc <= 0 => NULL, matching "no parameters"). Caller passes the result to
|
if (argc > 0 && argv == nullptr) {
|
||||||
// app_scheduler_start(), which takes ownership regardless of outcome.
|
return ERROR_OUT_OF_MEMORY;
|
||||||
char** copy_arguments(int argc, const char* const argv[]) {
|
|
||||||
if (argc <= 0) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
auto* copy = new char*[argc + 1];
|
|
||||||
for (int i = 0; i < argc; i++) {
|
|
||||||
size_t length = strlen(argv[i]);
|
|
||||||
copy[i] = new char[length + 1];
|
|
||||||
memcpy(copy[i], argv[i], length + 1);
|
|
||||||
}
|
|
||||||
copy[argc] = nullptr;
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Takes ownership of argv (already a deep copy, or NULL/argc==0) regardless of outcome -
|
|
||||||
// app_scheduler_start() frees it on any failure path, and the spawned task frees it once its
|
|
||||||
// run() returns. @a bindings (@a binding_count entries, may be NULL/0) are subscribed into the
|
|
||||||
// new instance's fd table before app_scheduler_start() is called, so they're in place before its
|
|
||||||
// task begins executing (see app_manager_start_with_streams()).
|
|
||||||
error_t start_internal(const char* id, AppInstanceId parent_instance_id, int argc, char* argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) {
|
|
||||||
if (binding_count != 0 && bindings == nullptr) {
|
if (binding_count != 0 && bindings == nullptr) {
|
||||||
app_ledger_free_arguments(argc, argv);
|
app_arguments_free(argc, argv);
|
||||||
return ERROR_INVALID_ARGUMENT;
|
return ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& ledger = app_ledger();
|
auto& ledger = app_ledger();
|
||||||
|
|
||||||
mutex_lock(&ledger.mutex);
|
mutex_lock(&ledger.mutex);
|
||||||
auto manifest_iterator = ledger.manifests.find(id);
|
|
||||||
if (manifest_iterator == ledger.manifests.end()) {
|
|
||||||
mutex_unlock(&ledger.mutex);
|
|
||||||
app_ledger_free_arguments(argc, argv);
|
|
||||||
return ERROR_NOT_FOUND;
|
|
||||||
}
|
|
||||||
const AppManifest* manifest = manifest_iterator->second;
|
|
||||||
|
|
||||||
AppInstanceId target_id = ledger.next_instance_id++;
|
AppInstanceId target_id = ledger.next_instance_id++;
|
||||||
AppInstanceRecord record { .id = target_id, .manifest = manifest, .state = APP_INSTANCE_STATE_STARTING, .task = nullptr };
|
AppInstanceRecord record { .id = target_id, .manifest = manifest, .state = APP_INSTANCE_STATE_STARTING, .task = nullptr };
|
||||||
record.parent_id = parent_instance_id;
|
record.parent_id = parent_instance_id;
|
||||||
ledger.instances[target_id] = record;
|
ledger.instances[target_id] = record;
|
||||||
// Constructed on the map-resident copy, not the local `record` about to go out of scope.
|
// Construct on the map-resident copy, not `record`: fds[] point into slots[] by address
|
||||||
// AppFdTable::fds[] entries point into AppFdTable::slots[] by address (see fd_table.h), so
|
// (fd_table.h), so constructing on the stack-local record would leave them dangling.
|
||||||
// constructing before the copy above would leave them pointing at stack storage.
|
|
||||||
app_fd_table_construct(&ledger.instances[target_id].fd_table);
|
app_fd_table_construct(&ledger.instances[target_id].fd_table);
|
||||||
mutex_unlock(&ledger.mutex);
|
mutex_unlock(&ledger.mutex);
|
||||||
|
|
||||||
LOG_I(TAG, "[instance %d] starting %s with parent %d", target_id, manifest->id, parent_instance_id);
|
LOG_I(TAG, "[instance %d] starting %s with parent %d", target_id, manifest != nullptr ? manifest->id : "<unregistered>", parent_instance_id);
|
||||||
|
|
||||||
for (size_t i = 0; i < binding_count; i++) {
|
for (size_t i = 0; i < binding_count; i++) {
|
||||||
error_t bind_result = app_stream_subscribe(bindings[i].stream, bindings[i].buffer, bindings[i].buffer_capacity, bindings[i].event_group, target_id, bindings[i].producer_fd);
|
error_t bind_result = app_stream_subscribe(bindings[i].stream, bindings[i].buffer, bindings[i].buffer_capacity, bindings[i].event_group, target_id, bindings[i].producer_fd);
|
||||||
if (bind_result != ERROR_NONE) {
|
if (bind_result != ERROR_NONE) {
|
||||||
LOG_E(TAG, "[instance %d] Failed to bind stream at fd %d: %s", target_id, bindings[i].producer_fd, error_to_string(bind_result));
|
LOG_E(TAG, "[instance %d] Failed to bind stream at fd %d: %s", target_id, bindings[i].producer_fd, error_to_string(bind_result));
|
||||||
// Undo bindings[0..i): app_fd_table_teardown() below only closes each stream. It
|
// Undo bindings[0..i): teardown() below only closes the fd, not the event bits
|
||||||
// doesn't release the event bits app_stream_subscribe() claimed or destruct
|
// or mutex app_stream_subscribe() claimed; only app_stream_unsubscribe() does.
|
||||||
// stream->internal.mutex; only app_stream_unsubscribe() does that.
|
|
||||||
for (size_t j = 0; j < i; j++) {
|
for (size_t j = 0; j < i; j++) {
|
||||||
app_stream_unsubscribe(bindings[j].stream);
|
app_stream_unsubscribe(bindings[j].stream);
|
||||||
}
|
}
|
||||||
@@ -136,15 +110,13 @@ error_t start_internal(const char* id, AppInstanceId parent_instance_id, int arg
|
|||||||
app_fd_table_teardown(&ledger.instances[target_id].fd_table);
|
app_fd_table_teardown(&ledger.instances[target_id].fd_table);
|
||||||
ledger.instances.erase(target_id);
|
ledger.instances.erase(target_id);
|
||||||
mutex_unlock(&ledger.mutex);
|
mutex_unlock(&ledger.mutex);
|
||||||
app_ledger_free_arguments(argc, argv);
|
app_arguments_free(argc, argv);
|
||||||
return bind_result;
|
return bind_result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
error_t error = app_scheduler_start(target_id, manifest->location, manifest->stack, argc, argv);
|
error_t error = app_scheduler_start(target_id, location, stack, argc, argv);
|
||||||
if (error != ERROR_NONE) {
|
if (error != ERROR_NONE) {
|
||||||
// Every binding succeeded before app_scheduler_start() failed. Unsubscribe all of them,
|
|
||||||
// same reasoning as the bind-failure path above.
|
|
||||||
for (size_t j = 0; j < binding_count; j++) {
|
for (size_t j = 0; j < binding_count; j++) {
|
||||||
app_stream_unsubscribe(bindings[j].stream);
|
app_stream_unsubscribe(bindings[j].stream);
|
||||||
}
|
}
|
||||||
@@ -160,28 +132,6 @@ error_t start_internal(const char* id, AppInstanceId parent_instance_id, int arg
|
|||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
error_t app_manager_start(const char* id, AppInstanceId* out_app_instance_id) {
|
|
||||||
return start_internal(id, 0, 0, nullptr, nullptr, 0, out_app_instance_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
error_t app_manager_start_with_parameters(const char* id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id) {
|
|
||||||
return start_internal(id, 0, argc, copy_arguments(argc, argv), nullptr, 0, out_app_instance_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
error_t app_manager_start_for_result(const char* id, AppInstanceId parent_instance_id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id) {
|
|
||||||
return start_internal(id, parent_instance_id, argc, copy_arguments(argc, argv), nullptr, 0, out_app_instance_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
error_t app_manager_start_with_streams(const char* id, const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) {
|
|
||||||
return start_internal(id, 0, 0, nullptr, bindings, binding_count, out_app_instance_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
error_t app_manager_start_for_result_with_streams(const char* id, AppInstanceId parent_instance_id, int argc, const char* const argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) {
|
|
||||||
return start_internal(id, parent_instance_id, argc, copy_arguments(argc, argv), bindings, binding_count, out_app_instance_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
error_t app_manager_stop(AppInstanceId app_instance_id) {
|
error_t app_manager_stop(AppInstanceId app_instance_id) {
|
||||||
return app_scheduler_stop(app_instance_id, pdMS_TO_TICKS(2000));
|
return app_scheduler_stop(app_instance_id, pdMS_TO_TICKS(2000));
|
||||||
}
|
}
|
||||||
@@ -200,8 +150,7 @@ error_t app_manager_get_topmost_instance_id(AppInstanceId* out_app_instance_id)
|
|||||||
mutex_lock(&ledger.mutex);
|
mutex_lock(&ledger.mutex);
|
||||||
AppInstanceId topmost_id = 0;
|
AppInstanceId topmost_id = 0;
|
||||||
for (auto& [instance_id, record] : ledger.instances) {
|
for (auto& [instance_id, record] : ledger.instances) {
|
||||||
// Instance ids are handed out in increasing order (AppLedger::next_instance_id), so
|
// Ids increase monotonically, so the highest Active id is the most recent.
|
||||||
// the highest Active id is also the most recently started one.
|
|
||||||
if (record.state == APP_INSTANCE_STATE_ACTIVE && instance_id > topmost_id) {
|
if (record.state == APP_INSTANCE_STATE_ACTIVE && instance_id > topmost_id) {
|
||||||
topmost_id = instance_id;
|
topmost_id = instance_id;
|
||||||
}
|
}
|
||||||
@@ -230,7 +179,8 @@ error_t app_manager_get_topmost_app_id(char* buffer, size_t buffer_size) {
|
|||||||
auto& ledger = app_ledger();
|
auto& ledger = app_ledger();
|
||||||
mutex_lock(&ledger.mutex);
|
mutex_lock(&ledger.mutex);
|
||||||
auto iterator = ledger.instances.find(topmost_id);
|
auto iterator = ledger.instances.find(topmost_id);
|
||||||
const char* app_id = (iterator != ledger.instances.end()) ? iterator->second.manifest->id : nullptr;
|
const AppManifest* manifest = (iterator != ledger.instances.end()) ? iterator->second.manifest : nullptr;
|
||||||
|
const char* app_id = manifest != nullptr ? manifest->id : nullptr;
|
||||||
mutex_unlock(&ledger.mutex);
|
mutex_unlock(&ledger.mutex);
|
||||||
|
|
||||||
if (app_id == nullptr) {
|
if (app_id == nullptr) {
|
||||||
@@ -251,10 +201,8 @@ error_t app_manager_get_topmost_app_id(char* buffer, size_t buffer_size) {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Owns the AppManifest (and its id/name/path strings) that app_manager_add() only keeps a
|
// Owns the AppManifest (and its id/name/path strings) that app_manager_add() only keeps a
|
||||||
// non-owning pointer to (see app_manager_add()'s contract), for manifests registered by
|
// non-owning pointer to. Separate from app_install.cpp's registry: scanning only
|
||||||
// app_manager_install_path_scan() specifically - separate from app_install.cpp's own registry,
|
// adds/removes registrations, never touches disk or running instances.
|
||||||
// since scanning only ever adds/removes manifest registrations and never touches files on disk
|
|
||||||
// or running instances (unlike app_install()/app_uninstall()).
|
|
||||||
struct ScannedAppManifest {
|
struct ScannedAppManifest {
|
||||||
std::string id;
|
std::string id;
|
||||||
std::string name;
|
std::string name;
|
||||||
@@ -301,15 +249,15 @@ void app_manager_install_path_scan(void) {
|
|||||||
app_fs_list_direct_subdirectories(root, found_app_dirs);
|
app_fs_list_direct_subdirectories(root, found_app_dirs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot of what's already registered, taken once so the rest of this scan can run without holding registry.mutex
|
// Snapshot once so the rest of the scan doesn't hold registry.mutex.
|
||||||
mutex_lock(®istry.mutex);
|
mutex_lock(®istry.mutex);
|
||||||
std::unordered_map<std::string, std::string> known_paths; // id -> path
|
std::unordered_map<std::string, std::string> known_paths;
|
||||||
for (const auto& [id, record] : registry.scanned) {
|
for (const auto& [id, record] : registry.scanned) {
|
||||||
known_paths.emplace(id, record->path);
|
known_paths.emplace(id, record->path);
|
||||||
}
|
}
|
||||||
mutex_unlock(®istry.mutex);
|
mutex_unlock(®istry.mutex);
|
||||||
|
|
||||||
// Stat each manifest and parse it entirely without registry.mutex held (due to filesystem IO being slow)
|
// Parses without registry.mutex held; filesystem IO is slow.
|
||||||
std::vector<std::unique_ptr<ScannedAppManifest>> new_records;
|
std::vector<std::unique_ptr<ScannedAppManifest>> new_records;
|
||||||
for (const auto& app_dir : found_app_dirs) {
|
for (const auto& app_dir : found_app_dirs) {
|
||||||
auto manifest_path = app_dir + "/manifest.properties";
|
auto manifest_path = app_dir + "/manifest.properties";
|
||||||
@@ -324,7 +272,7 @@ void app_manager_install_path_scan(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (known_paths.contains(metadata.app_id)) {
|
if (known_paths.contains(metadata.app_id)) {
|
||||||
continue; // already registered by an earlier scan
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto record = std::make_unique<ScannedAppManifest>();
|
auto record = std::make_unique<ScannedAppManifest>();
|
||||||
@@ -342,7 +290,6 @@ void app_manager_install_path_scan(void) {
|
|||||||
new_records.push_back(std::move(record));
|
new_records.push_back(std::move(record));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anything a previous scan registered whose directory has since disappeared gets unregistered below.
|
|
||||||
std::vector<std::string> missing_ids;
|
std::vector<std::string> missing_ids;
|
||||||
for (const auto& [id, path] : known_paths) {
|
for (const auto& [id, path] : known_paths) {
|
||||||
if (!app_fs_is_directory(path)) {
|
if (!app_fs_is_directory(path)) {
|
||||||
@@ -350,11 +297,9 @@ void app_manager_install_path_scan(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// app_manager_add()/app_manager_remove() take app-module's own ledger mutex internally -
|
// app_manager_add()/remove() take the ledger mutex internally, so calling them under
|
||||||
// calling them while holding registry.mutex would establish a registry.mutex -> ledger-
|
// registry.mutex would fix a lock order an opposite-order caller could deadlock against.
|
||||||
// mutex lock order that any future opposite-order path would deadlock against, so these
|
// registry.mutex is retaken afterward only to publish the in-memory results.
|
||||||
// also run with registry.mutex released. registry.mutex is taken only afterward, briefly,
|
|
||||||
// to publish the results (plain in-memory map updates, no I/O or other locks involved).
|
|
||||||
for (const auto& id : missing_ids) {
|
for (const auto& id : missing_ids) {
|
||||||
app_manager_remove(id.c_str());
|
app_manager_remove(id.c_str());
|
||||||
}
|
}
|
||||||
@@ -391,10 +336,8 @@ error_t app_manager_install_path_uninstall(const char* app_id) {
|
|||||||
auto path = iterator->second->path;
|
auto path = iterator->second->path;
|
||||||
mutex_unlock(®istry.mutex);
|
mutex_unlock(®istry.mutex);
|
||||||
|
|
||||||
// Stop every running instance that retains this manifest pointer, mirroring
|
// Mirrors stop_all_instances_of() in app_install.cpp. Collect under ledger.mutex, stop
|
||||||
// stop_all_instances_of() in app_install.cpp. Collect under ledger.mutex,
|
// outside it: app_manager_stop() bound-joins the thread, which itself takes ledger.mutex.
|
||||||
// then call app_manager_stop() outside it (that call bound-joins the
|
|
||||||
// instance's thread, which itself takes ledger.mutex in its thread_main).
|
|
||||||
std::vector<uint32_t> instance_ids;
|
std::vector<uint32_t> instance_ids;
|
||||||
auto& ledger = app_ledger();
|
auto& ledger = app_ledger();
|
||||||
mutex_lock(&ledger.mutex);
|
mutex_lock(&ledger.mutex);
|
||||||
@@ -409,14 +352,12 @@ error_t app_manager_install_path_uninstall(const char* app_id) {
|
|||||||
app_manager_stop(id);
|
app_manager_stop(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// app_manager_remove takes ledger.mutex internally - call outside both
|
// app_manager_remove() takes ledger.mutex; call outside registry.mutex too, matching
|
||||||
// registry.mutex and ledger.mutex to match the lock ordering in
|
// the lock order in app_manager_install_path_scan().
|
||||||
// app_manager_install_path_scan().
|
|
||||||
app_manager_remove(app_id);
|
app_manager_remove(app_id);
|
||||||
|
|
||||||
// Every instance has stopped and the manifest is unregistered — safe to
|
// Delete before erasing the scan record, so a failed deletion still leaves the
|
||||||
// delete the on-disk directory. Delete before erasing the scan record so
|
// entry discoverable for a retry.
|
||||||
// that a failed deletion leaves the entry discoverable for a retry.
|
|
||||||
if (!app_fs_delete_recursively(path)) {
|
if (!app_fs_delete_recursively(path)) {
|
||||||
return ERROR_RESOURCE;
|
return ERROR_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
|
#include <app/execute.h>
|
||||||
#include <app/install.h>
|
#include <app/install.h>
|
||||||
#include <app/io.h>
|
#include <app/io.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
@@ -7,11 +8,11 @@
|
|||||||
#include <app/metadata.h>
|
#include <app/metadata.h>
|
||||||
#include <app/paths.h>
|
#include <app/paths.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/stream.h>
|
#include <app/stream.h>
|
||||||
|
|
||||||
#include <service/manager.h>
|
#include <service/manager.h>
|
||||||
|
|
||||||
#include <tactility/concurrent/task_event_group.h>
|
|
||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
@@ -25,6 +26,12 @@ static const ModuleSymbol SYMBOLS[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(app_event_subscribe_with_app_id),
|
DEFINE_MODULE_SYMBOL(app_event_subscribe_with_app_id),
|
||||||
DEFINE_MODULE_SYMBOL(app_event_unsubscribe),
|
DEFINE_MODULE_SYMBOL(app_event_unsubscribe),
|
||||||
DEFINE_MODULE_SYMBOL(app_event_poll),
|
DEFINE_MODULE_SYMBOL(app_event_poll),
|
||||||
|
// app/execute
|
||||||
|
DEFINE_MODULE_SYMBOL(app_execute),
|
||||||
|
DEFINE_MODULE_SYMBOL(app_execute_for_result),
|
||||||
|
DEFINE_MODULE_SYMBOL(app_execute_with_streams),
|
||||||
|
DEFINE_MODULE_SYMBOL(app_execute_for_result_with_streams),
|
||||||
|
DEFINE_MODULE_SYMBOL(app_is_executable),
|
||||||
// app/install
|
// app/install
|
||||||
DEFINE_MODULE_SYMBOL(app_get_install_path),
|
DEFINE_MODULE_SYMBOL(app_get_install_path),
|
||||||
DEFINE_MODULE_SYMBOL(app_install),
|
DEFINE_MODULE_SYMBOL(app_install),
|
||||||
@@ -34,11 +41,6 @@ static const ModuleSymbol SYMBOLS[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(app_io_write),
|
DEFINE_MODULE_SYMBOL(app_io_write),
|
||||||
DEFINE_MODULE_SYMBOL(app_io_close),
|
DEFINE_MODULE_SYMBOL(app_io_close),
|
||||||
// app/manager
|
// app/manager
|
||||||
DEFINE_MODULE_SYMBOL(app_manager_start),
|
|
||||||
DEFINE_MODULE_SYMBOL(app_manager_start_with_parameters),
|
|
||||||
DEFINE_MODULE_SYMBOL(app_manager_start_for_result),
|
|
||||||
DEFINE_MODULE_SYMBOL(app_manager_start_with_streams),
|
|
||||||
DEFINE_MODULE_SYMBOL(app_manager_start_for_result_with_streams),
|
|
||||||
DEFINE_MODULE_SYMBOL(app_manager_stop),
|
DEFINE_MODULE_SYMBOL(app_manager_stop),
|
||||||
DEFINE_MODULE_SYMBOL(app_manager_get_state),
|
DEFINE_MODULE_SYMBOL(app_manager_get_state),
|
||||||
DEFINE_MODULE_SYMBOL(app_manager_find_manifest),
|
DEFINE_MODULE_SYMBOL(app_manager_find_manifest),
|
||||||
@@ -50,6 +52,11 @@ static const ModuleSymbol SYMBOLS[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(app_manager_install_path_add),
|
DEFINE_MODULE_SYMBOL(app_manager_install_path_add),
|
||||||
DEFINE_MODULE_SYMBOL(app_manager_install_path_scan),
|
DEFINE_MODULE_SYMBOL(app_manager_install_path_scan),
|
||||||
DEFINE_MODULE_SYMBOL(app_manager_install_path_uninstall),
|
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
|
// app/manifest
|
||||||
DEFINE_MODULE_SYMBOL(app_id_is_valid),
|
DEFINE_MODULE_SYMBOL(app_id_is_valid),
|
||||||
// app/metadata
|
// app/metadata
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
#include <app/instance.h>
|
#include <app/instance.h>
|
||||||
#include <app/loader.h>
|
#include <app/loader.h>
|
||||||
|
#include <app/private/arguments.h>
|
||||||
#include <app/private/event.h>
|
#include <app/private/event.h>
|
||||||
#include <app/private/fd_table.h>
|
#include <app/private/fd_table.h>
|
||||||
#include <app/private/ledger.h>
|
#include <app/private/ledger.h>
|
||||||
@@ -101,7 +102,7 @@ void set_task(AppInstanceId app_instance_id, TaskHandle_t task) {
|
|||||||
auto iterator = ledger.instances.find(app_instance_id);
|
auto iterator = ledger.instances.find(app_instance_id);
|
||||||
if (iterator != ledger.instances.end()) {
|
if (iterator != ledger.instances.end()) {
|
||||||
iterator->second.task = task;
|
iterator->second.task = task;
|
||||||
// Streams bound before this instance's task existed (app_manager_start_with_streams())
|
// Streams bound before this instance's task existed (app_start_with_streams())
|
||||||
// only got producer_task filled in as NULL at subscribe time. Backfill it now.
|
// only got producer_task filled in as NULL at subscribe time. Backfill it now.
|
||||||
AppFdTable& fd_table = iterator->second.fd_table;
|
AppFdTable& fd_table = iterator->second.fd_table;
|
||||||
for (auto& slot : fd_table.slots) {
|
for (auto& slot : fd_table.slots) {
|
||||||
@@ -168,7 +169,7 @@ const AppLoaderApi* find_loader_api(AppLocationType type) {
|
|||||||
return static_cast<const AppLoaderApi*>(service_instance_get_data(instance));
|
return static_cast<const AppLoaderApi*>(service_instance_get_data(instance));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this instance was launched via app_manager_start_for_result(), delivers @a result (its
|
// If this instance was launched via app_start_for_result(), delivers @a result (its
|
||||||
// own AppMainFn/AppLoaderApi::run() return value) to its parent. No-op for a top-level instance
|
// own AppMainFn/AppLoaderApi::run() return value) to its parent. No-op for a top-level instance
|
||||||
// (parent_id == 0).
|
// (parent_id == 0).
|
||||||
void deliver_result_to_parent_if_any(AppInstanceId app_instance_id, int32_t result) {
|
void deliver_result_to_parent_if_any(AppInstanceId app_instance_id, int32_t result) {
|
||||||
@@ -223,7 +224,7 @@ void app_task_main(void* context) {
|
|||||||
// response to APP_EVENT_CLOSE.
|
// response to APP_EVENT_CLOSE.
|
||||||
set_state(ctx->app_instance_id, APP_INSTANCE_STATE_STOPPED);
|
set_state(ctx->app_instance_id, APP_INSTANCE_STATE_STOPPED);
|
||||||
|
|
||||||
app_ledger_free_arguments(ctx->argc, ctx->argv);
|
app_arguments_free(ctx->argc, ctx->argv);
|
||||||
|
|
||||||
AppInstanceId app_instance_id = ctx->app_instance_id;
|
AppInstanceId app_instance_id = ctx->app_instance_id;
|
||||||
AppCompletionSignal* completion = ctx->completion;
|
AppCompletionSignal* completion = ctx->completion;
|
||||||
@@ -274,7 +275,7 @@ error_t app_scheduler_start(AppInstanceId app_instance_id, AppLocation location,
|
|||||||
const AppLoaderApi* loader = find_loader_api(location.type);
|
const AppLoaderApi* loader = find_loader_api(location.type);
|
||||||
if (loader == nullptr) {
|
if (loader == nullptr) {
|
||||||
LOG_E(TAG, "[instance %lu] No app loader is registered (service '%s' not found)", app_instance_id, loader_service_id_for(location.type));
|
LOG_E(TAG, "[instance %lu] No app loader is registered (service '%s' not found)", app_instance_id, loader_service_id_for(location.type));
|
||||||
app_ledger_free_arguments(argc, argv);
|
app_arguments_free(argc, argv);
|
||||||
return ERROR_NOT_FOUND;
|
return ERROR_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +283,7 @@ error_t app_scheduler_start(AppInstanceId app_instance_id, AppLocation location,
|
|||||||
error_t load_result = loader->load(location, &runtime);
|
error_t load_result = loader->load(location, &runtime);
|
||||||
if (load_result != ERROR_NONE) {
|
if (load_result != ERROR_NONE) {
|
||||||
LOG_E(TAG, "[instance %lu] Failed to load app: %s", app_instance_id, error_to_string(load_result));
|
LOG_E(TAG, "[instance %lu] Failed to load app: %s", app_instance_id, error_to_string(load_result));
|
||||||
app_ledger_free_arguments(argc, argv);
|
app_arguments_free(argc, argv);
|
||||||
return load_result;
|
return load_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +291,7 @@ error_t app_scheduler_start(AppInstanceId app_instance_id, AppLocation location,
|
|||||||
if (completion == nullptr) {
|
if (completion == nullptr) {
|
||||||
LOG_E(TAG, "[instance %lu] Failed to allocate app", app_instance_id);
|
LOG_E(TAG, "[instance %lu] Failed to allocate app", app_instance_id);
|
||||||
loader->unload(runtime);
|
loader->unload(runtime);
|
||||||
app_ledger_free_arguments(argc, argv);
|
app_arguments_free(argc, argv);
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
completion->semaphore = xSemaphoreCreateBinary();
|
completion->semaphore = xSemaphoreCreateBinary();
|
||||||
@@ -298,7 +299,7 @@ error_t app_scheduler_start(AppInstanceId app_instance_id, AppLocation location,
|
|||||||
LOG_E(TAG, "[instance %lu] Failed to allocate app", app_instance_id);
|
LOG_E(TAG, "[instance %lu] Failed to allocate app", app_instance_id);
|
||||||
delete completion;
|
delete completion;
|
||||||
loader->unload(runtime);
|
loader->unload(runtime);
|
||||||
app_ledger_free_arguments(argc, argv);
|
app_arguments_free(argc, argv);
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +310,7 @@ error_t app_scheduler_start(AppInstanceId app_instance_id, AppLocation location,
|
|||||||
vSemaphoreDelete(completion->semaphore);
|
vSemaphoreDelete(completion->semaphore);
|
||||||
delete completion;
|
delete completion;
|
||||||
loader->unload(runtime);
|
loader->unload(runtime);
|
||||||
app_ledger_free_arguments(argc, argv);
|
app_arguments_free(argc, argv);
|
||||||
return ERROR_INVALID_ARGUMENT;
|
return ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +336,7 @@ error_t app_scheduler_start(AppInstanceId app_instance_id, AppLocation location,
|
|||||||
vSemaphoreDelete(completion->semaphore);
|
vSemaphoreDelete(completion->semaphore);
|
||||||
delete completion;
|
delete completion;
|
||||||
loader->unload(runtime);
|
loader->unload(runtime);
|
||||||
app_ledger_free_arguments(argc, argv);
|
app_arguments_free(argc, argv);
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +347,7 @@ error_t app_scheduler_start(AppInstanceId app_instance_id, AppLocation location,
|
|||||||
vSemaphoreDelete(completion->semaphore);
|
vSemaphoreDelete(completion->semaphore);
|
||||||
delete completion;
|
delete completion;
|
||||||
loader->unload(runtime);
|
loader->unload(runtime);
|
||||||
app_ledger_free_arguments(argc, argv);
|
app_arguments_free(argc, argv);
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@@ -374,7 +375,7 @@ error_t app_scheduler_start(AppInstanceId app_instance_id, AppLocation location,
|
|||||||
vSemaphoreDelete(completion->semaphore);
|
vSemaphoreDelete(completion->semaphore);
|
||||||
delete completion;
|
delete completion;
|
||||||
loader->unload(runtime);
|
loader->unload(runtime);
|
||||||
app_ledger_free_arguments(argc, argv);
|
app_arguments_free(argc, argv);
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +402,7 @@ error_t app_scheduler_start(AppInstanceId app_instance_id, AppLocation location,
|
|||||||
vSemaphoreDelete(completion->semaphore);
|
vSemaphoreDelete(completion->semaphore);
|
||||||
delete completion;
|
delete completion;
|
||||||
loader->unload(runtime);
|
loader->unload(runtime);
|
||||||
app_ledger_free_arguments(argc, argv);
|
app_arguments_free(argc, argv);
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
vTaskSuspend(task_handle);
|
vTaskSuspend(task_handle);
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#include <app/start.h>
|
||||||
|
|
||||||
|
#include <app/private/ledger.h>
|
||||||
|
#include <app/private/manager_internal.h>
|
||||||
|
|
||||||
|
#include <tactility/concurrent/mutex.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Looks @a id up in the manifest registry, then delegates to app_manager_start_internal(). The
|
||||||
|
// only path that requires a registered manifest; app_execute*() (app/execute.h) bypasses this
|
||||||
|
// entirely.
|
||||||
|
error_t start_internal_by_id(const char* id, AppInstanceId parent_instance_id, int argc, const char* const argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) {
|
||||||
|
auto& ledger = app_ledger();
|
||||||
|
|
||||||
|
mutex_lock(&ledger.mutex);
|
||||||
|
auto manifest_iterator = ledger.manifests.find(id);
|
||||||
|
if (manifest_iterator == ledger.manifests.end()) {
|
||||||
|
mutex_unlock(&ledger.mutex);
|
||||||
|
return ERROR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
const AppManifest* manifest = manifest_iterator->second;
|
||||||
|
mutex_unlock(&ledger.mutex);
|
||||||
|
|
||||||
|
return app_manager_start_internal(manifest, manifest->location, manifest->stack, parent_instance_id, argc, argv, bindings, binding_count, out_app_instance_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
error_t app_start(const char* id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id) {
|
||||||
|
return start_internal_by_id(id, 0, argc, argv, nullptr, 0, out_app_instance_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t app_start_for_result(const char* id, int argc, const char* const argv[], AppInstanceId parent_instance_id, AppInstanceId* out_app_instance_id) {
|
||||||
|
return start_internal_by_id(id, parent_instance_id, argc, argv, nullptr, 0, out_app_instance_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t app_start_with_streams(const char* id, const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) {
|
||||||
|
return start_internal_by_id(id, 0, 0, nullptr, bindings, binding_count, out_app_instance_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t app_start_for_result_with_streams(const char* id, int argc, const char* const argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId parent_instance_id, AppInstanceId* out_app_instance_id) {
|
||||||
|
return start_internal_by_id(id, parent_instance_id, argc, argv, bindings, binding_count, out_app_instance_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -12,7 +12,7 @@ if (NOT APPLE)
|
|||||||
target_sources(AppModuleTests PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../../../Tactility/Source/AppStdioWrap.cpp)
|
target_sources(AppModuleTests PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../../../Tactility/Source/AppStdioWrap.cpp)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
target_include_directories(AppModuleTests PRIVATE ${DOCTESTINC})
|
target_include_directories(AppModuleTests PRIVATE ${DOCTESTINC} ${CMAKE_CURRENT_LIST_DIR}/../private)
|
||||||
|
|
||||||
add_test(NAME AppModuleTests COMMAND AppModuleTests)
|
add_test(NAME AppModuleTests COMMAND AppModuleTests)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#include "doctest.h"
|
||||||
|
|
||||||
|
#include <app/private/arguments.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
TEST_CASE("app_arguments_copy returns NULL for argc <= 0") {
|
||||||
|
const char* const argv[] = { "a" };
|
||||||
|
CHECK_EQ(app_arguments_copy(0, argv), nullptr);
|
||||||
|
CHECK_EQ(app_arguments_copy(-1, argv), nullptr);
|
||||||
|
CHECK_EQ(app_arguments_copy(0, nullptr), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("app_arguments_copy deep-copies argv") {
|
||||||
|
const char* const argv[] = { "one", "two", "three" };
|
||||||
|
char** copy = app_arguments_copy(3, argv);
|
||||||
|
REQUIRE_NE(copy, nullptr);
|
||||||
|
|
||||||
|
CHECK_NE(static_cast<const void*>(copy[0]), static_cast<const void*>(argv[0]));
|
||||||
|
CHECK_EQ(std::strcmp(copy[0], "one"), 0);
|
||||||
|
CHECK_EQ(std::strcmp(copy[1], "two"), 0);
|
||||||
|
CHECK_EQ(std::strcmp(copy[2], "three"), 0);
|
||||||
|
CHECK_EQ(copy[3], nullptr);
|
||||||
|
|
||||||
|
app_arguments_free(3, copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("app_arguments_copy handles an empty string argument") {
|
||||||
|
const char* const argv[] = { "" };
|
||||||
|
char** copy = app_arguments_copy(1, argv);
|
||||||
|
REQUIRE_NE(copy, nullptr);
|
||||||
|
CHECK_EQ(std::strcmp(copy[0], ""), 0);
|
||||||
|
CHECK_EQ(copy[1], nullptr);
|
||||||
|
app_arguments_free(1, copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("app_arguments_free is a no-op for count 0 / null values") {
|
||||||
|
app_arguments_free(0, nullptr);
|
||||||
|
}
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#include "doctest.h"
|
||||||
|
|
||||||
|
#include <app/event.h>
|
||||||
|
#include <app/execute.h>
|
||||||
|
#include <app/io.h>
|
||||||
|
#include <app/loader.h>
|
||||||
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
|
#include <app/scheduler.h>
|
||||||
|
#include <app/stream.h>
|
||||||
|
|
||||||
|
#include <service/manager.h>
|
||||||
|
|
||||||
|
#include <tactility/delay.h>
|
||||||
|
#include <tactility/freertos/task.h>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
extern ServiceManifest app_internal_loader_service_manifest;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// See manager_test.cpp's own copy of this helper for why this checks the registry directly
|
||||||
|
// rather than a per-translation-unit static bool.
|
||||||
|
void ensure_memory_loader_registered() {
|
||||||
|
if (service_manager_find_instance(APP_LOADER_MEMORY_SERVICE_ID) == nullptr) {
|
||||||
|
service_manager_add(&app_internal_loader_service_manifest, /*auto_start=*/true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wait_for_state(uint32_t instance_id, AppInstanceState target, uint32_t timeout_ms) {
|
||||||
|
uint32_t waited = 0;
|
||||||
|
while (waited < timeout_ms) {
|
||||||
|
if (app_manager_get_state(instance_id) == target) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
delay_millis(10);
|
||||||
|
waited += 10;
|
||||||
|
}
|
||||||
|
return app_manager_get_state(instance_id) == target;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppInstanceId topmost_instance_id() {
|
||||||
|
AppInstanceId id = 0;
|
||||||
|
return app_manager_get_topmost_instance_id(&id) == ERROR_NONE ? id : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every APP_LOCATION_PATH-based test below would need its own fake path loader, registered
|
||||||
|
// under the same singleton APP_LOADER_PATH_SERVICE_ID that manager_test.cpp's own fake loader
|
||||||
|
// already claims - two independent-but-competing registrations in the same test binary is a real
|
||||||
|
// race (whichever file's static registrar runs first wins process-wide; the other file then runs
|
||||||
|
// silently against a loader it didn't write). Using APP_LOCATION_MEMORY + the real, already-safe
|
||||||
|
// app_internal_loader_service_manifest (registered via the find-instance-check idiom above, safe
|
||||||
|
// under this exact contention already used elsewhere in this test suite) sidesteps the problem
|
||||||
|
// entirely: no second competing registration exists.
|
||||||
|
|
||||||
|
// Subscribes until APP_EVENT_CLOSE like a real app instance; if launched with a single argv
|
||||||
|
// entry, returns it parsed as int immediately instead (the app_execute_for_result() shortcut,
|
||||||
|
// mirroring a modal dialog's result).
|
||||||
|
int32_t location_app_main(int argc, char* argv[]) {
|
||||||
|
if (argc == 1) {
|
||||||
|
return static_cast<int32_t>(strtol(argv[0], nullptr, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskEventGroup event_group {};
|
||||||
|
task_event_group_construct(&event_group);
|
||||||
|
|
||||||
|
AppEventSubscription sub {};
|
||||||
|
app_event_subscribe(&sub, &event_group);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (task_event_group_wait_any(&event_group, nullptr, pdMS_TO_TICKS(5000)) != ERROR_NONE) {
|
||||||
|
break; // safety net so a bug here can't hang the test suite
|
||||||
|
}
|
||||||
|
bool done = false;
|
||||||
|
AppEvent event {};
|
||||||
|
while (app_event_poll(&sub, &event) == ERROR_NONE) {
|
||||||
|
if (event.type == APP_EVENT_CLOSE) {
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app_event_unsubscribe(&sub);
|
||||||
|
task_event_group_destruct(&event_group);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes a fixed string to its own stdout then returns 7 as its result, for the
|
||||||
|
// app_execute_with_streams()/_for_result_with_streams() tests: proves a binding installed
|
||||||
|
// before the task starts actually reaches the app's own app_io_write() calls.
|
||||||
|
int32_t stream_writer_app_main(int, char*[]) {
|
||||||
|
const char message[] = "loc";
|
||||||
|
size_t sent = 0;
|
||||||
|
while (sent < sizeof(message) - 1) {
|
||||||
|
ssize_t written = app_io_write(STDOUT_FILENO, message + sent, sizeof(message) - 1 - sent);
|
||||||
|
if (written < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sent += static_cast<size_t>(written);
|
||||||
|
}
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes argv[0] to its own stdout, for the app_execute_with_streams() argv-delivery test.
|
||||||
|
int32_t argv_echo_app_main(int argc, char* argv[]) {
|
||||||
|
if (argc < 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const char* message = argv[0];
|
||||||
|
size_t length = strlen(message);
|
||||||
|
size_t sent = 0;
|
||||||
|
while (sent < length) {
|
||||||
|
ssize_t written = app_io_write(STDOUT_FILENO, message + sent, length - sent);
|
||||||
|
if (written < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sent += static_cast<size_t>(written);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("app_execute runs a location with no manifest at all, and reports no topmost app id for it") {
|
||||||
|
ensure_memory_loader_registered();
|
||||||
|
|
||||||
|
AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast<void*>(location_app_main) };
|
||||||
|
uint32_t instance_id = 0;
|
||||||
|
REQUIRE_EQ(app_execute(location, AppStackConfig {}, 0, nullptr, &instance_id), ERROR_NONE);
|
||||||
|
CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
|
CHECK_EQ(topmost_instance_id(), instance_id);
|
||||||
|
char buffer[64];
|
||||||
|
// No manifest to report an id from - same NOT_FOUND a caller already sees for "nothing active".
|
||||||
|
CHECK_EQ(app_manager_get_topmost_app_id(buffer, sizeof(buffer)), ERROR_NOT_FOUND);
|
||||||
|
|
||||||
|
CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE);
|
||||||
|
CHECK_EQ(app_manager_get_state(instance_id), APP_INSTANCE_STATE_STOPPED);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("app_execute doesn't disturb app_manager_get_topmost_app_id for a normal manifest-backed app started afterward") {
|
||||||
|
ensure_memory_loader_registered();
|
||||||
|
|
||||||
|
AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast<void*>(location_app_main) };
|
||||||
|
uint32_t unregistered_id = 0;
|
||||||
|
REQUIRE_EQ(app_execute(location, AppStackConfig {}, 0, nullptr, &unregistered_id), ERROR_NONE);
|
||||||
|
CHECK(wait_for_state(unregistered_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
|
AppManifest manifest { "test.app.execute.after", "After", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(location_app_main) } };
|
||||||
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
||||||
|
uint32_t registered_id = 0;
|
||||||
|
REQUIRE_EQ(app_start("test.app.execute.after", 0, nullptr, ®istered_id), ERROR_NONE);
|
||||||
|
CHECK(wait_for_state(registered_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
|
char buffer[64];
|
||||||
|
CHECK_EQ(app_manager_get_topmost_app_id(buffer, sizeof(buffer)), ERROR_NONE);
|
||||||
|
CHECK_EQ(std::string(buffer), "test.app.execute.after");
|
||||||
|
|
||||||
|
app_manager_stop(unregistered_id);
|
||||||
|
app_manager_stop(registered_id);
|
||||||
|
app_manager_remove("test.app.execute.after");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("app_execute_for_result delivers APP_EVENT_RESULT to the parent, with no manifest for the child either") {
|
||||||
|
ensure_memory_loader_registered();
|
||||||
|
|
||||||
|
AppManifest parent_manifest { "test.app.execute.parent", "Parent", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(location_app_main) } };
|
||||||
|
REQUIRE_EQ(app_manager_add(&parent_manifest), ERROR_NONE);
|
||||||
|
|
||||||
|
uint32_t parent_id = 0;
|
||||||
|
REQUIRE_EQ(app_start("test.app.execute.parent", 0, nullptr, &parent_id), ERROR_NONE);
|
||||||
|
CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
|
TaskEventGroup parent_event_group {};
|
||||||
|
task_event_group_construct(&parent_event_group);
|
||||||
|
|
||||||
|
AppEventSubscription parent_sub {};
|
||||||
|
REQUIRE_EQ(app_event_subscribe_with_app_id(&parent_sub, &parent_event_group, parent_id), ERROR_NONE);
|
||||||
|
|
||||||
|
AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast<void*>(location_app_main) };
|
||||||
|
const char* argv[] = { "42" }; // location_app_main's single-arg shortcut - returns 42 immediately
|
||||||
|
uint32_t child_id = 0;
|
||||||
|
REQUIRE_EQ(app_execute_for_result(location, AppStackConfig {}, 1, argv, parent_id, &child_id), ERROR_NONE);
|
||||||
|
|
||||||
|
REQUIRE_EQ(task_event_group_wait(&parent_event_group, parent_sub.bit, false, nullptr, pdMS_TO_TICKS(2000)), ERROR_NONE);
|
||||||
|
AppEvent event {};
|
||||||
|
REQUIRE_EQ(app_event_poll(&parent_sub, &event), ERROR_NONE);
|
||||||
|
CHECK_EQ(event.type, APP_EVENT_RESULT);
|
||||||
|
CHECK_EQ(event.result.launch_id, child_id);
|
||||||
|
CHECK_EQ(event.result.result, 42);
|
||||||
|
|
||||||
|
app_event_unsubscribe(&parent_sub);
|
||||||
|
task_event_group_destruct(&parent_event_group);
|
||||||
|
app_manager_stop(child_id);
|
||||||
|
app_manager_stop(parent_id);
|
||||||
|
app_manager_remove("test.app.execute.parent");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("app_execute_with_streams pipes a manifest-less child's app_io_write() calls into a parent-owned AppStream") {
|
||||||
|
ensure_memory_loader_registered();
|
||||||
|
|
||||||
|
AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast<void*>(stream_writer_app_main) };
|
||||||
|
|
||||||
|
TaskEventGroup event_group {};
|
||||||
|
task_event_group_construct(&event_group);
|
||||||
|
|
||||||
|
uint8_t storage[64];
|
||||||
|
AppStream child_stdout {};
|
||||||
|
AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group };
|
||||||
|
|
||||||
|
AppInstanceId child_id = 0;
|
||||||
|
REQUIRE_EQ(app_execute_with_streams(location, AppStackConfig {}, 0, nullptr, &binding, 1, &child_id), ERROR_NONE);
|
||||||
|
|
||||||
|
std::vector<uint8_t> received;
|
||||||
|
while (app_stream_await(&child_stdout, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(1000)) == ERROR_NONE) {
|
||||||
|
uint8_t chunk[16];
|
||||||
|
size_t n = app_stream_read(&child_stdout, chunk, sizeof(chunk));
|
||||||
|
if (n == 0) {
|
||||||
|
break; // EOF
|
||||||
|
}
|
||||||
|
received.insert(received.end(), chunk, chunk + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE_EQ(received.size(), 3u);
|
||||||
|
CHECK_EQ(std::memcmp(received.data(), "loc", 3), 0);
|
||||||
|
|
||||||
|
REQUIRE(wait_for_state(child_id, APP_INSTANCE_STATE_STOPPED, 1000));
|
||||||
|
app_stream_unsubscribe(&child_stdout);
|
||||||
|
task_event_group_destruct(&event_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("app_execute_with_streams passes argv through to the started app") {
|
||||||
|
ensure_memory_loader_registered();
|
||||||
|
|
||||||
|
AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast<void*>(argv_echo_app_main) };
|
||||||
|
|
||||||
|
TaskEventGroup event_group {};
|
||||||
|
task_event_group_construct(&event_group);
|
||||||
|
|
||||||
|
uint8_t storage[64];
|
||||||
|
AppStream child_stdout {};
|
||||||
|
AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group };
|
||||||
|
|
||||||
|
const char* argv[] = { "hello" };
|
||||||
|
AppInstanceId child_id = 0;
|
||||||
|
REQUIRE_EQ(app_execute_with_streams(location, AppStackConfig {}, 1, argv, &binding, 1, &child_id), ERROR_NONE);
|
||||||
|
|
||||||
|
std::vector<uint8_t> received;
|
||||||
|
while (app_stream_await(&child_stdout, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(1000)) == ERROR_NONE) {
|
||||||
|
uint8_t chunk[16];
|
||||||
|
size_t n = app_stream_read(&child_stdout, chunk, sizeof(chunk));
|
||||||
|
if (n == 0) {
|
||||||
|
break; // EOF
|
||||||
|
}
|
||||||
|
received.insert(received.end(), chunk, chunk + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE_EQ(received.size(), 5u);
|
||||||
|
CHECK_EQ(std::memcmp(received.data(), "hello", 5), 0);
|
||||||
|
|
||||||
|
REQUIRE(wait_for_state(child_id, APP_INSTANCE_STATE_STOPPED, 1000));
|
||||||
|
app_stream_unsubscribe(&child_stdout);
|
||||||
|
task_event_group_destruct(&event_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("app_execute_for_result_with_streams delivers both the stream data and the APP_EVENT_RESULT") {
|
||||||
|
ensure_memory_loader_registered();
|
||||||
|
|
||||||
|
AppManifest parent_manifest { "test.app.execute.parent_streams", "Parent", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(location_app_main) } };
|
||||||
|
REQUIRE_EQ(app_manager_add(&parent_manifest), ERROR_NONE);
|
||||||
|
|
||||||
|
uint32_t parent_id = 0;
|
||||||
|
REQUIRE_EQ(app_start("test.app.execute.parent_streams", 0, nullptr, &parent_id), ERROR_NONE);
|
||||||
|
CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
|
TaskEventGroup parent_event_group {};
|
||||||
|
task_event_group_construct(&parent_event_group);
|
||||||
|
AppEventSubscription parent_sub {};
|
||||||
|
REQUIRE_EQ(app_event_subscribe_with_app_id(&parent_sub, &parent_event_group, parent_id), ERROR_NONE);
|
||||||
|
|
||||||
|
uint8_t storage[64];
|
||||||
|
AppStream child_stdout {};
|
||||||
|
AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &parent_event_group };
|
||||||
|
|
||||||
|
AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast<void*>(stream_writer_app_main) };
|
||||||
|
uint32_t child_id = 0;
|
||||||
|
REQUIRE_EQ(app_execute_for_result_with_streams(location, AppStackConfig {}, 0, nullptr, &binding, 1, parent_id, &child_id), ERROR_NONE);
|
||||||
|
|
||||||
|
std::vector<uint8_t> received;
|
||||||
|
while (app_stream_await(&child_stdout, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(1000)) == ERROR_NONE) {
|
||||||
|
uint8_t chunk[16];
|
||||||
|
size_t n = app_stream_read(&child_stdout, chunk, sizeof(chunk));
|
||||||
|
if (n == 0) {
|
||||||
|
break; // EOF
|
||||||
|
}
|
||||||
|
received.insert(received.end(), chunk, chunk + n);
|
||||||
|
}
|
||||||
|
REQUIRE_EQ(received.size(), 3u);
|
||||||
|
CHECK_EQ(std::memcmp(received.data(), "loc", 3), 0);
|
||||||
|
|
||||||
|
REQUIRE_EQ(task_event_group_wait(&parent_event_group, parent_sub.bit, false, nullptr, pdMS_TO_TICKS(2000)), ERROR_NONE);
|
||||||
|
AppEvent event {};
|
||||||
|
REQUIRE_EQ(app_event_poll(&parent_sub, &event), ERROR_NONE);
|
||||||
|
CHECK_EQ(event.type, APP_EVENT_RESULT);
|
||||||
|
CHECK_EQ(event.result.launch_id, child_id);
|
||||||
|
CHECK_EQ(event.result.result, 7);
|
||||||
|
|
||||||
|
app_stream_unsubscribe(&child_stdout);
|
||||||
|
app_event_unsubscribe(&parent_sub);
|
||||||
|
task_event_group_destruct(&parent_event_group);
|
||||||
|
app_manager_stop(child_id);
|
||||||
|
app_manager_stop(parent_id);
|
||||||
|
app_manager_remove("test.app.execute.parent_streams");
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <app/io.h>
|
#include <app/io.h>
|
||||||
#include <app/loader.h>
|
#include <app/loader.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
#include <app/stream.h>
|
#include <app/stream.h>
|
||||||
|
|
||||||
@@ -135,7 +136,7 @@ TEST_CASE("an app's stdio fds default to the null device: write succeeds and dis
|
|||||||
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
||||||
|
|
||||||
AppInstanceId instance_id = 0;
|
AppInstanceId instance_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.io.unbound", &instance_id), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.io.unbound", 0, nullptr, &instance_id), ERROR_NONE);
|
||||||
REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_STOPPED, 1000));
|
REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_STOPPED, 1000));
|
||||||
|
|
||||||
CHECK_EQ(g_stdio_write_result.load(std::memory_order_acquire), 1);
|
CHECK_EQ(g_stdio_write_result.load(std::memory_order_acquire), 1);
|
||||||
@@ -144,7 +145,7 @@ TEST_CASE("an app's stdio fds default to the null device: write succeeds and dis
|
|||||||
app_manager_remove("test.io.unbound");
|
app_manager_remove("test.io.unbound");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("app_manager_start_with_streams pipes a child's app_io_write() calls into a parent-owned AppStream, EOF at exit") {
|
TEST_CASE("app_start_with_streams pipes a child's app_io_write() calls into a parent-owned AppStream, EOF at exit") {
|
||||||
ensure_memory_loader_registered();
|
ensure_memory_loader_registered();
|
||||||
|
|
||||||
AppManifest manifest { "test.io.writer", "Writer", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(stdout_writer_app_main) } };
|
AppManifest manifest { "test.io.writer", "Writer", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(stdout_writer_app_main) } };
|
||||||
@@ -158,7 +159,7 @@ TEST_CASE("app_manager_start_with_streams pipes a child's app_io_write() calls i
|
|||||||
|
|
||||||
AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group };
|
AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group };
|
||||||
AppInstanceId child_id = 0;
|
AppInstanceId child_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start_with_streams("test.io.writer", &binding, 1, &child_id), ERROR_NONE);
|
REQUIRE_EQ(app_start_with_streams("test.io.writer", &binding, 1, &child_id), ERROR_NONE);
|
||||||
|
|
||||||
std::vector<uint8_t> received;
|
std::vector<uint8_t> received;
|
||||||
while (app_stream_await(&child_stdout, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(1000)) == ERROR_NONE) {
|
while (app_stream_await(&child_stdout, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(1000)) == ERROR_NONE) {
|
||||||
@@ -195,7 +196,7 @@ TEST_CASE("a write blocked on a full stream wakes with an error once the consume
|
|||||||
|
|
||||||
AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group };
|
AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group };
|
||||||
AppInstanceId child_id = 0;
|
AppInstanceId child_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start_with_streams("test.io.blocked", &binding, 1, &child_id), ERROR_NONE);
|
REQUIRE_EQ(app_start_with_streams("test.io.blocked", &binding, 1, &child_id), ERROR_NONE);
|
||||||
|
|
||||||
// Never drained: the child fills the 4-byte buffer and blocks awaiting space for the rest.
|
// Never drained: the child fills the 4-byte buffer and blocks awaiting space for the rest.
|
||||||
delay_millis(200);
|
delay_millis(200);
|
||||||
@@ -231,7 +232,7 @@ TEST_CASE("app_stream_unsubscribe is safe to call while a write is actively bloc
|
|||||||
|
|
||||||
AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group };
|
AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group };
|
||||||
AppInstanceId child_id = 0;
|
AppInstanceId child_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start_with_streams("test.io.unsub_race", &binding, 1, &child_id), ERROR_NONE);
|
REQUIRE_EQ(app_start_with_streams("test.io.unsub_race", &binding, 1, &child_id), ERROR_NONE);
|
||||||
|
|
||||||
// Give the child time to fill the 4-byte buffer and block inside app_io_write(), already
|
// Give the child time to fill the 4-byte buffer and block inside app_io_write(), already
|
||||||
// dispatched through app_fd_table_get_and_retain() and currently waiting in
|
// dispatched through app_fd_table_get_and_retain() and currently waiting in
|
||||||
@@ -262,7 +263,7 @@ TEST_CASE("app_io_read/write/close pass through a real file fd app-module never
|
|||||||
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
||||||
|
|
||||||
AppInstanceId instance_id = 0;
|
AppInstanceId instance_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.io.real_file", &instance_id), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.io.real_file", 0, nullptr, &instance_id), ERROR_NONE);
|
||||||
REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_STOPPED, 1000));
|
REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_STOPPED, 1000));
|
||||||
|
|
||||||
CHECK_EQ(g_real_file_write_result.load(std::memory_order_acquire), 2);
|
CHECK_EQ(g_real_file_write_result.load(std::memory_order_acquire), 2);
|
||||||
@@ -283,7 +284,7 @@ TEST_CASE("closing an already-closed app fd reports EBADF instead of falling thr
|
|||||||
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
||||||
|
|
||||||
AppInstanceId instance_id = 0;
|
AppInstanceId instance_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.io.double_close", &instance_id), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.io.double_close", 0, nullptr, &instance_id), ERROR_NONE);
|
||||||
REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_STOPPED, 1000));
|
REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_STOPPED, 1000));
|
||||||
|
|
||||||
CHECK_EQ(g_double_close_first_result.load(std::memory_order_acquire), 0);
|
CHECK_EQ(g_double_close_first_result.load(std::memory_order_acquire), 0);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/loader.h>
|
#include <app/loader.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
#include <service/manager.h>
|
#include <service/manager.h>
|
||||||
@@ -53,7 +54,7 @@ void stash_received_arguments(int argc, char* argv[]) {
|
|||||||
// A minimal stand-in for a real app's main(): subscribes to its own app_event stream and exits
|
// A minimal stand-in for a real app's main(): subscribes to its own app_event stream and exits
|
||||||
// as soon as it's asked to close - exactly the contract every app instance (with its own
|
// as soon as it's asked to close - exactly the contract every app instance (with its own
|
||||||
// dedicated task for its whole lifetime) is expected to follow. If launched with a single
|
// dedicated task for its whole lifetime) is expected to follow. If launched with a single
|
||||||
// parameter (app_manager_start_for_result()), acts as a modal dialog instead: returns the
|
// parameter (app_start_for_result()), acts as a modal dialog instead: returns the
|
||||||
// requested result (argv[0], parsed as an int) immediately (the app's own return value IS the
|
// requested result (argv[0], parsed as an int) immediately (the app's own return value IS the
|
||||||
// delivered APP_EVENT_RESULT.result - see app_scheduler.cpp's thread_main()).
|
// delivered APP_EVENT_RESULT.result - see app_scheduler.cpp's thread_main()).
|
||||||
int32_t fake_run(void*, uint32_t /*app_instance_id*/, int argc, char* argv[]) {
|
int32_t fake_run(void*, uint32_t /*app_instance_id*/, int argc, char* argv[]) {
|
||||||
@@ -118,11 +119,13 @@ ServiceManifest fake_loader_manifest = {
|
|||||||
.on_stop = nullptr,
|
.on_stop = nullptr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Checks the registry directly rather than a per-translation-unit static bool, matching
|
||||||
|
// ensure_memory_loader_registered() below - this is the only file that registers a fake path
|
||||||
|
// loader, but a second one would silently win the race otherwise (see execute_test.cpp, which
|
||||||
|
// deliberately avoids needing one at all for exactly this reason).
|
||||||
void ensure_fake_loader_registered() {
|
void ensure_fake_loader_registered() {
|
||||||
static bool registered = false;
|
if (service_manager_find_instance(APP_LOADER_PATH_SERVICE_ID) == nullptr) {
|
||||||
if (!registered) {
|
service_manager_add(&fake_loader_manifest, /*auto_start=*/true);
|
||||||
CHECK_EQ(service_manager_add(&fake_loader_manifest, /*auto_start=*/true), ERROR_NONE);
|
|
||||||
registered = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,14 +178,14 @@ bool wait_for_arguments_stashed(uint32_t timeout_ms) {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("app_manager_start activates an app instance, app_manager_stop terminates it") {
|
TEST_CASE("app_start activates an app instance, app_manager_stop terminates it") {
|
||||||
ensure_fake_loader_registered();
|
ensure_fake_loader_registered();
|
||||||
|
|
||||||
AppManifest manifest { "test.app.a", "Test App A", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
AppManifest manifest { "test.app.a", "Test App A", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
||||||
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
||||||
|
|
||||||
uint32_t instance_id = 0;
|
uint32_t instance_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.a", &instance_id), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.a", 0, nullptr, &instance_id), ERROR_NONE);
|
||||||
CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE);
|
CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE);
|
||||||
@@ -191,7 +194,7 @@ TEST_CASE("app_manager_start activates an app instance, app_manager_stop termina
|
|||||||
app_manager_remove("test.app.a");
|
app_manager_remove("test.app.a");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("app_manager_start never touches another already-running app - every instance gets its own task") {
|
TEST_CASE("app_start never touches another already-running app - every instance gets its own task") {
|
||||||
ensure_fake_loader_registered();
|
ensure_fake_loader_registered();
|
||||||
|
|
||||||
AppManifest manifest_b { "test.app.b", "Test App B", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
AppManifest manifest_b { "test.app.b", "Test App B", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
||||||
@@ -200,11 +203,11 @@ TEST_CASE("app_manager_start never touches another already-running app - every i
|
|||||||
REQUIRE_EQ(app_manager_add(&manifest_c), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&manifest_c), ERROR_NONE);
|
||||||
|
|
||||||
uint32_t id_b = 0;
|
uint32_t id_b = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.b", &id_b), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.b", 0, nullptr, &id_b), ERROR_NONE);
|
||||||
CHECK(wait_for_state(id_b, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(id_b, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
uint32_t id_c = 0;
|
uint32_t id_c = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.c", &id_c), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.c", 0, nullptr, &id_c), ERROR_NONE);
|
||||||
CHECK(wait_for_state(id_c, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(id_c, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
// b is untouched by c starting - both stay Active at once, each with its own task.
|
// b is untouched by c starting - both stay Active at once, each with its own task.
|
||||||
@@ -216,18 +219,18 @@ TEST_CASE("app_manager_start never touches another already-running app - every i
|
|||||||
app_manager_remove("test.app.c");
|
app_manager_remove("test.app.c");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("app_manager_start always creates a fresh instance, even for the same manifest id twice") {
|
TEST_CASE("app_start always creates a fresh instance, even for the same manifest id twice") {
|
||||||
ensure_fake_loader_registered();
|
ensure_fake_loader_registered();
|
||||||
|
|
||||||
AppManifest manifest { "test.app.twice", "Test App Twice", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
AppManifest manifest { "test.app.twice", "Test App Twice", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
||||||
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
||||||
|
|
||||||
uint32_t id_first = 0;
|
uint32_t id_first = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.twice", &id_first), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.twice", 0, nullptr, &id_first), ERROR_NONE);
|
||||||
CHECK(wait_for_state(id_first, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(id_first, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
uint32_t id_second = 0;
|
uint32_t id_second = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.twice", &id_second), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.twice", 0, nullptr, &id_second), ERROR_NONE);
|
||||||
CHECK(wait_for_state(id_second, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(id_second, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
CHECK_NE(id_first, id_second);
|
CHECK_NE(id_first, id_second);
|
||||||
@@ -242,7 +245,7 @@ TEST_CASE("app_manager_get_state returns STOPPED for an unknown instance id") {
|
|||||||
CHECK_EQ(app_manager_get_state(999999), APP_INSTANCE_STATE_STOPPED);
|
CHECK_EQ(app_manager_get_state(999999), APP_INSTANCE_STATE_STOPPED);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("app_manager_start_with_parameters deep-copies argv before the app instance receives it") {
|
TEST_CASE("app_start_with_parameters deep-copies argv before the app instance receives it") {
|
||||||
ensure_fake_loader_registered();
|
ensure_fake_loader_registered();
|
||||||
|
|
||||||
AppManifest manifest { "test.app.args", "Test App Args", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
AppManifest manifest { "test.app.args", "Test App Args", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
||||||
@@ -256,7 +259,7 @@ TEST_CASE("app_manager_start_with_parameters deep-copies argv before the app ins
|
|||||||
std::string ssid = "MyNetwork";
|
std::string ssid = "MyNetwork";
|
||||||
std::string password = "hunter2";
|
std::string password = "hunter2";
|
||||||
const char* argv[] = { ssid.c_str(), password.c_str() };
|
const char* argv[] = { ssid.c_str(), password.c_str() };
|
||||||
REQUIRE_EQ(app_manager_start_with_parameters("test.app.args", 2, argv, &instance_id), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.args", 2, argv, &instance_id), ERROR_NONE);
|
||||||
}
|
}
|
||||||
CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
REQUIRE(wait_for_arguments_stashed(1000));
|
REQUIRE(wait_for_arguments_stashed(1000));
|
||||||
@@ -301,12 +304,12 @@ TEST_CASE("app_manager_for_each_manifest visits every registered manifest, inclu
|
|||||||
CHECK(std::ranges::find(seen_ids, "test.app.foreach.x") == seen_ids.end());
|
CHECK(std::ranges::find(seen_ids, "test.app.foreach.x") == seen_ids.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("app_manager_start fails for an unregistered manifest id") {
|
TEST_CASE("app_start fails for an unregistered manifest id") {
|
||||||
uint32_t instance_id = 0;
|
uint32_t instance_id = 0;
|
||||||
CHECK_EQ(app_manager_start("test.app.nonexistent", &instance_id), ERROR_NOT_FOUND);
|
CHECK_EQ(app_start("test.app.nonexistent", 0, nullptr, &instance_id), ERROR_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("app_manager_start runs an APP_LOCATION_MEMORY app via its function pointer, through the real internal loader") {
|
TEST_CASE("app_start runs an APP_LOCATION_MEMORY app via its function pointer, through the real internal loader") {
|
||||||
ensure_memory_loader_registered();
|
ensure_memory_loader_registered();
|
||||||
|
|
||||||
AppManifest manifest {
|
AppManifest manifest {
|
||||||
@@ -318,7 +321,7 @@ TEST_CASE("app_manager_start runs an APP_LOCATION_MEMORY app via its function po
|
|||||||
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
||||||
|
|
||||||
uint32_t instance_id = 0;
|
uint32_t instance_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.memory", &instance_id), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.memory", 0, nullptr, &instance_id), ERROR_NONE);
|
||||||
CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE);
|
CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE);
|
||||||
@@ -327,7 +330,7 @@ TEST_CASE("app_manager_start runs an APP_LOCATION_MEMORY app via its function po
|
|||||||
app_manager_remove("test.app.memory");
|
app_manager_remove("test.app.memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("app_manager_start_for_result delivers APP_EVENT_RESULT to the parent, which stays Active throughout") {
|
TEST_CASE("app_start_for_result delivers APP_EVENT_RESULT to the parent, which stays Active throughout") {
|
||||||
ensure_fake_loader_registered();
|
ensure_fake_loader_registered();
|
||||||
|
|
||||||
AppManifest parent_manifest { "test.app.parent", "Parent", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
AppManifest parent_manifest { "test.app.parent", "Parent", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
||||||
@@ -336,7 +339,7 @@ TEST_CASE("app_manager_start_for_result delivers APP_EVENT_RESULT to the parent,
|
|||||||
REQUIRE_EQ(app_manager_add(&child_manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&child_manifest), ERROR_NONE);
|
||||||
|
|
||||||
uint32_t parent_id = 0;
|
uint32_t parent_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.parent", &parent_id), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.parent", 0, nullptr, &parent_id), ERROR_NONE);
|
||||||
CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
TaskEventGroup parent_event_group {};
|
TaskEventGroup parent_event_group {};
|
||||||
@@ -347,7 +350,7 @@ TEST_CASE("app_manager_start_for_result delivers APP_EVENT_RESULT to the parent,
|
|||||||
|
|
||||||
const char* argv[] = { "42" };
|
const char* argv[] = { "42" };
|
||||||
uint32_t child_id = 0;
|
uint32_t child_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start_for_result("test.app.child", parent_id, 1, argv, &child_id), ERROR_NONE);
|
REQUIRE_EQ(app_start_for_result("test.app.child", 1, argv, parent_id, &child_id), ERROR_NONE);
|
||||||
|
|
||||||
// Launching a modal child never touches the parent's own task/state.
|
// Launching a modal child never touches the parent's own task/state.
|
||||||
CHECK_EQ(app_manager_get_state(parent_id), APP_INSTANCE_STATE_ACTIVE);
|
CHECK_EQ(app_manager_get_state(parent_id), APP_INSTANCE_STATE_ACTIVE);
|
||||||
@@ -367,7 +370,7 @@ TEST_CASE("app_manager_start_for_result delivers APP_EVENT_RESULT to the parent,
|
|||||||
app_manager_remove("test.app.child");
|
app_manager_remove("test.app.child");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("app_manager_start_for_result delivers the child's own return value as the result") {
|
TEST_CASE("app_start_for_result delivers the child's own return value as the result") {
|
||||||
ensure_fake_loader_registered();
|
ensure_fake_loader_registered();
|
||||||
|
|
||||||
AppManifest parent_manifest { "test.app.parent2", "Parent2", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
AppManifest parent_manifest { "test.app.parent2", "Parent2", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
||||||
@@ -376,7 +379,7 @@ TEST_CASE("app_manager_start_for_result delivers the child's own return value as
|
|||||||
REQUIRE_EQ(app_manager_add(&child_manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&child_manifest), ERROR_NONE);
|
||||||
|
|
||||||
uint32_t parent_id = 0;
|
uint32_t parent_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.parent2", &parent_id), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.parent2", 0, nullptr, &parent_id), ERROR_NONE);
|
||||||
CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
TaskEventGroup parent_event_group {};
|
TaskEventGroup parent_event_group {};
|
||||||
@@ -388,7 +391,7 @@ TEST_CASE("app_manager_start_for_result delivers the child's own return value as
|
|||||||
uint32_t child_id = 0;
|
uint32_t child_id = 0;
|
||||||
// No parameters - fake_run falls through to its normal CLOSE loop instead of acting as a
|
// No parameters - fake_run falls through to its normal CLOSE loop instead of acting as a
|
||||||
// dialog.
|
// dialog.
|
||||||
REQUIRE_EQ(app_manager_start_for_result("test.app.child2", parent_id, 0, nullptr, &child_id), ERROR_NONE);
|
REQUIRE_EQ(app_start_for_result("test.app.child2", 0, nullptr, parent_id, &child_id), ERROR_NONE);
|
||||||
CHECK(wait_for_state(child_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(child_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
app_manager_stop(child_id); // force-close
|
app_manager_stop(child_id); // force-close
|
||||||
@@ -418,14 +421,14 @@ TEST_CASE("app_manager_get_topmost_instance_id returns NOT_FOUND when nothing is
|
|||||||
REQUIRE_EQ(app_manager_add(&manifest_b), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&manifest_b), ERROR_NONE);
|
||||||
|
|
||||||
uint32_t id_a = 0;
|
uint32_t id_a = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.top_a", &id_a), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.top_a", 0, nullptr, &id_a), ERROR_NONE);
|
||||||
CHECK(wait_for_state(id_a, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(id_a, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
CHECK_EQ(topmost_instance_id(), id_a);
|
CHECK_EQ(topmost_instance_id(), id_a);
|
||||||
|
|
||||||
// a stays Active - b just has a higher (more recently allocated) instance id, so it becomes
|
// a stays Active - b just has a higher (more recently allocated) instance id, so it becomes
|
||||||
// topmost without a superseding/saving.
|
// topmost without a superseding/saving.
|
||||||
uint32_t id_b = 0;
|
uint32_t id_b = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.top_b", &id_b), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.top_b", 0, nullptr, &id_b), ERROR_NONE);
|
||||||
CHECK(wait_for_state(id_b, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(id_b, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
CHECK_EQ(topmost_instance_id(), id_b);
|
CHECK_EQ(topmost_instance_id(), id_b);
|
||||||
|
|
||||||
@@ -438,7 +441,7 @@ TEST_CASE("app_manager_get_topmost_instance_id returns NOT_FOUND when nothing is
|
|||||||
// its persistent CLOSE loop branch instead of instantly resolving like a real dialog would -
|
// its persistent CLOSE loop branch instead of instantly resolving like a real dialog would -
|
||||||
// needed here so there's a reliable window to observe it as topmost.
|
// needed here so there's a reliable window to observe it as topmost.
|
||||||
uint32_t id_c = 0;
|
uint32_t id_c = 0;
|
||||||
REQUIRE_EQ(app_manager_start_for_result("test.app.top_a", id_b, 0, nullptr, &id_c), ERROR_NONE);
|
REQUIRE_EQ(app_start_for_result("test.app.top_a", 0, nullptr, id_b, &id_c), ERROR_NONE);
|
||||||
CHECK(wait_for_state(id_c, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(id_c, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
CHECK_EQ(topmost_instance_id(), id_c);
|
CHECK_EQ(topmost_instance_id(), id_c);
|
||||||
|
|
||||||
@@ -451,7 +454,7 @@ TEST_CASE("app_manager_get_topmost_instance_id returns NOT_FOUND when nothing is
|
|||||||
app_manager_remove("test.app.top_b");
|
app_manager_remove("test.app.top_b");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("app_manager_start honors a custom AppManifest::stack.depth") {
|
TEST_CASE("app_start honors a custom AppManifest::stack.depth") {
|
||||||
ensure_fake_loader_registered();
|
ensure_fake_loader_registered();
|
||||||
|
|
||||||
AppManifest manifest { "test.app.stack.custom", "Stack Custom", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
AppManifest manifest { "test.app.stack.custom", "Stack Custom", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } };
|
||||||
@@ -459,7 +462,7 @@ TEST_CASE("app_manager_start honors a custom AppManifest::stack.depth") {
|
|||||||
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
||||||
|
|
||||||
uint32_t instance_id = 0;
|
uint32_t instance_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.stack.custom", &instance_id), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.stack.custom", 0, nullptr, &instance_id), ERROR_NONE);
|
||||||
CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE);
|
CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE);
|
||||||
@@ -468,7 +471,7 @@ TEST_CASE("app_manager_start honors a custom AppManifest::stack.depth") {
|
|||||||
app_manager_remove("test.app.stack.custom");
|
app_manager_remove("test.app.stack.custom");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("app_manager_start still works when AppManifest::stack is left at its zero-value default") {
|
TEST_CASE("app_start still works when AppManifest::stack is left at its zero-value default") {
|
||||||
ensure_fake_loader_registered();
|
ensure_fake_loader_registered();
|
||||||
|
|
||||||
// stack.depth == 0 - app_scheduler_start() must fall back to its own default stack depth
|
// stack.depth == 0 - app_scheduler_start() must fall back to its own default stack depth
|
||||||
@@ -478,7 +481,7 @@ TEST_CASE("app_manager_start still works when AppManifest::stack is left at its
|
|||||||
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
||||||
|
|
||||||
uint32_t instance_id = 0;
|
uint32_t instance_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.stack.default", &instance_id), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.stack.default", 0, nullptr, &instance_id), ERROR_NONE);
|
||||||
CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE);
|
CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE);
|
||||||
@@ -498,7 +501,7 @@ TEST_CASE("app_manager_get_topmost_app_id returns BUFFER_OVERFLOW for a too-smal
|
|||||||
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
||||||
|
|
||||||
uint32_t id = 0;
|
uint32_t id = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.app.top_overflow", &id), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.app.top_overflow", 0, nullptr, &id), ERROR_NONE);
|
||||||
CHECK(wait_for_state(id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
CHECK(wait_for_state(id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
|
|
||||||
// "test.app.top_overflow" doesn't fit in a 4-byte buffer.
|
// "test.app.top_overflow" doesn't fit in a 4-byte buffer.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/loader.h>
|
#include <app/loader.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
#include <app/stream.h>
|
#include <app/stream.h>
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@ AppInstanceId start_idle_app(const char* id) {
|
|||||||
AppManifest manifest { id, id, APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(idle_app_main) } };
|
AppManifest manifest { id, id, APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(idle_app_main) } };
|
||||||
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
||||||
AppInstanceId instance_id = 0;
|
AppInstanceId instance_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start(id, &instance_id), ERROR_NONE);
|
REQUIRE_EQ(app_start(id, 0, nullptr, &instance_id), ERROR_NONE);
|
||||||
REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
|
||||||
return instance_id;
|
return instance_id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#include <app/elf_check.h>
|
||||||
#include <app/loader.h>
|
#include <app/loader.h>
|
||||||
#include <app/location.h>
|
#include <app/location.h>
|
||||||
|
|
||||||
@@ -27,6 +28,36 @@ bool is_regular_file(const std::string& path) {
|
|||||||
return ::stat(path.c_str(), &path_stat) == 0 && S_ISREG(path_stat.st_mode);
|
return ::stat(path.c_str(), &path_stat) == 0 && S_ISREG(path_stat.st_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef __APPLE__
|
||||||
|
constexpr ElfRequirements EXECUTABLE_REQUIREMENTS = {
|
||||||
|
.elf_class = ELF_CLASS_64,
|
||||||
|
.data = ELF_DATA_2LSB,
|
||||||
|
.type = ELF_TYPE_DYN,
|
||||||
|
#if defined(__x86_64__)
|
||||||
|
.machine = ELF_MACHINE_X86_64,
|
||||||
|
#elif defined(__aarch64__)
|
||||||
|
.machine = ELF_MACHINE_AARCH64,
|
||||||
|
#else
|
||||||
|
#error "Unsupported POSIX architecture for ELF machine check"
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Validates an already-resolved binary path (see resolve_app_path()) before it's handed to
|
||||||
|
// dlopen(): the extension check is a cheap string comparison, so the file is only opened as a
|
||||||
|
// last resort.
|
||||||
|
bool is_executable_file(const std::string& resolved_path) {
|
||||||
|
if (!resolved_path.ends_with(".so")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#ifdef __APPLE__
|
||||||
|
// The simulator's app binaries are Mach-O on macOS, not ELF, so there is no header to check.
|
||||||
|
return is_regular_file(resolved_path);
|
||||||
|
#else
|
||||||
|
return elf_check_file(resolved_path.c_str(), &EXECUTABLE_REQUIREMENTS);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// location.location can be either an app's install directory or the .so file directly; the
|
// location.location can be either an app's install directory or the .so file directly; the
|
||||||
// former resolves to the per-architecture binary at {dir}/elf/posix-{TACTILITY_POSIX_ARCH}.so,
|
// former resolves to the per-architecture binary at {dir}/elf/posix-{TACTILITY_POSIX_ARCH}.so,
|
||||||
// mirroring app_esp32_loader_service.cpp's resolve_elf_path().
|
// mirroring app_esp32_loader_service.cpp's resolve_elf_path().
|
||||||
@@ -56,6 +87,11 @@ error_t api_load(AppLocation location, AppRuntime* out_runtime) {
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!is_executable_file(app_path)) {
|
||||||
|
LOG_E(TAG, "Not executable: %s", app_path.c_str());
|
||||||
|
return ERROR_NOT_ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
LOG_I(TAG, "Loading %s", app_path.c_str());
|
LOG_I(TAG, "Loading %s", app_path.c_str());
|
||||||
|
|
||||||
// RTLD_NOW: a missing symbol fails here, not mid-run(). RTLD_LOCAL: this app's own exported
|
// RTLD_NOW: a missing symbol fails here, not mid-run(). RTLD_LOCAL: this app's own exported
|
||||||
@@ -100,10 +136,24 @@ void api_unload(AppRuntime runtime_ptr) {
|
|||||||
delete runtime;
|
delete runtime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool api_is_executable(AppLocation location) {
|
||||||
|
if (location.type != APP_LOCATION_PATH) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string app_path;
|
||||||
|
if (resolve_app_path(static_cast<const char*>(location.location), app_path) != ERROR_NONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_executable_file(app_path);
|
||||||
|
}
|
||||||
|
|
||||||
AppLoaderApi loader_api = {
|
AppLoaderApi loader_api = {
|
||||||
.load = api_load,
|
.load = api_load,
|
||||||
.run = api_run,
|
.run = api_run,
|
||||||
.unload = api_unload,
|
.unload = api_unload,
|
||||||
|
.is_executable = api_is_executable,
|
||||||
};
|
};
|
||||||
|
|
||||||
void* create_service(const ServiceManifest*) {
|
void* create_service(const ServiceManifest*) {
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ add_library(app_posix_module_test_fixture SHARED EXCLUDE_FROM_ALL ${CMAKE_CURREN
|
|||||||
target_include_directories(app_posix_module_test_fixture PRIVATE ${CMAKE_SOURCE_DIR}/Modules/app-module/include)
|
target_include_directories(app_posix_module_test_fixture PRIVATE ${CMAKE_SOURCE_DIR}/Modules/app-module/include)
|
||||||
set_target_properties(app_posix_module_test_fixture PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
set_target_properties(app_posix_module_test_fixture PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||||
|
|
||||||
|
# A file with a ".so" extension but no ELF header, for is_executable() rejection tests.
|
||||||
|
set(NON_ELF_FIXTURE_PATH "${CMAKE_CURRENT_BINARY_DIR}/not-elf.so")
|
||||||
|
file(WRITE "${NON_ELF_FIXTURE_PATH}" "not an elf file")
|
||||||
|
|
||||||
file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/source/*.cpp)
|
file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/source/*.cpp)
|
||||||
add_executable(AppPosixModuleTests EXCLUDE_FROM_ALL ${TEST_SOURCES})
|
add_executable(AppPosixModuleTests EXCLUDE_FROM_ALL ${TEST_SOURCES})
|
||||||
add_dependencies(AppPosixModuleTests app_posix_module_test_fixture)
|
add_dependencies(AppPosixModuleTests app_posix_module_test_fixture)
|
||||||
@@ -18,6 +22,7 @@ add_dependencies(AppPosixModuleTests app_posix_module_test_fixture)
|
|||||||
target_include_directories(AppPosixModuleTests PRIVATE ${DOCTESTINC})
|
target_include_directories(AppPosixModuleTests PRIVATE ${DOCTESTINC})
|
||||||
target_compile_definitions(AppPosixModuleTests PRIVATE
|
target_compile_definitions(AppPosixModuleTests PRIVATE
|
||||||
FIXTURE_APP_PATH="$<TARGET_FILE:app_posix_module_test_fixture>"
|
FIXTURE_APP_PATH="$<TARGET_FILE:app_posix_module_test_fixture>"
|
||||||
|
FIXTURE_NON_ELF_PATH="${NON_ELF_FIXTURE_PATH}"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_test(NAME AppPosixModuleTests COMMAND AppPosixModuleTests)
|
add_test(NAME AppPosixModuleTests COMMAND AppPosixModuleTests)
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
|
#include <app/execute.h>
|
||||||
#include <app/loader.h>
|
#include <app/loader.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
#include <service/manager.h>
|
#include <service/manager.h>
|
||||||
@@ -11,6 +13,7 @@
|
|||||||
#include <tactility/delay.h>
|
#include <tactility/delay.h>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
extern ServiceManifest loader_service_manifest; // app-posix-module's own
|
extern ServiceManifest loader_service_manifest; // app-posix-module's own
|
||||||
extern ServiceManifest app_internal_loader_service_manifest; // app-module's real memory loader
|
extern ServiceManifest app_internal_loader_service_manifest; // app-module's real memory loader
|
||||||
@@ -29,6 +32,13 @@ void ensure_memory_loader_registered() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string directory_of(const std::string& path) {
|
||||||
|
auto slash = path.find_last_of('/');
|
||||||
|
return slash == std::string::npos ? "." : path.substr(0, slash);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string FIXTURE_DIR = directory_of(FIXTURE_APP_PATH);
|
||||||
|
|
||||||
bool wait_for_state(AppInstanceId id, AppInstanceState target, uint32_t timeout_ms) {
|
bool wait_for_state(AppInstanceId id, AppInstanceState target, uint32_t timeout_ms) {
|
||||||
uint32_t waited = 0;
|
uint32_t waited = 0;
|
||||||
while (waited < timeout_ms) {
|
while (waited < timeout_ms) {
|
||||||
@@ -41,6 +51,11 @@ bool wait_for_state(AppInstanceId id, AppInstanceState target, uint32_t timeout_
|
|||||||
return app_manager_get_state(id) == target;
|
return app_manager_get_state(id) == target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_executable_path(const char* path) {
|
||||||
|
AppLocation location { APP_LOCATION_PATH, const_cast<char*>(path) };
|
||||||
|
return app_is_executable(location);
|
||||||
|
}
|
||||||
|
|
||||||
std::atomic<int32_t> g_fixture_result { -1 };
|
std::atomic<int32_t> g_fixture_result { -1 };
|
||||||
std::atomic<bool> g_fixture_result_received { false };
|
std::atomic<bool> g_fixture_result_received { false };
|
||||||
|
|
||||||
@@ -62,7 +77,7 @@ int32_t parent_app_main(int, char*[]) {
|
|||||||
app_manager_add(&fixture_manifest);
|
app_manager_add(&fixture_manifest);
|
||||||
|
|
||||||
AppInstanceId fixture_id = 0;
|
AppInstanceId fixture_id = 0;
|
||||||
app_manager_start_for_result("test.posix.fixture", self_id, 0, nullptr, &fixture_id);
|
app_start_for_result("test.posix.fixture", 0, nullptr, self_id, &fixture_id);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (task_event_group_wait_any(&event_group, nullptr, pdMS_TO_TICKS(5000)) != ERROR_NONE) {
|
if (task_event_group_wait_any(&event_group, nullptr, pdMS_TO_TICKS(5000)) != ERROR_NONE) {
|
||||||
@@ -100,7 +115,7 @@ TEST_CASE("app-posix-module's loader-path service dlopen()s a .so and calls its
|
|||||||
REQUIRE_EQ(app_manager_add(&parent_manifest), ERROR_NONE);
|
REQUIRE_EQ(app_manager_add(&parent_manifest), ERROR_NONE);
|
||||||
|
|
||||||
AppInstanceId parent_id = 0;
|
AppInstanceId parent_id = 0;
|
||||||
REQUIRE_EQ(app_manager_start("test.posix.parent", &parent_id), ERROR_NONE);
|
REQUIRE_EQ(app_start("test.posix.parent", 0, nullptr, &parent_id), ERROR_NONE);
|
||||||
REQUIRE(wait_for_state(parent_id, APP_INSTANCE_STATE_STOPPED, 3000));
|
REQUIRE(wait_for_state(parent_id, APP_INSTANCE_STATE_STOPPED, 3000));
|
||||||
|
|
||||||
CHECK(g_fixture_result_received.load(std::memory_order_acquire));
|
CHECK(g_fixture_result_received.load(std::memory_order_acquire));
|
||||||
@@ -111,3 +126,28 @@ TEST_CASE("app-posix-module's loader-path service dlopen()s a .so and calls its
|
|||||||
|
|
||||||
app_manager_remove("test.posix.parent");
|
app_manager_remove("test.posix.parent");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("app_is_executable() accepts a real .so") {
|
||||||
|
ensure_path_loader_registered();
|
||||||
|
|
||||||
|
CHECK(is_executable_path(FIXTURE_APP_PATH));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("app_is_executable() rejects a .so-named file with no ELF header") {
|
||||||
|
ensure_path_loader_registered();
|
||||||
|
|
||||||
|
CHECK_FALSE(is_executable_path(FIXTURE_NON_ELF_PATH));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("app_is_executable() rejects a nonexistent path") {
|
||||||
|
ensure_path_loader_registered();
|
||||||
|
|
||||||
|
CHECK_FALSE(is_executable_path((FIXTURE_DIR + "/does-not-exist.so").c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("app_is_executable() rejects an install-directory-shaped path missing its per-arch .so") {
|
||||||
|
ensure_path_loader_registered();
|
||||||
|
|
||||||
|
// FIXTURE_DIR itself has no elf/posix-<arch>.so under it, so resolution fails.
|
||||||
|
CHECK_FALSE(is_executable_path(FIXTURE_DIR.c_str()));
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace tt::app::fileselection {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a file selection dialog that allows the user to select an existing file, as a modal
|
* Show a file selection dialog that allows the user to select an existing file, as a modal
|
||||||
* child of @a callerAppInstanceId (see app_manager_start_for_result_with_streams()). Result
|
* child of @a callerAppInstanceId (see app_start_for_result_with_streams()). Result
|
||||||
* (0 = Ok, 1 = Cancelled) is delivered back via APP_EVENT_RESULT once this app's thread exits.
|
* (0 = Ok, 1 = Cancelled) is delivered back via APP_EVENT_RESULT once this app's thread exits.
|
||||||
* On result == 0, read the picked path with app_stream_read(&stream, ...) then
|
* On result == 0, read the picked path with app_stream_read(&stream, ...) then
|
||||||
* app_stream_unsubscribe(&stream); on any other result, just app_stream_unsubscribe(&stream).
|
* app_stream_unsubscribe(&stream); on any other result, just app_stream_unsubscribe(&stream).
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <app/stream.h>
|
||||||
|
#include <tactility/concurrent/task_event_group.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a dialog with a title, a message and a text field.
|
* Show a dialog with a title, a message and a text field.
|
||||||
*/
|
*/
|
||||||
@@ -10,19 +14,21 @@ namespace tt::app::inputdialog {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a dialog with the provided title, message and prefilled text, as a modal child of
|
* Show a dialog with the provided title, message and prefilled text, as a modal child of
|
||||||
* @a callerAppInstanceId (a new-model app - see app/manager.h). The caller receives the result
|
* @a callerAppInstanceId (see app_start_for_result_with_streams()). Result (0 = OK, 1 =
|
||||||
* as an APP_EVENT_RESULT in its own event loop: 0 = OK (call getLastText() for the entered
|
* Cancelled or dismissed without a press) is delivered back via APP_EVENT_RESULT once this app's
|
||||||
* text), 1 = Cancelled or dismissed without a press. The caller is responsible for calling
|
* thread exits. On result == 0, read the entered text with app_stream_read(&stream, ...) then
|
||||||
* app_manager_stop() on the returned instance id once it has handled the result.
|
* app_stream_unsubscribe(&stream); on any other result, just app_stream_unsubscribe(&stream).
|
||||||
|
* The caller must call app_manager_stop() on the returned instance id once that event arrives,
|
||||||
|
* to fully reap this instance.
|
||||||
|
* @param[in,out] stream caller-owned storage bound to the started app's stdout; must stay valid
|
||||||
|
* until app_stream_unsubscribe() is called on it (see above).
|
||||||
|
* @param[in] buffer caller-owned backing storage for @a stream's ring buffer; must stay valid
|
||||||
|
* for the same duration as @a stream.
|
||||||
|
* @param[in] bufferCapacity size of @a buffer in bytes.
|
||||||
|
* @param[in] eventGroup the caller's own event group, reused for the stream's readiness bits
|
||||||
|
* (see app_stream_subscribe()). The caller isn't required to actually wait on them itself.
|
||||||
* @return the new dialog's app instance id
|
* @return the new dialog's app instance id
|
||||||
*/
|
*/
|
||||||
uint32_t start(uint32_t callerAppInstanceId, const std::string& title, const std::string& message, const std::string& prefilled = "");
|
uint32_t start(uint32_t callerAppInstanceId, const std::string& title, const std::string& message, const std::string& prefilled, AppStream& stream, void* buffer, size_t bufferCapacity, TaskEventGroup* eventGroup);
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the text entered the last time any InputDialog instance was closed with OK. Only one
|
|
||||||
* dialog is expected to be open at a time - call this right after receiving its
|
|
||||||
* APP_EVENT_RESULT with result == 0.
|
|
||||||
*/
|
|
||||||
std::string getLastText();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
namespace tt::app::wifimanage {
|
namespace tt::app::wifimanage {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts as a modal child of @a callerAppInstanceId (see app_manager_start_for_result()) - an
|
* Starts as a modal child of @a callerAppInstanceId (see app_start_for_result()) - an
|
||||||
* APP_EVENT_RESULT is delivered back once the user closes this screen (default Cancelled/no
|
* APP_EVENT_RESULT is delivered back once the user closes this screen (default Cancelled/no
|
||||||
* bundle if never explicitly set - callers that just want a "the wifi step is done" signal, like
|
* bundle if never explicitly set - callers that just want a "the wifi step is done" signal, like
|
||||||
* Setup, can ignore the actual result value).
|
* Setup, can ignore the actual result value).
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
#include "./State.h"
|
#include "./State.h"
|
||||||
|
|
||||||
|
#include <app/stream.h>
|
||||||
|
#include <tactility/concurrent/task_event_group.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -10,8 +13,12 @@ namespace tt::app::files {
|
|||||||
|
|
||||||
class View final {
|
class View final {
|
||||||
std::shared_ptr<State> state;
|
std::shared_ptr<State> state;
|
||||||
|
TaskEventGroup* eventGroup = nullptr;
|
||||||
uint32_t appInstanceId = 0;
|
uint32_t appInstanceId = 0;
|
||||||
|
|
||||||
|
AppStream inputDialogStream {};
|
||||||
|
uint8_t inputDialogBuffer[256] {};
|
||||||
|
|
||||||
size_t current_start_index = 0;
|
size_t current_start_index = 0;
|
||||||
size_t last_loaded_index = 0;
|
size_t last_loaded_index = 0;
|
||||||
const size_t MAX_BATCH = 50;
|
const size_t MAX_BATCH = 50;
|
||||||
@@ -30,14 +37,16 @@ class View final {
|
|||||||
void showActionsForDirectory();
|
void showActionsForDirectory();
|
||||||
void showActionsForFile();
|
void showActionsForFile();
|
||||||
void showActionsForMountPoint();
|
void showActionsForMountPoint();
|
||||||
|
void addCommonFileActions();
|
||||||
|
|
||||||
void viewFile(const std::string&path, const std::string&filename);
|
void viewFile(const std::string&path, const std::string&filename);
|
||||||
|
void runFile(const std::string& file_path);
|
||||||
void createDirEntryWidget(lv_obj_t* parent, dirent& dir_entry);
|
void createDirEntryWidget(lv_obj_t* parent, dirent& dir_entry);
|
||||||
void onNavigate();
|
void onNavigate();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit View(const std::shared_ptr<State>& state) : state(state) {}
|
View(const std::shared_ptr<State>& state, TaskEventGroup* eventGroup) : state(state), eventGroup(eventGroup) {}
|
||||||
|
|
||||||
void init(uint32_t appInstanceId, lv_obj_t* parent);
|
void init(uint32_t appInstanceId, lv_obj_t* parent);
|
||||||
void update(size_t start_index = 0);
|
void update(size_t start_index = 0);
|
||||||
@@ -54,6 +63,7 @@ public:
|
|||||||
void onCutPressed();
|
void onCutPressed();
|
||||||
void onPastePressed();
|
void onPastePressed();
|
||||||
void onEjectPressed();
|
void onEjectPressed();
|
||||||
|
void onRunPressed();
|
||||||
void onDirEntryListScrollBegin();
|
void onDirEntryListScrollBegin();
|
||||||
void onResult(uint32_t launchId, int32_t result);
|
void onResult(uint32_t launchId, int32_t result);
|
||||||
void deinit();
|
void deinit();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/module.h>
|
#include <app/module.h>
|
||||||
|
|
||||||
@@ -571,7 +572,7 @@ void run(Module* const dtsModules[], const DtsDevice dtsDevices[]) {
|
|||||||
// It's a new-model (app-module + window-manager) app now, replacing the old app::start().
|
// It's a new-model (app-module + window-manager) app now, replacing the old app::start().
|
||||||
app_manager_add(&app::boot::manifest);
|
app_manager_add(&app::boot::manifest);
|
||||||
uint32_t boot_instance_id = 0;
|
uint32_t boot_instance_id = 0;
|
||||||
app_manager_start(app::boot::manifest.id, &boot_instance_id);
|
app_start(app::boot::manifest.id, 0, nullptr, &boot_instance_id);
|
||||||
|
|
||||||
LOG_I(TAG, "Main dispatcher ready");
|
LOG_I(TAG, "Main dispatcher ready");
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -132,7 +133,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Builds argv = [title, message, buttonLabels...] for app_manager_start_for_result().
|
// Builds argv = [title, message, buttonLabels...] for app_start_for_result().
|
||||||
std::vector<const char*> buildArgv(const std::string& title, const std::string& message, const std::vector<std::string>& buttonLabels) {
|
std::vector<const char*> buildArgv(const std::string& title, const std::string& message, const std::vector<std::string>& buttonLabels) {
|
||||||
std::vector<const char*> argv { title.c_str(), message.c_str() };
|
std::vector<const char*> argv { title.c_str(), message.c_str() };
|
||||||
for (const auto& label: buttonLabels) {
|
for (const auto& label: buttonLabels) {
|
||||||
@@ -146,7 +147,7 @@ std::vector<const char*> buildArgv(const std::string& title, const std::string&
|
|||||||
uint32_t start(uint32_t callerAppInstanceId, const std::string& title, const std::string& message, const std::vector<std::string>& buttonLabels) {
|
uint32_t start(uint32_t callerAppInstanceId, const std::string& title, const std::string& message, const std::vector<std::string>& buttonLabels) {
|
||||||
auto argv = buildArgv(title, message, buttonLabels);
|
auto argv = buildArgv(title, message, buttonLabels);
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_for_result(manifest.id, callerAppInstanceId, static_cast<int>(argv.size()), argv.data(), &instanceId);
|
app_start_for_result(manifest.id, static_cast<int>(argv.size()), argv.data(), callerAppInstanceId, &instanceId);
|
||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/install.h>
|
#include <app/install.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
@@ -158,7 +159,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
void start(const std::string& appId) {
|
void start(const std::string& appId) {
|
||||||
const char* argv[] = { appId.c_str() };
|
const char* argv[] = { appId.c_str() };
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_with_parameters(manifest.id, 1, argv, &instanceId);
|
app_start(manifest.id, 1, argv, &instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const ::AppManifest manifest = {
|
extern const ::AppManifest manifest = {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <app/install.h>
|
#include <app/install.h>
|
||||||
#include <app/metadata.h>
|
#include <app/metadata.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -351,7 +352,7 @@ void start(const apphub::AppHubEntry& entry) {
|
|||||||
argv.push_back(platform.c_str());
|
argv.push_back(platform.c_str());
|
||||||
}
|
}
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_for_result(manifest.id, /*parent_instance_id=*/0, static_cast<int>(argv.size()), argv.data(), &instanceId);
|
app_start_for_result(manifest.id, static_cast<int>(argv.size()), argv.data(), /*parent_instance_id=*/0, &instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const ::AppManifest manifest = {
|
extern const ::AppManifest manifest = {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ void onAppPressed(lv_event_t* e) {
|
|||||||
// Fire-and-forget top-level navigation, same as Launcher's own app-launch buttons.
|
// Fire-and-forget top-level navigation, same as Launcher's own app-launch buttons.
|
||||||
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));
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start(manifest->id, &instanceId);
|
app_start(manifest->id, 0, nullptr, &instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onBackPressed(lv_event_t* event) {
|
void onBackPressed(lv_event_t* event) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -251,7 +252,7 @@ void startNextApp() {
|
|||||||
|
|
||||||
auto launcher_app_id = getLauncherAppId();
|
auto launcher_app_id = getLauncherAppId();
|
||||||
uint32_t launcher_instance_id = 0;
|
uint32_t launcher_instance_id = 0;
|
||||||
app_manager_start(launcher_app_id.c_str(), &launcher_instance_id);
|
app_start(launcher_app_id.c_str(), 0, nullptr, &launcher_instance_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void runBootSequence(TickType_t startTime) {
|
void runBootSequence(TickType_t startTime) {
|
||||||
@@ -318,7 +319,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
|
|
||||||
runBootSequence(start_time);
|
runBootSequence(start_time);
|
||||||
|
|
||||||
// Waits until app_manager_start(launcher) (or a permanent stop) tells us to give up -
|
// Waits until app_start(launcher) (or a permanent stop) tells us to give up -
|
||||||
// startNextApp() above is what triggers that, via app-module's "save the previously active
|
// startNextApp() above is what triggers that, via app-module's "save the previously active
|
||||||
// app" policy, unless sdCardMissing halted before it.
|
// app" policy, unless sdCardMissing halted before it.
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -236,7 +237,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
|
|
||||||
uint32_t start() {
|
uint32_t start() {
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start(manifest.id, &instanceId);
|
app_start(manifest.id, 0, nullptr, &instanceId);
|
||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -262,7 +263,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
void start(const std::string& addrHex) {
|
void start(const std::string& addrHex) {
|
||||||
const char* argv[] = { addrHex.c_str() };
|
const char* argv[] = { addrHex.c_str() };
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_with_parameters(manifest.id, 1, argv, &instanceId);
|
app_start(manifest.id, 1, argv, &instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const ::AppManifest manifest = {
|
extern const ::AppManifest manifest = {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -272,7 +273,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start(manifest.id, &instanceId);
|
app_start(manifest.id, 0, nullptr, &instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const ::AppManifest manifest = {
|
extern const ::AppManifest manifest = {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -184,7 +185,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
if (lvgl_is_running()) {
|
if (lvgl_is_running()) {
|
||||||
lvgl_lock();
|
lvgl_lock();
|
||||||
// Widgets only exist while this window is topmost - skip otherwise. Another app
|
// Widgets only exist while this window is topmost - skip otherwise. Another app
|
||||||
// (started non-modally, e.g. via app_manager_start()) can bury this window without
|
// (started non-modally, e.g. via app_start()) can bury this window without
|
||||||
// stopping this instance or notifying it; window_manager deletes a buried window's
|
// stopping this instance or notifying it; window_manager deletes a buried window's
|
||||||
// widgets, so touching ctx->statusLabel here would use-after-free it.
|
// widgets, so touching ctx->statusLabel here would use-after-free it.
|
||||||
if (window_manager_get_state(window) == WINDOW_STATE_GRANTED) {
|
if (window_manager_get_state(window) == WINDOW_STATE_GRANTED) {
|
||||||
|
|||||||
@@ -31,12 +31,13 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
|||||||
int32_t appMain(int argc, char* argv[]) {
|
int32_t appMain(int argc, char* argv[]) {
|
||||||
uint32_t appInstanceId = app_scheduler_current_app_id();
|
uint32_t appInstanceId = app_scheduler_current_app_id();
|
||||||
auto state = std::make_shared<State>();
|
auto state = std::make_shared<State>();
|
||||||
View view(state);
|
|
||||||
CreateContext createContext { &view, appInstanceId };
|
|
||||||
|
|
||||||
TaskEventGroup event_group {};
|
TaskEventGroup event_group {};
|
||||||
task_event_group_construct(&event_group);
|
task_event_group_construct(&event_group);
|
||||||
|
|
||||||
|
View view(state, &event_group);
|
||||||
|
CreateContext createContext { &view, appInstanceId };
|
||||||
|
|
||||||
AppEventSubscription sub {};
|
AppEventSubscription sub {};
|
||||||
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
|
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include <app/install.h>
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
|
#include <app/execute.h>
|
||||||
|
#include <app/install.h>
|
||||||
|
#include <app/stream.h>
|
||||||
|
|
||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
#include <lvgl/widgets/toolbar.h>
|
#include <lvgl/widgets/toolbar.h>
|
||||||
@@ -102,10 +104,20 @@ static void onPastePressedCallback(lv_event_t* event) {
|
|||||||
view->onPastePressed();
|
view->onPastePressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void onRunPressedCallback(lv_event_t* event) {
|
||||||
|
auto* view = static_cast<View*>(lv_event_get_user_data(event));
|
||||||
|
view->onRunPressed();
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region File helpers
|
// region File helpers
|
||||||
|
|
||||||
|
static bool isExecutablePath(const std::string& path) {
|
||||||
|
AppLocation location { APP_LOCATION_PATH, const_cast<char*>(path.c_str()) };
|
||||||
|
return app_is_executable(location);
|
||||||
|
}
|
||||||
|
|
||||||
static bool copyFileContents(const std::string& src, const std::string& dst) {
|
static bool copyFileContents(const std::string& src, const std::string& dst) {
|
||||||
FILE* in = fopen(src.c_str(), "rb");
|
FILE* in = fopen(src.c_str(), "rb");
|
||||||
if (in == nullptr) {
|
if (in == nullptr) {
|
||||||
@@ -192,6 +204,8 @@ void View::viewFile(const std::string& path, const std::string& filename) {
|
|||||||
// Remove forward slash, because we need a relative path
|
// Remove forward slash, because we need a relative path
|
||||||
notes::start(file_path.substr(1));
|
notes::start(file_path.substr(1));
|
||||||
}
|
}
|
||||||
|
} else if (isExecutablePath(file_path)) {
|
||||||
|
runFile(file_path);
|
||||||
} else {
|
} else {
|
||||||
LOG_W(TAG, "Opening files of this type is not supported");
|
LOG_W(TAG, "Opening files of this type is not supported");
|
||||||
}
|
}
|
||||||
@@ -199,6 +213,23 @@ void View::viewFile(const std::string& path, const std::string& filename) {
|
|||||||
onNavigate();
|
onNavigate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void View::runFile(const std::string& file_path) {
|
||||||
|
LOG_I(TAG, "Running %s", file_path.c_str());
|
||||||
|
|
||||||
|
if (!isExecutablePath(file_path)) {
|
||||||
|
LOG_W(TAG, "Not executable: %s", file_path.c_str());
|
||||||
|
alertdialog::start(appInstanceId, "Run failed", "Could not run \"" + file::getLastPathSegment(file_path) + "\".");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppLocation location { APP_LOCATION_PATH, const_cast<char*>(file_path.c_str()) };
|
||||||
|
AppInstanceId instance_id = 0;
|
||||||
|
if (app_execute(location, AppStackConfig {}, 0, nullptr, &instance_id) != ERROR_NONE) {
|
||||||
|
LOG_W(TAG, "Failed to run %s", file_path.c_str());
|
||||||
|
alertdialog::start(appInstanceId, "Run failed", "Could not run \"" + file::getLastPathSegment(file_path) + "\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool View::resolveDirentFromListIndex(int32_t list_index, dirent& out_entry) {
|
bool View::resolveDirentFromListIndex(int32_t list_index, dirent& out_entry) {
|
||||||
const bool is_root = (state->getCurrentPath() == "/");
|
const bool is_root = (state->getCurrentPath() == "/");
|
||||||
const bool has_back = (!is_root && current_start_index > 0);
|
const bool has_back = (!is_root && current_start_index > 0);
|
||||||
@@ -287,6 +318,8 @@ void View::createDirEntryWidget(lv_obj_t* list, dirent& dir_entry) {
|
|||||||
symbol = LV_SYMBOL_IMAGE;
|
symbol = LV_SYMBOL_IMAGE;
|
||||||
} else if (dir_entry.d_type == file::TT_DT_LNK) {
|
} else if (dir_entry.d_type == file::TT_DT_LNK) {
|
||||||
symbol = LV_SYMBOL_LOOP;
|
symbol = LV_SYMBOL_LOOP;
|
||||||
|
} else if (isExecutablePath(file::getChildPath(state->getCurrentPath(), dir_entry.d_name))) {
|
||||||
|
symbol = LV_SYMBOL_PLAY;
|
||||||
} else {
|
} else {
|
||||||
symbol = LV_SYMBOL_FILE;
|
symbol = LV_SYMBOL_FILE;
|
||||||
}
|
}
|
||||||
@@ -346,7 +379,7 @@ void View::onRenamePressed() {
|
|||||||
std::string entry_name = state->getSelectedChildEntry();
|
std::string entry_name = state->getSelectedChildEntry();
|
||||||
LOG_I(TAG, "Pending rename %s", entry_name.c_str());
|
LOG_I(TAG, "Pending rename %s", entry_name.c_str());
|
||||||
state->setPendingAction(State::ActionRename);
|
state->setPendingAction(State::ActionRename);
|
||||||
inputdialog::start(appInstanceId, "Rename", "", entry_name);
|
inputdialog::start(appInstanceId, "Rename", "", entry_name, inputDialogStream, inputDialogBuffer, sizeof(inputDialogBuffer), eventGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
void View::onDeletePressed() {
|
void View::onDeletePressed() {
|
||||||
@@ -361,18 +394,16 @@ void View::onDeletePressed() {
|
|||||||
void View::onNewFilePressed() {
|
void View::onNewFilePressed() {
|
||||||
LOG_I(TAG, "Creating new file");
|
LOG_I(TAG, "Creating new file");
|
||||||
state->setPendingAction(State::ActionCreateFile);
|
state->setPendingAction(State::ActionCreateFile);
|
||||||
inputdialog::start(appInstanceId, "New File", "Enter filename:", "");
|
inputdialog::start(appInstanceId, "New File", "Enter filename:", "", inputDialogStream, inputDialogBuffer, sizeof(inputDialogBuffer), eventGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
void View::onNewFolderPressed() {
|
void View::onNewFolderPressed() {
|
||||||
LOG_I(TAG, "Creating new folder");
|
LOG_I(TAG, "Creating new folder");
|
||||||
state->setPendingAction(State::ActionCreateFolder);
|
state->setPendingAction(State::ActionCreateFolder);
|
||||||
inputdialog::start(appInstanceId, "New Folder", "Enter folder name:", "");
|
inputdialog::start(appInstanceId, "New Folder", "Enter folder name:", "", inputDialogStream, inputDialogBuffer, sizeof(inputDialogBuffer), eventGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
void View::showActions() {
|
void View::addCommonFileActions() {
|
||||||
lv_obj_clean(action_list);
|
|
||||||
|
|
||||||
auto* copy_button = lv_list_add_button(action_list, LV_SYMBOL_COPY, "Copy");
|
auto* copy_button = lv_list_add_button(action_list, LV_SYMBOL_COPY, "Copy");
|
||||||
lv_obj_add_event_cb(copy_button, onCopyPressedCallback, LV_EVENT_SHORT_CLICKED, this);
|
lv_obj_add_event_cb(copy_button, onCopyPressedCallback, LV_EVENT_SHORT_CLICKED, this);
|
||||||
auto* cut_button = lv_list_add_button(action_list, LV_SYMBOL_CUT, "Cut");
|
auto* cut_button = lv_list_add_button(action_list, LV_SYMBOL_CUT, "Cut");
|
||||||
@@ -381,12 +412,27 @@ void View::showActions() {
|
|||||||
lv_obj_add_event_cb(rename_button, onRenamePressedCallback, LV_EVENT_SHORT_CLICKED, this);
|
lv_obj_add_event_cb(rename_button, onRenamePressedCallback, LV_EVENT_SHORT_CLICKED, this);
|
||||||
auto* delete_button = lv_list_add_button(action_list, LV_SYMBOL_TRASH, "Delete");
|
auto* delete_button = lv_list_add_button(action_list, LV_SYMBOL_TRASH, "Delete");
|
||||||
lv_obj_add_event_cb(delete_button, onDeletePressedCallback, LV_EVENT_SHORT_CLICKED, this);
|
lv_obj_add_event_cb(delete_button, onDeletePressedCallback, LV_EVENT_SHORT_CLICKED, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void View::showActions() {
|
||||||
|
lv_obj_clean(action_list);
|
||||||
|
addCommonFileActions();
|
||||||
lv_obj_remove_flag(action_list, LV_OBJ_FLAG_HIDDEN);
|
lv_obj_remove_flag(action_list, LV_OBJ_FLAG_HIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
void View::showActionsForDirectory() { showActions(); }
|
void View::showActionsForDirectory() { showActions(); }
|
||||||
void View::showActionsForFile() { showActions(); }
|
|
||||||
|
void View::showActionsForFile() {
|
||||||
|
lv_obj_clean(action_list);
|
||||||
|
|
||||||
|
if (isExecutablePath(state->getSelectedChildPath())) {
|
||||||
|
auto* run_button = lv_list_add_button(action_list, LV_SYMBOL_PLAY, "Run");
|
||||||
|
lv_obj_add_event_cb(run_button, onRunPressedCallback, LV_EVENT_SHORT_CLICKED, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
addCommonFileActions();
|
||||||
|
lv_obj_remove_flag(action_list, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
void View::showActionsForMountPoint() {
|
void View::showActionsForMountPoint() {
|
||||||
lv_obj_clean(action_list);
|
lv_obj_clean(action_list);
|
||||||
@@ -397,6 +443,12 @@ void View::showActionsForMountPoint() {
|
|||||||
lv_obj_remove_flag(action_list, LV_OBJ_FLAG_HIDDEN);
|
lv_obj_remove_flag(action_list, LV_OBJ_FLAG_HIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void View::onRunPressed() {
|
||||||
|
std::string file_path = state->getSelectedChildPath();
|
||||||
|
onNavigate();
|
||||||
|
runFile(file_path);
|
||||||
|
}
|
||||||
|
|
||||||
void View::onEjectPressed() {
|
void View::onEjectPressed() {
|
||||||
std::string mount_path = state->getSelectedChildPath();
|
std::string mount_path = state->getSelectedChildPath();
|
||||||
LOG_I(TAG, "Ejecting %s", mount_path.c_str());
|
LOG_I(TAG, "Ejecting %s", mount_path.c_str());
|
||||||
@@ -546,10 +598,21 @@ void View::onResult(uint32_t launchId, int32_t result) {
|
|||||||
std::string filepath = state->getSelectedChildPath();
|
std::string filepath = state->getSelectedChildPath();
|
||||||
LOG_I(TAG, "Result for %s", filepath.c_str());
|
LOG_I(TAG, "Result for %s", filepath.c_str());
|
||||||
|
|
||||||
// Text-entry result (rename/new file/new folder); empty for Cancel, or for a dialog that
|
// Text-entry result (rename/new file/new folder), read from the AppStream bound to that
|
||||||
// doesn't produce text (delete/paste confirmations) - those switch cases below only look at
|
// dialog's stdout. Empty for Cancel. Other pending actions (delete/paste confirmations) never
|
||||||
// `result`, not this.
|
// bound this stream; their switch cases below only look at `result`, not `resultText`.
|
||||||
std::string resultText = (result == 0) ? inputdialog::getLastText() : std::string();
|
bool isTextEntryAction = state->getPendingAction() == State::ActionRename ||
|
||||||
|
state->getPendingAction() == State::ActionCreateFile ||
|
||||||
|
state->getPendingAction() == State::ActionCreateFolder;
|
||||||
|
std::string resultText;
|
||||||
|
if (isTextEntryAction) {
|
||||||
|
if (result == 0) {
|
||||||
|
char buffer[sizeof(inputDialogBuffer)];
|
||||||
|
size_t length = app_stream_read(&inputDialogStream, buffer, sizeof(buffer));
|
||||||
|
resultText = std::string(buffer, length);
|
||||||
|
}
|
||||||
|
app_stream_unsubscribe(&inputDialogStream);
|
||||||
|
}
|
||||||
|
|
||||||
switch (state->getPendingAction()) {
|
switch (state->getPendingAction()) {
|
||||||
case State::ActionDelete: {
|
case State::ActionDelete: {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/io.h>
|
#include <app/io.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
#include <app/stream.h>
|
#include <app/stream.h>
|
||||||
@@ -106,7 +107,7 @@ uint32_t startWithMode(const char* modeArg, uint32_t callerAppInstanceId, AppStr
|
|||||||
.event_group = eventGroup,
|
.event_group = eventGroup,
|
||||||
};
|
};
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_for_result_with_streams(manifest.id, callerAppInstanceId, 1, argv, &binding, 1, &instanceId);
|
app_start_for_result_with_streams(manifest.id, 1, argv, &binding, 1, callerAppInstanceId, &instanceId);
|
||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ void onAddGpsPressed(lv_event_t* event) {
|
|||||||
// this app; rebuildDeviceList() runs fresh whenever this app is resumed regardless).
|
// this app; rebuildDeviceList() runs fresh whenever this app is resumed regardless).
|
||||||
(void)ctx;
|
(void)ctx;
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start(addgps::manifest.id, &instanceId);
|
app_start(addgps::manifest.id, 0, nullptr, &instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDeviceButtonPressed(lv_event_t* event) {
|
void onDeviceButtonPressed(lv_event_t* event) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/paths.h>
|
#include <app/paths.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
@@ -430,7 +431,7 @@ extern const ::AppManifest manifest = {
|
|||||||
|
|
||||||
uint32_t start() {
|
uint32_t start() {
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start(manifest.id, &instanceId);
|
app_start(manifest.id, 0, nullptr, &instanceId);
|
||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
void start(const std::string& file) {
|
void start(const std::string& file) {
|
||||||
const char* argv[] = { file.c_str() };
|
const char* argv[] = { file.c_str() };
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_with_parameters(manifest.id, 1, argv, &instanceId);
|
app_start(manifest.id, 1, argv, &instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const ::AppManifest manifest = {
|
extern const ::AppManifest manifest = {
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
#include <app/stream.h>
|
||||||
|
|
||||||
#include <lvgl_window_manager/window_manager.h>
|
#include <lvgl_window_manager/window_manager.h>
|
||||||
|
|
||||||
@@ -13,6 +15,8 @@
|
|||||||
|
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
namespace tt::app::inputdialog {
|
namespace tt::app::inputdialog {
|
||||||
|
|
||||||
constexpr auto* TAG = "InputDialog";
|
constexpr auto* TAG = "InputDialog";
|
||||||
@@ -30,7 +34,8 @@ struct Context {
|
|||||||
// The eventual appMain() return value - see AlertDialog.cpp's Context::result for why this
|
// The eventual appMain() return value - see AlertDialog.cpp's Context::result for why this
|
||||||
// is a plain (non-atomic) field safely shared between the LVGL thread (writer, before
|
// is a plain (non-atomic) field safely shared between the LVGL thread (writer, before
|
||||||
// emitting APP_EVENT_CLOSE) and this dialog's own thread (reader, after waking from it).
|
// emitting APP_EVENT_CLOSE) and this dialog's own thread (reader, after waking from it).
|
||||||
int32_t result = 1; // Cancelled - safety-net default if closed without pressing a button
|
int32_t resultCode = 1; // Cancelled - safety-net default if closed without pressing a button
|
||||||
|
std::string resultText;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ButtonContext {
|
struct ButtonContext {
|
||||||
@@ -39,12 +44,6 @@ struct ButtonContext {
|
|||||||
lv_obj_t* textarea;
|
lv_obj_t* textarea;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The last text entered via OK. Static rather than per-instance: simple, and in practice only
|
|
||||||
// one InputDialog is ever open at a time. Written on the LVGL thread (onButtonPressed(), before
|
|
||||||
// emitting APP_EVENT_CLOSE); read by the parent via getLastText() after receiving that event -
|
|
||||||
// safe without a lock for the same reason Context::result is (see AlertDialog.cpp).
|
|
||||||
std::string lastText;
|
|
||||||
|
|
||||||
void onButtonDeleted(lv_event_t* e) {
|
void onButtonDeleted(lv_event_t* e) {
|
||||||
delete static_cast<ButtonContext*>(lv_event_get_user_data(e));
|
delete static_cast<ButtonContext*>(lv_event_get_user_data(e));
|
||||||
}
|
}
|
||||||
@@ -53,11 +52,11 @@ void onButtonPressed(lv_event_t* e) {
|
|||||||
auto* btnCtx = static_cast<ButtonContext*>(lv_event_get_user_data(e));
|
auto* btnCtx = static_cast<ButtonContext*>(lv_event_get_user_data(e));
|
||||||
if (btnCtx->textarea != nullptr) {
|
if (btnCtx->textarea != nullptr) {
|
||||||
LOG_I(TAG, "OK pressed");
|
LOG_I(TAG, "OK pressed");
|
||||||
lastText = lv_textarea_get_text(btnCtx->textarea);
|
btnCtx->ctx->resultText = lv_textarea_get_text(btnCtx->textarea);
|
||||||
btnCtx->ctx->result = 0;
|
btnCtx->ctx->resultCode = 0;
|
||||||
} else {
|
} else {
|
||||||
LOG_I(TAG, "Cancel pressed");
|
LOG_I(TAG, "Cancel pressed");
|
||||||
btnCtx->ctx->result = 1;
|
btnCtx->ctx->resultCode = 1;
|
||||||
}
|
}
|
||||||
app_event_emit_close(btnCtx->ctx->appInstanceId);
|
app_event_emit_close(btnCtx->ctx->appInstanceId);
|
||||||
}
|
}
|
||||||
@@ -137,22 +136,30 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
check(app_event_unsubscribe(&sub) == ERROR_NONE);
|
check(app_event_unsubscribe(&sub) == ERROR_NONE);
|
||||||
task_event_group_destruct(&event_group);
|
task_event_group_destruct(&event_group);
|
||||||
|
|
||||||
return ctx.result;
|
if (ctx.resultCode == 0) {
|
||||||
|
// The caller captures this via an AppStream bound to our stdout (see start()); see
|
||||||
|
// AppStdioWrap.cpp for how printf() itself gets routed there on POSIX.
|
||||||
|
printf("%s", ctx.resultText.c_str());
|
||||||
|
}
|
||||||
|
return ctx.resultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
uint32_t start(uint32_t callerAppInstanceId, const std::string& title, const std::string& message, const std::string& prefilled) {
|
uint32_t start(uint32_t callerAppInstanceId, const std::string& title, const std::string& message, const std::string& prefilled, AppStream& stream, void* buffer, size_t bufferCapacity, TaskEventGroup* eventGroup) {
|
||||||
const char* argv[] = { title.c_str(), message.c_str(), prefilled.c_str() };
|
const char* argv[] = { title.c_str(), message.c_str(), prefilled.c_str() };
|
||||||
|
AppStreamBinding binding = {
|
||||||
|
.producer_fd = STDOUT_FILENO,
|
||||||
|
.stream = &stream,
|
||||||
|
.buffer = buffer,
|
||||||
|
.buffer_capacity = bufferCapacity,
|
||||||
|
.event_group = eventGroup,
|
||||||
|
};
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_for_result(manifest.id, callerAppInstanceId, 3, argv, &instanceId);
|
app_start_for_result_with_streams(manifest.id, 3, argv, &binding, 1, callerAppInstanceId, &instanceId);
|
||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getLastText() {
|
|
||||||
return lastText;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern const ::AppManifest manifest = {
|
extern const ::AppManifest manifest = {
|
||||||
.id = "tactility.inputdialog",
|
.id = "tactility.inputdialog",
|
||||||
.name = "Input Dialog",
|
.name = "Input Dialog",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -45,7 +46,7 @@ int32_t computeButtonMargin(int32_t available_span, int32_t total_button_size) {
|
|||||||
void onAppPressed(lv_event_t* e) {
|
void onAppPressed(lv_event_t* e) {
|
||||||
auto* appId = static_cast<const char*>(lv_event_get_user_data(e));
|
auto* appId = static_cast<const char*>(lv_event_get_user_data(e));
|
||||||
uint32_t instance_id = 0;
|
uint32_t instance_id = 0;
|
||||||
app_manager_start(appId, &instance_id);
|
app_start(appId, 0, nullptr, &instance_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
lv_obj_t* createAppButton(lv_obj_t* parent, UiDensity uiDensity, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) {
|
lv_obj_t* createAppButton(lv_obj_t* parent, UiDensity uiDensity, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) {
|
||||||
@@ -214,7 +215,7 @@ void runAutoStart() {
|
|||||||
) {
|
) {
|
||||||
LOG_I(TAG, "Starting %s", CONFIG_TT_AUTO_START_APP_ID);
|
LOG_I(TAG, "Starting %s", CONFIG_TT_AUTO_START_APP_ID);
|
||||||
uint32_t app_launch_id;
|
uint32_t app_launch_id;
|
||||||
app_manager_start(CONFIG_TT_AUTO_START_APP_ID, &app_launch_id);
|
app_start(CONFIG_TT_AUTO_START_APP_ID, 0, nullptr, &app_launch_id);
|
||||||
} else if (
|
} else if (
|
||||||
// Auto-start due to user configuration
|
// Auto-start due to user configuration
|
||||||
settings::loadBootSettings(boot_properties) &&
|
settings::loadBootSettings(boot_properties) &&
|
||||||
@@ -223,7 +224,7 @@ void runAutoStart() {
|
|||||||
) {
|
) {
|
||||||
LOG_I(TAG, "Starting %s", boot_properties.autoStartAppId.c_str());
|
LOG_I(TAG, "Starting %s", boot_properties.autoStartAppId.c_str());
|
||||||
uint32_t app_launch_id;
|
uint32_t app_launch_id;
|
||||||
app_manager_start(boot_properties.autoStartAppId.c_str(), &app_launch_id);
|
app_start(boot_properties.autoStartAppId.c_str(), 0, nullptr, &app_launch_id);
|
||||||
} else {
|
} else {
|
||||||
// No auto-start, consider running system setup
|
// No auto-start, consider running system setup
|
||||||
if (!setup::isCompleted()) {
|
if (!setup::isCompleted()) {
|
||||||
@@ -282,7 +283,7 @@ extern const ::AppManifest manifest = {
|
|||||||
// used by the old, unconverted CrashDiagnostics app to return to the launcher after a crash).
|
// used by the old, unconverted CrashDiagnostics app to return to the launcher after a crash).
|
||||||
uint32_t start() {
|
uint32_t start() {
|
||||||
uint32_t instance_id = 0;
|
uint32_t instance_id = 0;
|
||||||
app_manager_start(manifest.id, &instance_id);
|
app_start(manifest.id, 0, nullptr, &instance_id);
|
||||||
return instance_id;
|
return instance_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
#include <app/stream.h>
|
#include <app/stream.h>
|
||||||
@@ -268,7 +269,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
void start(const std::string& filePath) {
|
void start(const std::string& filePath) {
|
||||||
const char* argv[] = { filePath.c_str() };
|
const char* argv[] = { filePath.c_str() };
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_with_parameters(manifest.id, 1, argv, &instanceId);
|
app_start(manifest.id, 1, argv, &instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const ::AppManifest manifest = {
|
extern const ::AppManifest manifest = {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Builds argv = [title, items...] for app_manager_start_for_result().
|
// Builds argv = [title, items...] for app_start_for_result().
|
||||||
std::vector<const char*> buildArgv(const std::string& title, const std::vector<std::string>& items) {
|
std::vector<const char*> buildArgv(const std::string& title, const std::vector<std::string>& items) {
|
||||||
std::vector<const char*> argv { title.c_str() };
|
std::vector<const char*> argv { title.c_str() };
|
||||||
for (const auto& item: items) {
|
for (const auto& item: items) {
|
||||||
@@ -147,7 +148,7 @@ std::vector<const char*> buildArgv(const std::string& title, const std::vector<s
|
|||||||
AppInstanceId start(AppInstanceId callerAppInstanceId, const std::string& title, const std::vector<std::string>& items) {
|
AppInstanceId start(AppInstanceId callerAppInstanceId, const std::string& title, const std::vector<std::string>& items) {
|
||||||
auto argv = buildArgv(title, items);
|
auto argv = buildArgv(title, items);
|
||||||
AppInstanceId instanceId = 0;
|
AppInstanceId instanceId = 0;
|
||||||
app_manager_start_for_result(manifest.id, callerAppInstanceId, static_cast<int>(argv.size()), argv.data(), &instanceId);
|
app_start_for_result(manifest.id, static_cast<int>(argv.size()), argv.data(), callerAppInstanceId, &instanceId);
|
||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ void onAppPressed(lv_event_t* e) {
|
|||||||
// Fire-and-forget top-level navigation, same as AppList's own app-launch buttons.
|
// Fire-and-forget top-level navigation, same as AppList's own app-launch buttons.
|
||||||
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));
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start(manifest->id, &instanceId);
|
app_start(manifest->id, 0, nullptr, &instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onBackPressed(lv_event_t* event) {
|
void onBackPressed(lv_event_t* event) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -269,7 +270,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start(manifest.id, &instanceId);
|
app_start(manifest.id, 0, nullptr, &instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const ::AppManifest manifest = {
|
extern const ::AppManifest manifest = {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -206,7 +207,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
|
|
||||||
uint32_t start() {
|
uint32_t start() {
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start(manifest.id, &instanceId);
|
app_start(manifest.id, 0, nullptr, &instanceId);
|
||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -273,7 +274,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
uint32_t start(uint32_t callerAppInstanceId, bool saveTimeZone) {
|
uint32_t start(uint32_t callerAppInstanceId, bool saveTimeZone) {
|
||||||
const char* argv[] = { saveTimeZone ? "1" : "0" };
|
const char* argv[] = { saveTimeZone ? "1" : "0" };
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_for_result(manifest.id, callerAppInstanceId, 1, argv, &instanceId);
|
app_start_for_result(manifest.id, 1, argv, callerAppInstanceId, &instanceId);
|
||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -285,7 +286,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
|
|
||||||
uint32_t start(uint32_t callerAppInstanceId) {
|
uint32_t start(uint32_t callerAppInstanceId) {
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_for_result(manifest.id, callerAppInstanceId, 0, nullptr, &instanceId);
|
app_start_for_result(manifest.id, 0, nullptr, callerAppInstanceId, &instanceId);
|
||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -317,7 +318,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
void start(const std::string& ssid) {
|
void start(const std::string& ssid) {
|
||||||
const char* argv[] = { ssid.c_str() };
|
const char* argv[] = { ssid.c_str() };
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_with_parameters(manifest.id, 1, argv, &instanceId);
|
app_start(manifest.id, 1, argv, &instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const ::AppManifest manifest = {
|
extern const ::AppManifest manifest = {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -377,7 +378,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
void start(const std::string& ssid, const std::string& password) {
|
void start(const std::string& ssid, const std::string& password) {
|
||||||
const char* argv[] = { ssid.c_str(), password.c_str() };
|
const char* argv[] = { ssid.c_str(), password.c_str() };
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_with_parameters(manifest.id, 2, argv, &instanceId);
|
app_start(manifest.id, 2, argv, &instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const ::AppManifest manifest = {
|
extern const ::AppManifest manifest = {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
#include <app/manifest.h>
|
#include <app/manifest.h>
|
||||||
#include <app/scheduler.h>
|
#include <app/scheduler.h>
|
||||||
|
|
||||||
@@ -240,7 +241,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
|
|
||||||
uint32_t start(uint32_t callerAppInstanceId) {
|
uint32_t start(uint32_t callerAppInstanceId) {
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
app_manager_start_for_result(manifest.id, callerAppInstanceId, 0, nullptr, &instanceId);
|
app_start_for_result(manifest.id, 0, nullptr, callerAppInstanceId, &instanceId);
|
||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <app/install.h>
|
#include <app/install.h>
|
||||||
#include <app/manager.h>
|
#include <app/manager.h>
|
||||||
|
#include <app/start.h>
|
||||||
|
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
|
||||||
@@ -113,7 +114,7 @@ esp_err_t DevelopmentService::handleAppRun(httpd_req_t* request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app_manager_start(id_key_pos->second.c_str(), &instance_id);
|
app_start(id_key_pos->second.c_str(), 0, nullptr, &instance_id);
|
||||||
|
|
||||||
LOG_I(TAG, "[200] /app/run %s", id_key_pos->second.c_str());
|
LOG_I(TAG, "[200] /app/run %s", id_key_pos->second.c_str());
|
||||||
httpd_resp_send(request, nullptr, 0);
|
httpd_resp_send(request, nullptr, 0);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <Tactility/service/webserver/WebServerService.h>
|
#include <Tactility/service/webserver/WebServerService.h>
|
||||||
#include <Tactility/service/ServiceManifest.h>
|
#include <Tactility/service/ServiceManifest.h>
|
||||||
|
|
||||||
|
#include <app/start.h>
|
||||||
#include <Tactility/settings/WebServerSettings.h>
|
#include <Tactility/settings/WebServerSettings.h>
|
||||||
#include <Tactility/MountPoints.h>
|
#include <Tactility/MountPoints.h>
|
||||||
#include <Tactility/file/File.h>
|
#include <Tactility/file/File.h>
|
||||||
@@ -1270,7 +1272,7 @@ esp_err_t WebServerService::handleApiAppsRun(httpd_req_t* request) {
|
|||||||
// Every app instance gets its own task now, so there's no "stop the existing one first" -
|
// Every app instance gets its own task now, so there's no "stop the existing one first" -
|
||||||
// this just starts a fresh instance alongside whatever's already running.
|
// this just starts a fresh instance alongside whatever's already running.
|
||||||
AppInstanceId instance_id = 0;
|
AppInstanceId instance_id = 0;
|
||||||
app_manager_start(appId.c_str(), &instance_id);
|
app_start(appId.c_str(), 0, nullptr, &instance_id);
|
||||||
|
|
||||||
LOG_I(TAG, "[200] /api/apps/run %s", appId.c_str());
|
LOG_I(TAG, "[200] /api/apps/run %s", appId.c_str());
|
||||||
httpd_resp_sendstr(request, "ok");
|
httpd_resp_sendstr(request, "ok");
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
if (NOT DEFINED TACTILITY_SDK_PATH)
|
include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake")
|
||||||
get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}/..")
|
|
||||||
endif ()
|
|
||||||
include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake")
|
|
||||||
|
|
||||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c)
|
file(GLOB_RECURSE SOURCE_FILES Source/*.c)
|
||||||
tactility_component_register(SRCS ${SOURCE_FILES} INCLUDE_DIRS include)
|
tactility_component_register(SRCS ${SOURCE_FILES} INCLUDE_DIRS include)
|
||||||
@@ -12,7 +12,7 @@ import tarfile
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
ttbuild_path = ".tactility"
|
ttbuild_path = ".tactility"
|
||||||
ttbuild_version = "5.0.0"
|
ttbuild_version = "5.0.1"
|
||||||
ttbuild_cdn = "https://cdn.tactilityproject.org"
|
ttbuild_cdn = "https://cdn.tactilityproject.org"
|
||||||
ttbuild_sdk_json_validity = 3600 # seconds
|
ttbuild_sdk_json_validity = 3600 # seconds
|
||||||
ttport = 6666
|
ttport = 6666
|
||||||
@@ -140,9 +140,13 @@ def get_sdk_dir(version, platform):
|
|||||||
sdk_dir = os.path.join(sdk_parent_dir, "TactilitySDK")
|
sdk_dir = os.path.join(sdk_parent_dir, "TactilitySDK")
|
||||||
if not os.path.isdir(sdk_dir):
|
if not os.path.isdir(sdk_dir):
|
||||||
exit_with_error(f"Local SDK folder not found for platform {platform}: {sdk_dir}")
|
exit_with_error(f"Local SDK folder not found for platform {platform}: {sdk_dir}")
|
||||||
return sdk_dir
|
return os.path.abspath(sdk_dir)
|
||||||
else:
|
else:
|
||||||
return os.path.join(ttbuild_path, f"{version}-{platform}", "TactilitySDK")
|
# Must be absolute: this is exported as $TACTILITY_SDK_PATH and included by each app's
|
||||||
|
# main/CMakeLists.txt, which ESP-IDF also re-processes in a separate `cmake -P` subprocess
|
||||||
|
# (tools/cmake/scripts/component_get_requirements.cmake) with its own working directory -
|
||||||
|
# a relative path here resolves against whatever CWD that subprocess happens to have.
|
||||||
|
return os.path.abspath(os.path.join(ttbuild_path, f"{version}-{platform}", "TactilitySDK"))
|
||||||
|
|
||||||
def validate_local_sdks(platforms, version):
|
def validate_local_sdks(platforms, version):
|
||||||
if not use_local_sdk:
|
if not use_local_sdk:
|
||||||
|
|||||||
Reference in New Issue
Block a user