Merge develop into main (#343)
- Refactor `AppManifest`: add new fields and rename existing ones - Parse and validate the manifest from an app that is being installed. - Remove deprecated `scoped()` from `Lock` - Create `Tactility/Paths.h` - App loading at boot now properly parses the manifest files of external apps - Properly lock both source and destination locations during app install - Remove LVGL path variants from `AppPaths` and `ServicePaths` - Removed `xPath` base classes for apps and services. There's now `AppPaths` and `ServicePaths`. - Renamed app and service paths: "data" and "system" paths are now "user data" and "assets"
@ -17,7 +17,7 @@ class HelloWorldApp : public App {
|
||||
};
|
||||
|
||||
extern const AppManifest hello_world_app = {
|
||||
.id = "HelloWorld",
|
||||
.name = "Hello World",
|
||||
.appId = "HelloWorld",
|
||||
.appName = "Hello World",
|
||||
.createApp = create<HelloWorldApp>
|
||||
};
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
#include "CYD4848S040C.h"
|
||||
|
||||
#include "Tactility/kernel/SystemEvents.h"
|
||||
#include "Tactility/lvgl/LvglSync.h"
|
||||
#include "devices/St7701Display.h"
|
||||
#include "devices/SdCard.h"
|
||||
|
||||
@ -12,7 +15,7 @@ static bool initBoot() {
|
||||
|
||||
static DeviceVector createDevices() {
|
||||
return {
|
||||
std::reinterpret_pointer_cast<Device>(std::make_shared<St7701Display>()),
|
||||
std::make_shared<St7701Display>(),
|
||||
createSdCard()
|
||||
};
|
||||
}
|
||||
@ -59,7 +62,7 @@ const Configuration cyd_4848s040c_config = {
|
||||
}
|
||||
},
|
||||
.spi {
|
||||
//SD Card
|
||||
// SD Card & display init
|
||||
spi::Configuration {
|
||||
.device = SPI2_HOST,
|
||||
.dma = SPI_DMA_CH_AUTO,
|
||||
@ -68,20 +71,20 @@ const Configuration cyd_4848s040c_config = {
|
||||
.miso_io_num = GPIO_NUM_41,
|
||||
.sclk_io_num = GPIO_NUM_48,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.quadhd_io_num = GPIO_NUM_42,
|
||||
.data4_io_num = -1,
|
||||
.data5_io_num = -1,
|
||||
.data6_io_num = -1,
|
||||
.data7_io_num = -1,
|
||||
.data_io_default_level = false,
|
||||
.max_transfer_sz = 8192,
|
||||
.max_transfer_sz = 1024 * 128,
|
||||
.flags = 0,
|
||||
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
|
||||
.intr_flags = 0
|
||||
},
|
||||
.initMode = spi::InitMode::ByTactility,
|
||||
.isMutable = false,
|
||||
.lock = nullptr
|
||||
.lock = tt::lvgl::getSyncLock()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -11,7 +11,9 @@ std::shared_ptr<SdCardDevice> createSdCard() {
|
||||
GPIO_NUM_NC,
|
||||
GPIO_NUM_NC,
|
||||
GPIO_NUM_NC,
|
||||
SdCardDevice::MountBehaviour::AtBoot
|
||||
SdCardDevice::MountBehaviour::AtBoot,
|
||||
tt::lvgl::getSyncLock(),
|
||||
std::vector { GPIO_NUM_39 }
|
||||
);
|
||||
|
||||
auto sdcard = std::make_shared<SpiSdCardDevice>(
|
||||
|
||||
@ -1,39 +1,32 @@
|
||||
# Contributing
|
||||
|
||||
## Accepted changes
|
||||
## New features and boards
|
||||
|
||||
Before releasing version 1.0.0, the APIs are changing rapidly.
|
||||
I want to minimize the amount of changes that I have to maintain, because it limits the amount of breaking changes that I have to deal with when the APIs change.
|
||||
Feel free to open an [issue](https://github.com/ByteWelder/Tactility/issues/new)
|
||||
to discuss ideas you have regarding the implementation of new boards or features.
|
||||
|
||||
### New features
|
||||
Keep in mind that the internal APIs are changing rapidly. They might change considerably in a short timespan.
|
||||
This means it's likely that you get merge conflicts while developing new features or boards.
|
||||
|
||||
These are currently not accepted.
|
||||
## Fixing things
|
||||
|
||||
### Visual / interaction design changes
|
||||
|
||||
These are currently not accepted. I'm aware that there is a lot of room for improvement, but I want to mainly focusing on strong technical foundations before
|
||||
I start building further on top of that.
|
||||
Feel free to open an [issue](https://github.com/ByteWelder/Tactility/issues/new) to discuss ideas you have, if you feel like they will have a considerable impact.
|
||||
|
||||
### Fixing things
|
||||
|
||||
The general take here is that minor changes are not accepted at this stage. I don't want to spend my time reviewing and discussing these during the current stage of the project.
|
||||
The general take here is that minor changes are not accepted at this stage.
|
||||
I don't want to spend my time reviewing and discussing these during the current stage of the project.
|
||||
|
||||
Only fixes for serious issues are accepted, like:
|
||||
|
||||
- Bugs
|
||||
- Crashes
|
||||
- Security issues
|
||||
|
||||
Some examples of non-serious issues include:
|
||||
- Documentation changes
|
||||
|
||||
- Code documentation changes
|
||||
- Renaming code
|
||||
- Fixes for compiler warnings that have low impact on the actual application logic (e.g. only happens on simulator when calling a logging function)
|
||||
- Typographical errors
|
||||
- Fixes for compiler warnings that have a low impact on the actual application logic (e.g. only happens on simulator when calling a logging function)
|
||||
|
||||
### New board implementations
|
||||
|
||||
Please open an [issue](https://github.com/ByteWelder/Tactility/issues/new) on GitHub to discuss new boards.
|
||||
|
||||
### Anything that doesn't fall in the above categories?
|
||||
## Anything that doesn't fall in the above categories?
|
||||
|
||||
Please [contact me](https://tactility.one/#/support) first!
|
||||
|
||||
@ -42,6 +35,13 @@ Please [contact me](https://tactility.one/#/support) first!
|
||||
Pull requests should only contain a single set of changes that are related to each other.
|
||||
That way, an approved set of changes will not be blocked by an unapproved set of changes.
|
||||
|
||||
## Licensing
|
||||
|
||||
All contributions to a Tactility (sub)project will be licensed under the license(s) of that project.
|
||||
|
||||
When third party code is used, its license must be included.
|
||||
It's important that these third-party licenses are compatible with the relevant Tactility (sub)projects.
|
||||
|
||||
## Code Style
|
||||
|
||||
See [this document](CODING_STYLE.md) and [.clang-format](.clang-format).
|
||||
|
||||
|
Before Width: | Height: | Size: 753 B After Width: | Height: | Size: 753 B |
|
Before Width: | Height: | Size: 528 B After Width: | Height: | Size: 528 B |
|
Before Width: | Height: | Size: 142 B After Width: | Height: | Size: 142 B |
|
Before Width: | Height: | Size: 144 B After Width: | Height: | Size: 144 B |
|
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 149 B |
|
Before Width: | Height: | Size: 146 B After Width: | Height: | Size: 146 B |
|
Before Width: | Height: | Size: 146 B After Width: | Height: | Size: 146 B |
|
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 149 B |
|
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 149 B |
|
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 149 B |
|
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 149 B |
|
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 149 B |
|
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 149 B |
|
Before Width: | Height: | Size: 193 B After Width: | Height: | Size: 193 B |
|
Before Width: | Height: | Size: 196 B After Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 394 B After Width: | Height: | Size: 394 B |
|
Before Width: | Height: | Size: 407 B After Width: | Height: | Size: 407 B |
|
Before Width: | Height: | Size: 524 B After Width: | Height: | Size: 524 B |
|
Before Width: | Height: | Size: 517 B After Width: | Height: | Size: 517 B |
|
Before Width: | Height: | Size: 534 B After Width: | Height: | Size: 534 B |
@ -93,6 +93,7 @@
|
||||
- RSS reader
|
||||
- Static file web server (with option to specify path and port)
|
||||
- Diceware
|
||||
- Port TamaFi https://github.com/cifertech/TamaFi
|
||||
|
||||
# App Store
|
||||
|
||||
|
||||
@ -4,10 +4,7 @@ version=0.1
|
||||
sdk=0.6.0-SNAPSHOT1
|
||||
platforms=esp32,esp32s3
|
||||
[app]
|
||||
id=com.bytewelder.calculator
|
||||
version=0.1.0
|
||||
id=one.tactility.calculator
|
||||
versionName=0.1.0
|
||||
versionCode=1
|
||||
name=Calculator
|
||||
description=Math is cool
|
||||
[author]
|
||||
name=ByteWelder
|
||||
website=https://bytewelder.com
|
||||
|
||||
@ -14,7 +14,7 @@ import shutil
|
||||
import configparser
|
||||
|
||||
ttbuild_path = ".tactility"
|
||||
ttbuild_version = "2.1.1"
|
||||
ttbuild_version = "2.2.0"
|
||||
ttbuild_cdn = "https://cdn.tactility.one"
|
||||
ttbuild_sdk_json_validity = 3600 # seconds
|
||||
ttport = 6666
|
||||
@ -242,19 +242,12 @@ def validate_manifest(manifest):
|
||||
exit_with_error("Invalid manifest format: [app] not found")
|
||||
if not "id" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] id not found")
|
||||
if not "version" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] version not found")
|
||||
if not "versionName" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] versionName not found")
|
||||
if not "versionCode" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] versionCode not found")
|
||||
if not "name" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] name not found")
|
||||
if not "description" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] description not found")
|
||||
# [author]
|
||||
if not "author" in manifest:
|
||||
exit_with_error("Invalid manifest format: [author] not found")
|
||||
if not "name" in manifest["author"]:
|
||||
exit_with_error("Invalid manifest format: [author] name not found")
|
||||
if not "website" in manifest["author"]:
|
||||
exit_with_error("Invalid manifest format: [author] website not found")
|
||||
|
||||
def is_valid_manifest_platform(manifest, platform):
|
||||
manifest_platforms = manifest["target"]["platforms"].split(",")
|
||||
|
||||
@ -4,10 +4,7 @@ version=0.1
|
||||
sdk=0.6.0-SNAPSHOT1
|
||||
platforms=esp32,esp32s3
|
||||
[app]
|
||||
id=com.bytewelder.graphicsdemo
|
||||
version=0.1.0
|
||||
id=one.tactility.graphicsdemo
|
||||
versionName=0.1.0
|
||||
versionCode=1
|
||||
name=Graphics Demo
|
||||
description=A graphics and touch driver demonstration
|
||||
[author]
|
||||
name=ByteWelder
|
||||
website=https://bytewelder.com
|
||||
|
||||
@ -14,7 +14,7 @@ import shutil
|
||||
import configparser
|
||||
|
||||
ttbuild_path = ".tactility"
|
||||
ttbuild_version = "2.1.1"
|
||||
ttbuild_version = "2.2.0"
|
||||
ttbuild_cdn = "https://cdn.tactility.one"
|
||||
ttbuild_sdk_json_validity = 3600 # seconds
|
||||
ttport = 6666
|
||||
@ -242,19 +242,12 @@ def validate_manifest(manifest):
|
||||
exit_with_error("Invalid manifest format: [app] not found")
|
||||
if not "id" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] id not found")
|
||||
if not "version" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] version not found")
|
||||
if not "versionName" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] versionName not found")
|
||||
if not "versionCode" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] versionCode not found")
|
||||
if not "name" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] name not found")
|
||||
if not "description" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] description not found")
|
||||
# [author]
|
||||
if not "author" in manifest:
|
||||
exit_with_error("Invalid manifest format: [author] not found")
|
||||
if not "name" in manifest["author"]:
|
||||
exit_with_error("Invalid manifest format: [author] name not found")
|
||||
if not "website" in manifest["author"]:
|
||||
exit_with_error("Invalid manifest format: [author] website not found")
|
||||
|
||||
def is_valid_manifest_platform(manifest, platform):
|
||||
manifest_platforms = manifest["target"]["platforms"].split(",")
|
||||
|
||||
@ -4,10 +4,7 @@ version=0.1
|
||||
sdk=0.6.0-SNAPSHOT1
|
||||
platforms=esp32,esp32s3
|
||||
[app]
|
||||
id=com.bytewelder.helloworld
|
||||
version=0.1.0
|
||||
id=one.tactility.helloworld
|
||||
versionName=0.1.0
|
||||
versionCode=1
|
||||
name=Hello World
|
||||
description=A demonstration app that says hi
|
||||
[author]
|
||||
name=ByteWelder
|
||||
website=https://bytewelder.com
|
||||
|
||||
@ -14,7 +14,7 @@ import shutil
|
||||
import configparser
|
||||
|
||||
ttbuild_path = ".tactility"
|
||||
ttbuild_version = "2.1.1"
|
||||
ttbuild_version = "2.2.0"
|
||||
ttbuild_cdn = "https://cdn.tactility.one"
|
||||
ttbuild_sdk_json_validity = 3600 # seconds
|
||||
ttport = 6666
|
||||
@ -242,19 +242,12 @@ def validate_manifest(manifest):
|
||||
exit_with_error("Invalid manifest format: [app] not found")
|
||||
if not "id" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] id not found")
|
||||
if not "version" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] version not found")
|
||||
if not "versionName" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] versionName not found")
|
||||
if not "versionCode" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] versionCode not found")
|
||||
if not "name" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] name not found")
|
||||
if not "description" in manifest["app"]:
|
||||
exit_with_error("Invalid manifest format: [app] description not found")
|
||||
# [author]
|
||||
if not "author" in manifest:
|
||||
exit_with_error("Invalid manifest format: [author] not found")
|
||||
if not "name" in manifest["author"]:
|
||||
exit_with_error("Invalid manifest format: [author] name not found")
|
||||
if not "website" in manifest["author"]:
|
||||
exit_with_error("Invalid manifest format: [author] website not found")
|
||||
|
||||
def is_valid_manifest_platform(manifest, platform):
|
||||
manifest_platforms = manifest["target"]["platforms"].split(",")
|
||||
|
||||
21
Tactility/Include/Tactility/Paths.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tt {
|
||||
|
||||
bool findFirstMountedSdCardPath(std::string& path);
|
||||
|
||||
std::string getSystemRootPath();
|
||||
|
||||
std::string getTempPath();
|
||||
|
||||
std::string getAppInstallPath();
|
||||
|
||||
std::string getAppInstallPath(const std::string& appId);
|
||||
|
||||
std::string getUserPath();
|
||||
|
||||
std::string getAppUserPath(const std::string& appId);
|
||||
|
||||
}
|
||||
@ -94,10 +94,6 @@ std::shared_ptr<AppContext> _Nullable getCurrentAppContext();
|
||||
/** @return the currently running app (it is only ever null before the splash screen is shown) */
|
||||
std::shared_ptr<App> _Nullable getCurrentApp();
|
||||
|
||||
std::string getTempPath();
|
||||
|
||||
std::string getInstallPath();
|
||||
|
||||
bool install(const std::string& path);
|
||||
|
||||
bool uninstall(const std::string& appId);
|
||||
|
||||
@ -7,7 +7,7 @@ namespace tt::app {
|
||||
|
||||
// Forward declarations
|
||||
class App;
|
||||
class Paths;
|
||||
class AppPaths;
|
||||
struct AppManifest;
|
||||
enum class Result;
|
||||
|
||||
@ -32,70 +32,10 @@ public:
|
||||
|
||||
virtual const AppManifest& getManifest() const = 0;
|
||||
virtual std::shared_ptr<const Bundle> getParameters() const = 0;
|
||||
virtual std::unique_ptr<Paths> getPaths() const = 0;
|
||||
virtual std::unique_ptr<AppPaths> getPaths() const = 0;
|
||||
|
||||
virtual std::shared_ptr<App> getApp() const = 0;
|
||||
};
|
||||
|
||||
class Paths {
|
||||
|
||||
public:
|
||||
|
||||
Paths() = default;
|
||||
virtual ~Paths() = default;
|
||||
|
||||
/**
|
||||
* Returns the directory path for the data location for an app.
|
||||
* The data directory is intended to survive OS upgrades.
|
||||
* The path will not end with a "/".
|
||||
*/
|
||||
virtual std::string getDataDirectory() const = 0;
|
||||
|
||||
/**
|
||||
* @see getDataDirectory(), but with LVGL prefix.
|
||||
*/
|
||||
virtual std::string getDataDirectoryLvgl() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the full path for an entry inside the data location for an app.
|
||||
* The data directory is intended to survive OS upgrades.
|
||||
* Configuration data should be stored here.
|
||||
* @param[in] childPath the path without a "/" prefix
|
||||
*/
|
||||
virtual std::string getDataPath(const std::string& childPath) const = 0;
|
||||
|
||||
/**
|
||||
* @see getDataPath(), but with LVGL prefix.
|
||||
*/
|
||||
virtual std::string getDataPathLvgl(const std::string& childPath) const = 0;
|
||||
|
||||
/**
|
||||
* Returns the directory path for the system location for an app.
|
||||
* The system directory is not intended to survive OS upgrades.
|
||||
* You should not store configuration data here.
|
||||
* The path will not end with a "/".
|
||||
* This is mainly used for core apps (system/boot/settings type).
|
||||
*/
|
||||
virtual std::string getSystemDirectory() const = 0;
|
||||
|
||||
/**
|
||||
* @see getSystemDirectory(), but with LVGL prefix.
|
||||
*/
|
||||
virtual std::string getSystemDirectoryLvgl() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the full path for an entry inside the system location for an app.
|
||||
* The data directory is not intended to survive OS upgrades.
|
||||
* You should not store configuration data here.
|
||||
* This is mainly used for core apps (system/boot/settings type).
|
||||
* @param[in] childPath the path without a "/" prefix
|
||||
*/
|
||||
virtual std::string getSystemPath(const std::string& childPath) const = 0;
|
||||
|
||||
/**
|
||||
* @see getSystemPath(), but with LVGL prefix.
|
||||
*/
|
||||
virtual std::string getSystemPathLvgl(const std::string& childPath) const = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -63,30 +63,45 @@ struct AppManifest {
|
||||
constexpr static uint32_t Hidden = 1 << 1;
|
||||
};
|
||||
|
||||
/** The version of the manifest file format */
|
||||
std::string manifestVersion = {};
|
||||
|
||||
/** The SDK version that was used to compile this app. (e.g. "0.6.0") */
|
||||
std::string targetSdk = {};
|
||||
|
||||
/** Comma-separated list of platforms, e.g. "esp32,esp32s3" */
|
||||
std::string targetPlatforms = {};
|
||||
|
||||
/** The identifier by which the app is launched by the system and other apps. */
|
||||
std::string id = {};
|
||||
std::string appId = {};
|
||||
|
||||
/** The user-readable name of the app. Used in UI. */
|
||||
std::string name = {};
|
||||
std::string appName = {};
|
||||
|
||||
/** Optional icon. */
|
||||
std::string icon = {};
|
||||
std::string appIcon = {};
|
||||
|
||||
/** The version as it is displayed to the user (e.g. "1.2.0") */
|
||||
std::string appVersionName = {};
|
||||
|
||||
/** The technical version (must be incremented with new releases of the app */
|
||||
uint64_t appVersionCode = {};
|
||||
|
||||
/** App category helps with listing apps in Launcher, app list or settings apps. */
|
||||
Category category = Category::User;
|
||||
Category appCategory = Category::User;
|
||||
|
||||
/** Where the app is located */
|
||||
Location location = Location::internal();
|
||||
Location appLocation = Location::internal();
|
||||
|
||||
/** Controls various settings */
|
||||
uint32_t flags = Flags::None;
|
||||
uint32_t appFlags = Flags::None;
|
||||
|
||||
/** Create the instance of the app */
|
||||
CreateApp createApp = nullptr;
|
||||
};
|
||||
|
||||
struct {
|
||||
bool operator()(const std::shared_ptr<AppManifest>& left, const std::shared_ptr<AppManifest>& right) const { return left->name < right->name; }
|
||||
bool operator()(const std::shared_ptr<AppManifest>& left, const std::shared_ptr<AppManifest>& right) const { return left->appName < right->appName; }
|
||||
} SortAppManifestByName;
|
||||
|
||||
} // namespace
|
||||
|
||||
47
Tactility/Include/Tactility/app/AppPaths.h
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
// Forward declarations
|
||||
class AppManifest;
|
||||
|
||||
class AppPaths {
|
||||
|
||||
const AppManifest& manifest;
|
||||
|
||||
public:
|
||||
|
||||
explicit AppPaths(const AppManifest& manifest) : manifest(manifest) {}
|
||||
|
||||
/**
|
||||
* The user data directory is intended to survive OS upgrades.
|
||||
* The path will not end with a "/".
|
||||
*/
|
||||
std::string getUserDataPath() const;
|
||||
|
||||
/**
|
||||
* The user data directory is intended to survive OS upgrades.
|
||||
* Configuration data should be stored here.
|
||||
* @param[in] childPath the path without a "/" prefix
|
||||
*/
|
||||
std::string getUserDataPath(const std::string& childPath) const;
|
||||
|
||||
/**
|
||||
* You should not store configuration data here.
|
||||
* The path will not end with a "/".
|
||||
* This is mainly used for core apps (system/boot/settings type).
|
||||
*/
|
||||
std::string getAssetsDirectory() const;
|
||||
|
||||
/**
|
||||
* You should not store configuration data here.
|
||||
* This is mainly used for core apps (system/boot/settings type).
|
||||
* @param[in] childPath the path without a "/" prefix
|
||||
*/
|
||||
std::string getAssetsPath(const std::string& childPath) const;
|
||||
};
|
||||
|
||||
}
|
||||
@ -1,14 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "ServiceManifest.h"
|
||||
|
||||
#include <Tactility/Mutex.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
class Paths;
|
||||
struct ServiceManifest;
|
||||
class ServicePaths;
|
||||
|
||||
/**
|
||||
* The public representation of a service instance.
|
||||
@ -26,68 +23,8 @@ public:
|
||||
virtual const ServiceManifest& getManifest() const = 0;
|
||||
|
||||
/** Retrieve the paths that are relevant to this service */
|
||||
virtual std::unique_ptr<Paths> getPaths() const = 0;
|
||||
virtual std::unique_ptr<ServicePaths> getPaths() const = 0;
|
||||
};
|
||||
|
||||
class Paths {
|
||||
|
||||
public:
|
||||
|
||||
Paths() = default;
|
||||
virtual ~Paths() = default;
|
||||
|
||||
/**
|
||||
* Returns the directory path for the data location for a service.
|
||||
* The data directory is intended to survive OS upgrades.
|
||||
* The path will not end with a "/".
|
||||
*/
|
||||
virtual std::string getDataDirectory() const = 0;
|
||||
|
||||
/**
|
||||
* @see getDataDirectory(), but with LVGL prefix.
|
||||
*/
|
||||
virtual std::string getDataDirectoryLvgl() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the full path for an entry inside the data location for a service.
|
||||
* The data directory is intended to survive OS upgrades.
|
||||
* Configuration data should be stored here.
|
||||
* @param[in] childPath the path without a "/" prefix
|
||||
*/
|
||||
virtual std::string getDataPath(const std::string& childPath) const = 0;
|
||||
|
||||
/**
|
||||
* @see getDataPath(), but with LVGL prefix.
|
||||
*/
|
||||
virtual std::string getDataPathLvgl(const std::string& childPath) const = 0;
|
||||
|
||||
/**
|
||||
* Returns the directory path for the system location for a service.
|
||||
* The system directory is not intended to survive OS upgrades.
|
||||
* You should not store configuration data here.
|
||||
* The path will not end with a "/".
|
||||
* This is mainly used for core services.
|
||||
*/
|
||||
virtual std::string getSystemDirectory() const = 0;
|
||||
|
||||
/**
|
||||
* @see getSystemDirectory(), but with LVGL prefix.
|
||||
*/
|
||||
virtual std::string getSystemDirectoryLvgl() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the full path for an entry inside the system location for an app.
|
||||
* The data directory is not intended to survive OS upgrades.
|
||||
* You should not store configuration data here.
|
||||
* This is mainly used for core apps (system/boot/settings type).
|
||||
* @param[in] childPath the path without a "/" prefix
|
||||
*/
|
||||
virtual std::string getSystemPath(const std::string& childPath) const = 0;
|
||||
|
||||
/**
|
||||
* @see getSystemPath(), but with LVGL prefix.
|
||||
*/
|
||||
virtual std::string getSystemPathLvgl(const std::string& childPath) const = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tactility/service/Service.h"
|
||||
#include <Tactility/service/Service.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
45
Tactility/Include/Tactility/service/ServicePaths.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
// Forward declarations
|
||||
class ServiceManifest;
|
||||
|
||||
class ServicePaths {
|
||||
|
||||
std::shared_ptr<const ServiceManifest> manifest;
|
||||
|
||||
public:
|
||||
|
||||
explicit ServicePaths(std::shared_ptr<const ServiceManifest> manifest) : manifest(std::move(manifest)) {}
|
||||
|
||||
/**
|
||||
* The user data directory is intended to survive OS upgrades.
|
||||
* The path will not end with a "/".
|
||||
*/
|
||||
std::string getUserDataDirectory() const;
|
||||
|
||||
/**
|
||||
* The user data directory is intended to survive OS upgrades.
|
||||
* Configuration data should be stored here.
|
||||
* @param[in] childPath the path without a "/" prefix
|
||||
*/
|
||||
std::string getUserDataPath(const std::string& childPath) const;
|
||||
|
||||
/**
|
||||
* You should not store configuration data here.
|
||||
* The path will not end with a "/".
|
||||
*/
|
||||
std::string getAssetsDirectory() const;
|
||||
|
||||
/**
|
||||
* You should not store configuration data here.
|
||||
* @param[in] childPath the path without a "/" prefix
|
||||
*/
|
||||
std::string getAssetsPath(const std::string& childPath) const;
|
||||
};
|
||||
|
||||
}
|
||||
@ -24,7 +24,7 @@ class GpsService final : public Service {
|
||||
Mutex stateMutex;
|
||||
std::vector<GpsDeviceRecord> deviceRecords;
|
||||
std::shared_ptr<PubSub<State>> statePubSub = std::make_shared<PubSub<State>>();
|
||||
std::unique_ptr<Paths> paths;
|
||||
std::unique_ptr<ServicePaths> paths;
|
||||
State state = State::Off;
|
||||
|
||||
bool startGpsDevice(GpsDeviceRecord& deviceRecord);
|
||||
|
||||
@ -41,10 +41,10 @@ class AppInstance : public AppContext {
|
||||
static std::shared_ptr<App> createApp(
|
||||
const std::shared_ptr<AppManifest>& manifest
|
||||
) {
|
||||
if (manifest->location.isInternal()) {
|
||||
if (manifest->appLocation.isInternal()) {
|
||||
assert(manifest->createApp != nullptr);
|
||||
return manifest->createApp();
|
||||
} else if (manifest->location.isExternal()) {
|
||||
} else if (manifest->appLocation.isExternal()) {
|
||||
if (manifest->createApp != nullptr) {
|
||||
TT_LOG_W("", "Manifest specifies createApp, but this is not used with external apps");
|
||||
}
|
||||
@ -88,7 +88,7 @@ public:
|
||||
|
||||
std::shared_ptr<const Bundle> getParameters() const override;
|
||||
|
||||
std::unique_ptr<Paths> getPaths() const override;
|
||||
std::unique_ptr<AppPaths> getPaths() const override;
|
||||
|
||||
std::shared_ptr<App> getApp() const override { return app; }
|
||||
};
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tactility/app/AppInstance.h"
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
class AppInstancePaths final : public Paths {
|
||||
|
||||
const AppManifest& manifest;
|
||||
|
||||
public:
|
||||
|
||||
explicit AppInstancePaths(const AppManifest& manifest) : manifest(manifest) {}
|
||||
~AppInstancePaths() override = default;
|
||||
|
||||
std::string getDataDirectory() const override;
|
||||
std::string getDataDirectoryLvgl() const override;
|
||||
std::string getDataPath(const std::string& childPath) const override;
|
||||
std::string getDataPathLvgl(const std::string& childPath) const override;
|
||||
std::string getSystemDirectory() const override;
|
||||
std::string getSystemDirectoryLvgl() const override;
|
||||
std::string getSystemPath(const std::string& childPath) const override;
|
||||
std::string getSystemPathLvgl(const std::string& childPath) const override;
|
||||
};
|
||||
|
||||
}
|
||||
14
Tactility/Private/Tactility/app/AppManifestParsing.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
bool isValidId(const std::string& id);
|
||||
|
||||
bool parseManifest(const std::map<std::string, std::string>& map, AppManifest& manifest);
|
||||
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
#include "./State.h"
|
||||
|
||||
#include <Tactility/app/AppContext.h>
|
||||
#include <Tactility/app/AppPaths.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
@ -13,7 +14,7 @@ class View final {
|
||||
|
||||
Bindings* bindings;
|
||||
State* state;
|
||||
std::unique_ptr<Paths> paths;
|
||||
std::unique_ptr<AppPaths> paths;
|
||||
lv_obj_t* root = nullptr;
|
||||
lv_obj_t* enable_switch = nullptr;
|
||||
lv_obj_t* enable_on_boot_switch = nullptr;
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tactility/service/ServiceContext.h"
|
||||
#include "Tactility/service/Service.h"
|
||||
#include <Tactility/service/ServiceContext.h>
|
||||
#include <Tactility/service/Service.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
@ -21,7 +24,7 @@ public:
|
||||
const ServiceManifest& getManifest() const override;
|
||||
|
||||
/** Retrieve the paths that are relevant to this service */
|
||||
std::unique_ptr<Paths> getPaths() const override;
|
||||
std::unique_ptr<ServicePaths> getPaths() const override;
|
||||
|
||||
std::shared_ptr<Service> getService() const { return service; }
|
||||
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tactility/service/ServiceInstance.h"
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
class ServiceInstancePaths final : public Paths {
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<const ServiceManifest> manifest;
|
||||
|
||||
public:
|
||||
|
||||
explicit ServiceInstancePaths(std::shared_ptr<const ServiceManifest> manifest) : manifest(std::move(manifest)) {}
|
||||
~ServiceInstancePaths() final = default;
|
||||
|
||||
std::string getDataDirectory() const final;
|
||||
std::string getDataDirectoryLvgl() const final;
|
||||
std::string getDataPath(const std::string& childPath) const final;
|
||||
std::string getDataPathLvgl(const std::string& childPath) const final;
|
||||
std::string getSystemDirectory() const final;
|
||||
std::string getSystemDirectoryLvgl() const final;
|
||||
std::string getSystemPath(const std::string& childPath) const final;
|
||||
std::string getSystemPathLvgl(const std::string& childPath) const final;
|
||||
};
|
||||
|
||||
}
|
||||
56
Tactility/Source/Paths.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
#include <Tactility/Paths.h>
|
||||
|
||||
#include <Tactility/app/AppManifestParsing.h>
|
||||
#include <Tactility/MountPoints.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
|
||||
#include <format>
|
||||
|
||||
namespace tt {
|
||||
|
||||
bool findFirstMountedSdCardPath(std::string& path) {
|
||||
// const auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||
bool is_set = false;
|
||||
hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard, [&is_set, &path](const auto& device) {
|
||||
if (device->isMounted()) {
|
||||
path = device->getMountPath();
|
||||
is_set = true;
|
||||
return false; // stop iterating
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return is_set;
|
||||
}
|
||||
|
||||
std::string getSystemRootPath() {
|
||||
std::string root_path;
|
||||
if (!findFirstMountedSdCardPath(root_path)) {
|
||||
root_path = file::MOUNT_POINT_DATA;
|
||||
}
|
||||
return root_path;
|
||||
}
|
||||
|
||||
std::string getTempPath() {
|
||||
return getSystemRootPath() + "/tmp";
|
||||
}
|
||||
|
||||
std::string getAppInstallPath() {
|
||||
return getSystemRootPath() + "/app";
|
||||
}
|
||||
|
||||
std::string getUserPath() {
|
||||
return getSystemRootPath() + "/user";
|
||||
}
|
||||
|
||||
std::string getAppInstallPath(const std::string& appId) {
|
||||
assert(app::isValidId(appId));
|
||||
return std::format("{}/{}", getAppInstallPath(), appId);
|
||||
}
|
||||
|
||||
std::string getAppUserPath(const std::string& appId) {
|
||||
assert(app::isValidId(appId));
|
||||
return std::format("{}/app/{}", getUserPath(), appId);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
#include "Tactility/app/AppManifestParsing.h"
|
||||
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/TactilityConfig.h>
|
||||
#include <Tactility/app/AppRegistration.h>
|
||||
@ -158,29 +160,22 @@ static void registerInstalledApp(std::string path) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> manifest;
|
||||
if (!file::loadPropertiesFile(manifest_path, manifest)) {
|
||||
std::map<std::string, std::string> properties;
|
||||
if (!file::loadPropertiesFile(manifest_path, properties)) {
|
||||
TT_LOG_E(TAG, "Failed to load manifest at %s", manifest_path.c_str());
|
||||
}
|
||||
|
||||
auto app_id_entry = manifest.find("[app]id");
|
||||
if (app_id_entry == manifest.end()) {
|
||||
TT_LOG_E(TAG, "Failed to find app id in manifest");
|
||||
return;
|
||||
}
|
||||
|
||||
auto app_name_entry = manifest.find("[app]name");
|
||||
if (app_name_entry == manifest.end()) {
|
||||
TT_LOG_E(TAG, "Failed to find app name in manifest");
|
||||
app::AppManifest manifest;
|
||||
if (!app::parseManifest(properties, manifest)) {
|
||||
TT_LOG_E(TAG, "Failed to parse manifest at %s", manifest_path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
app::addApp({
|
||||
.id = app_id_entry->second,
|
||||
.name = app_name_entry->second,
|
||||
.category = app::Category::User,
|
||||
.location = app::Location::external(path)
|
||||
});
|
||||
manifest.appCategory = app::Category::User;
|
||||
manifest.appLocation = app::Location::external(path);
|
||||
|
||||
app::addApp(manifest);
|
||||
}
|
||||
|
||||
static void registerInstalledApps(const std::string& path) {
|
||||
@ -194,7 +189,7 @@ static void registerInstalledApps(const std::string& path) {
|
||||
|
||||
static void registerInstalledAppsFromSdCard(const std::shared_ptr<hal::sdcard::SdCardDevice>& sdcard) {
|
||||
auto sdcard_root_path = sdcard->getMountPath();
|
||||
auto app_path = std::format("{}/apps", sdcard_root_path);
|
||||
auto app_path = std::format("{}/app", sdcard_root_path);
|
||||
sdcard->getLock()->lock();
|
||||
if (file::isDirectory(app_path)) {
|
||||
registerInstalledApps(app_path);
|
||||
@ -289,7 +284,7 @@ void run(const Configuration& config) {
|
||||
TT_LOG_I(TAG, "Starting boot app");
|
||||
// The boot app takes care of registering system apps, user services and user apps
|
||||
addApp(app::boot::manifest);
|
||||
service::loader::startApp(app::boot::manifest.id);
|
||||
service::loader::startApp(app::boot::manifest.appId);
|
||||
|
||||
TT_LOG_I(TAG, "Main dispatcher ready");
|
||||
while (true) {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#include <Tactility/app/App.h>
|
||||
#include "Tactility/Paths.h"
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
#include <Tactility/app/AppManifestParsing.h>
|
||||
|
||||
#include <Tactility/MountPoints.h>
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
#include <Tactility/app/AppRegistration.h>
|
||||
#include <Tactility/file/File.h>
|
||||
@ -103,130 +105,105 @@ static bool untar(const std::string& tarPath, const std::string& destinationPath
|
||||
return success;
|
||||
}
|
||||
|
||||
bool findFirstMountedSdCardPath(std::string& path) {
|
||||
// const auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||
bool is_set = false;
|
||||
hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard, [&is_set, &path](const auto& device) {
|
||||
if (device->isMounted()) {
|
||||
path = device->getMountPath();
|
||||
is_set = true;
|
||||
return false; // stop iterating
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return is_set;
|
||||
}
|
||||
|
||||
std::string getTempPath() {
|
||||
std::string root_path;
|
||||
if (!findFirstMountedSdCardPath(root_path)) {
|
||||
root_path = file::MOUNT_POINT_DATA;
|
||||
void cleanupInstallDirectory(const std::string& path) {
|
||||
const auto lock = file::getLock(path);
|
||||
lock->lock();
|
||||
if (!file::deleteRecursively(path)) {
|
||||
TT_LOG_W(TAG, "Failed to delete existing installation at %s", path.c_str());
|
||||
}
|
||||
return root_path + "/tmp";
|
||||
}
|
||||
|
||||
std::string getInstallPath() {
|
||||
std::string root_path;
|
||||
if (!findFirstMountedSdCardPath(root_path)) {
|
||||
root_path = file::MOUNT_POINT_DATA;
|
||||
}
|
||||
return root_path + "/apps";
|
||||
lock->unlock();
|
||||
}
|
||||
|
||||
bool install(const std::string& path) {
|
||||
// TODO: Make better: lock for each path type properly (source vs target)
|
||||
|
||||
// We lock and unlock frequently because SPI SD card devices share
|
||||
// the lock with the display. We don't want to lock the display for very long.
|
||||
|
||||
auto app_parent_path = getInstallPath();
|
||||
auto app_parent_path = getAppInstallPath();
|
||||
TT_LOG_I(TAG, "Installing app %s to %s", path.c_str(), app_parent_path.c_str());
|
||||
|
||||
auto lock = file::getLock(app_parent_path)->asScopedLock();
|
||||
auto target_path_lock = file::getLock(app_parent_path)->asScopedLock();
|
||||
|
||||
lock.lock();
|
||||
target_path_lock.lock();
|
||||
auto filename = file::getLastPathSegment(path);
|
||||
const std::string app_target_path = std::format("{}/{}", app_parent_path, filename);
|
||||
if (file::isDirectory(app_target_path) && !file::deleteRecursively(app_target_path)) {
|
||||
TT_LOG_W(TAG, "Failed to delete %s", app_target_path.c_str());
|
||||
}
|
||||
lock.unlock();
|
||||
target_path_lock.unlock();
|
||||
|
||||
lock.lock();
|
||||
target_path_lock.lock();
|
||||
if (!file::findOrCreateDirectory(app_target_path, 0777)) {
|
||||
TT_LOG_I(TAG, "Failed to create directory %s", app_target_path.c_str());
|
||||
return false;
|
||||
}
|
||||
lock.unlock();
|
||||
target_path_lock.unlock();
|
||||
|
||||
lock.lock();
|
||||
auto source_path_lock = file::getLock(path)->asScopedLock();
|
||||
target_path_lock.lock();
|
||||
source_path_lock.lock();
|
||||
TT_LOG_I(TAG, "Extracting app from %s to %s", path.c_str(), app_target_path.c_str());
|
||||
if (!untar(path, app_target_path)) {
|
||||
TT_LOG_E(TAG, "Failed to extract");
|
||||
return false;
|
||||
}
|
||||
lock.unlock();
|
||||
source_path_lock.unlock();
|
||||
target_path_lock.unlock();
|
||||
|
||||
lock.lock();
|
||||
target_path_lock.lock();
|
||||
auto manifest_path = app_target_path + "/manifest.properties";
|
||||
if (!file::isFile(manifest_path)) {
|
||||
TT_LOG_E(TAG, "Manifest not found at %s", manifest_path.c_str());
|
||||
cleanupInstallDirectory(app_target_path);
|
||||
return false;
|
||||
}
|
||||
lock.unlock();
|
||||
target_path_lock.unlock();
|
||||
|
||||
lock.lock();
|
||||
target_path_lock.lock();
|
||||
std::map<std::string, std::string> properties;
|
||||
if (!file::loadPropertiesFile(manifest_path, properties)) {
|
||||
TT_LOG_E(TAG, "Failed to load manifest at %s", manifest_path.c_str());
|
||||
cleanupInstallDirectory(app_target_path);
|
||||
return false;
|
||||
}
|
||||
lock.unlock();
|
||||
target_path_lock.unlock();
|
||||
|
||||
auto app_id_iterator = properties.find("[app]id");
|
||||
if (app_id_iterator == properties.end()) {
|
||||
TT_LOG_E(TAG, "Failed to find app id in manifest");
|
||||
AppManifest manifest;
|
||||
if (!parseManifest(properties, manifest)) {
|
||||
TT_LOG_W(TAG, "Invalid manifest");
|
||||
cleanupInstallDirectory(app_target_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto app_name_entry = properties.find("[app]name");
|
||||
if (app_name_entry == properties.end()) {
|
||||
TT_LOG_E(TAG, "Failed to find app name in manifest");
|
||||
return false;
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
const std::string renamed_target_path = std::format("{}/{}", app_parent_path, app_id_iterator->second);
|
||||
target_path_lock.lock();
|
||||
const std::string renamed_target_path = std::format("{}/{}", app_parent_path, manifest.appId);
|
||||
if (file::isDirectory(renamed_target_path)) {
|
||||
if (!file::deleteRecursively(renamed_target_path)) {
|
||||
TT_LOG_W(TAG, "Failed to delete existing installation at %s", renamed_target_path.c_str());
|
||||
cleanupInstallDirectory(app_target_path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
lock.unlock();
|
||||
target_path_lock.unlock();
|
||||
|
||||
lock.lock();
|
||||
target_path_lock.lock();
|
||||
if (rename(app_target_path.c_str(), renamed_target_path.c_str()) != 0) {
|
||||
TT_LOG_E(TAG, "Failed to rename %s to %s", app_target_path.c_str(), app_id_iterator->second.c_str());
|
||||
TT_LOG_E(TAG, "Failed to rename \"%s\" to \"%s\"", app_target_path.c_str(), manifest.appId.c_str());
|
||||
cleanupInstallDirectory(app_target_path);
|
||||
return false;
|
||||
}
|
||||
lock.unlock();
|
||||
target_path_lock.unlock();
|
||||
|
||||
addApp({
|
||||
.id = app_id_iterator->second,
|
||||
.name = app_name_entry->second,
|
||||
.category = Category::User,
|
||||
.location = Location::external(renamed_target_path)
|
||||
});
|
||||
manifest.appLocation = Location::external(renamed_target_path);
|
||||
|
||||
addApp(manifest);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool uninstall(const std::string& appId) {
|
||||
TT_LOG_I(TAG, "Uninstalling app %s", appId.c_str());
|
||||
auto app_path = getInstallPath() + "/" + appId;
|
||||
return file::withLock<bool>(app_path, [&app_path, &appId]() {
|
||||
auto app_path = getAppInstallPath(appId);
|
||||
return file::withLock<bool>(app_path, [&app_path, &appId] {
|
||||
if (!file::isDirectory(app_path)) {
|
||||
TT_LOG_E(TAG, "App %s not found at ", app_path.c_str());
|
||||
return false;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#include "Tactility/app/AppInstance.h"
|
||||
#include "Tactility/app/AppInstancePaths.h"
|
||||
#include <Tactility/app/AppInstance.h>
|
||||
#include <Tactility/app/AppPaths.h>
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
@ -49,9 +49,9 @@ std::shared_ptr<const Bundle> AppInstance::getParameters() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<Paths> AppInstance::getPaths() const {
|
||||
std::unique_ptr<AppPaths> AppInstance::getPaths() const {
|
||||
assert(manifest != nullptr);
|
||||
return std::make_unique<AppInstancePaths>(*manifest);
|
||||
return std::make_unique<AppPaths>(*manifest);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
#include "Tactility/app/AppInstancePaths.h"
|
||||
|
||||
#include <Tactility/MountPoints.h>
|
||||
|
||||
#define LVGL_PATH_PREFIX std::string("A:/")
|
||||
#ifdef ESP_PLATFORM
|
||||
#define PARTITION_PREFIX std::string("/")
|
||||
#else
|
||||
#define PARTITION_PREFIX std::string("")
|
||||
#endif
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
std::string AppInstancePaths::getDataDirectory() const {
|
||||
return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.id;
|
||||
}
|
||||
|
||||
std::string AppInstancePaths::getDataDirectoryLvgl() const {
|
||||
return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.id;
|
||||
}
|
||||
|
||||
std::string AppInstancePaths::getDataPath(const std::string& childPath) const {
|
||||
assert(!childPath.starts_with('/'));
|
||||
return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath;
|
||||
}
|
||||
|
||||
std::string AppInstancePaths::getDataPathLvgl(const std::string& childPath) const {
|
||||
assert(!childPath.starts_with('/'));
|
||||
return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath;
|
||||
}
|
||||
|
||||
std::string AppInstancePaths::getSystemDirectory() const {
|
||||
return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.id;
|
||||
}
|
||||
|
||||
std::string AppInstancePaths::getSystemDirectoryLvgl() const {
|
||||
return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.id;
|
||||
}
|
||||
|
||||
std::string AppInstancePaths::getSystemPath(const std::string& childPath) const {
|
||||
assert(!childPath.starts_with('/'));
|
||||
return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath;
|
||||
}
|
||||
|
||||
std::string AppInstancePaths::getSystemPathLvgl(const std::string& childPath) const {
|
||||
return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath;
|
||||
}
|
||||
|
||||
}
|
||||
131
Tactility/Source/app/AppManifestParsing.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
#include <Tactility/app/AppManifestParsing.h>
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
constexpr auto* TAG = "App";
|
||||
|
||||
static bool validateString(const std::string& value, const std::function<bool(const char)>& isValidChar) {
|
||||
for (const auto& c : value) {
|
||||
if (!isValidChar(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool getValueFromManifest(const std::map<std::string, std::string>& map, const std::string& key, std::string& output) {
|
||||
const auto iterator = map.find(key);
|
||||
if (iterator == map.end()) {
|
||||
TT_LOG_E(TAG, "Failed to find %s in manifest", key.c_str());
|
||||
return false;
|
||||
}
|
||||
output = iterator->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isValidId(const std::string& id) {
|
||||
return id.size() >= 5 && validateString(id, [](const char c) {
|
||||
return std::isalnum(c) != 0 || c == '.';
|
||||
});
|
||||
}
|
||||
|
||||
static bool isValidManifestVersion(const std::string& version) {
|
||||
return version.size() > 0 && validateString(version, [](const char c) {
|
||||
return std::isalnum(c) != 0 || c == '.';
|
||||
});
|
||||
}
|
||||
|
||||
static bool isValidAppVersionName(const std::string& version) {
|
||||
return version.size() > 0 && validateString(version, [](const char c) {
|
||||
return std::isalnum(c) != 0 || c == '.' || c == '-' || c == '_';
|
||||
});
|
||||
}
|
||||
|
||||
static bool isValidAppVersionCode(const std::string& version) {
|
||||
return version.size() > 0 && validateString(version, [](const char c) {
|
||||
return std::isdigit(c) != 0;
|
||||
});
|
||||
}
|
||||
|
||||
static bool isValidName(const std::string& name) {
|
||||
return name.size() >= 2 && validateString(name, [](const char c) {
|
||||
return std::isalnum(c) != 0 || c == ' ' || c == '-';
|
||||
});
|
||||
}
|
||||
|
||||
bool parseManifest(const std::map<std::string, std::string>& map, AppManifest& manifest) {
|
||||
TT_LOG_I(TAG, "Parsing manifest");
|
||||
|
||||
// [manifest]
|
||||
|
||||
if (!getValueFromManifest(map, "[manifest]version", manifest.manifestVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidManifestVersion(manifest.manifestVersion)) {
|
||||
TT_LOG_E(TAG, "Invalid version");
|
||||
return false;
|
||||
}
|
||||
|
||||
// [app]
|
||||
|
||||
if (!getValueFromManifest(map, "[app]id", manifest.appId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidId(manifest.appId)) {
|
||||
TT_LOG_E(TAG, "Invalid app id");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!getValueFromManifest(map, "[app]name", manifest.appName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidName(manifest.appName)) {
|
||||
TT_LOG_I(TAG, "Invalid app name");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!getValueFromManifest(map, "[app]versionName", manifest.appVersionName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidAppVersionName(manifest.appVersionName)) {
|
||||
TT_LOG_E(TAG, "Invalid app version name");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string version_code_string;
|
||||
if (!getValueFromManifest(map, "[app]versionCode", version_code_string)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidAppVersionCode(version_code_string)) {
|
||||
TT_LOG_E(TAG, "Invalid app version code");
|
||||
return false;
|
||||
}
|
||||
|
||||
manifest.appVersionCode = std::stoull(version_code_string);
|
||||
|
||||
// [target]
|
||||
|
||||
if (!getValueFromManifest(map, "[target]sdk", manifest.targetSdk)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!getValueFromManifest(map, "[target]platforms", manifest.targetPlatforms)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Defaults
|
||||
|
||||
manifest.appCategory = Category::User;
|
||||
manifest.appLocation = Location::external("");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
44
Tactility/Source/app/AppPaths.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#include <Tactility/app/AppPaths.h>
|
||||
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
#include <Tactility/MountPoints.h>
|
||||
#include <Tactility/file/File.h>
|
||||
|
||||
#include <format>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
constexpr auto PARTITION_PREFIX = std::string("/");
|
||||
#else
|
||||
constexpr auto PARTITION_PREFIX = std::string("");
|
||||
#endif
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
std::string AppPaths::getUserDataPath() const {
|
||||
if (manifest.appLocation.isInternal()) {
|
||||
return std::format("{}{}/user/app/{}", PARTITION_PREFIX, file::DATA_PARTITION_NAME, manifest.appId);
|
||||
} else {
|
||||
return std::format("{}/user/app/{}", file::getFirstPathSegment(manifest.appLocation.getPath()), manifest.appId);
|
||||
}
|
||||
}
|
||||
|
||||
std::string AppPaths::getUserDataPath(const std::string& childPath) const {
|
||||
assert(!childPath.starts_with('/'));
|
||||
return std::format("{}/{}", getUserDataPath(), childPath);
|
||||
}
|
||||
|
||||
|
||||
std::string AppPaths::getAssetsDirectory() const {
|
||||
if (manifest.appLocation.isInternal()) {
|
||||
return std::format("{}{}/app/{}/assets", PARTITION_PREFIX, file::SYSTEM_PARTITION_NAME, manifest.appId);
|
||||
} else {
|
||||
return std::format("{}/assets", manifest.appLocation.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
std::string AppPaths::getAssetsPath(const std::string& childPath) const {
|
||||
assert(!childPath.starts_with('/'));
|
||||
return std::format("{}/{}", getAssetsDirectory(), childPath);
|
||||
}
|
||||
|
||||
}
|
||||
@ -16,15 +16,15 @@ static AppManifestMap app_manifest_map;
|
||||
static Mutex hash_mutex(Mutex::Type::Normal);
|
||||
|
||||
void addApp(const AppManifest& manifest) {
|
||||
TT_LOG_I(TAG, "Registering manifest %s", manifest.id.c_str());
|
||||
TT_LOG_I(TAG, "Registering manifest %s", manifest.appId.c_str());
|
||||
|
||||
hash_mutex.lock();
|
||||
|
||||
if (app_manifest_map.contains(manifest.id)) {
|
||||
TT_LOG_W(TAG, "Overwriting existing manifest for %s", manifest.id.c_str());
|
||||
if (app_manifest_map.contains(manifest.appId)) {
|
||||
TT_LOG_W(TAG, "Overwriting existing manifest for %s", manifest.appId.c_str());
|
||||
}
|
||||
|
||||
app_manifest_map[manifest.id] = std::make_shared<AppManifest>(manifest);
|
||||
app_manifest_map[manifest.appId] = std::make_shared<AppManifest>(manifest);
|
||||
|
||||
hash_mutex.unlock();
|
||||
}
|
||||
|
||||
@ -223,8 +223,8 @@ void setElfAppParameters(
|
||||
std::shared_ptr<App> createElfApp(const std::shared_ptr<AppManifest>& manifest) {
|
||||
TT_LOG_I(TAG, "createElfApp");
|
||||
assert(manifest != nullptr);
|
||||
assert(manifest->location.isExternal());
|
||||
return std::make_shared<ElfApp>(manifest->location.getPath());
|
||||
assert(manifest->appLocation.isExternal());
|
||||
return std::make_shared<ElfApp>(manifest->appLocation.getPath());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -172,16 +172,16 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "AddGps",
|
||||
.name = "Add GPS",
|
||||
.icon = LV_SYMBOL_GPS,
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "AddGps",
|
||||
.appName = "Add GPS",
|
||||
.appIcon = LV_SYMBOL_GPS,
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<AddGpsApp>
|
||||
};
|
||||
|
||||
void start() {
|
||||
app::start(manifest.id);
|
||||
app::start(manifest.appId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -28,7 +28,7 @@ LaunchId start(const std::string& title, const std::string& message, const std::
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title);
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message);
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_BUTTON_LABELS, items_joined);
|
||||
return service::loader::startApp(manifest.id, bundle);
|
||||
return service::loader::startApp(manifest.appId, bundle);
|
||||
}
|
||||
|
||||
LaunchId start(const std::string& title, const std::string& message) {
|
||||
@ -36,7 +36,7 @@ LaunchId start(const std::string& title, const std::string& message) {
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title);
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message);
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_BUTTON_LABELS, "OK");
|
||||
return service::loader::startApp(manifest.id, bundle);
|
||||
return service::loader::startApp(manifest.appId, bundle);
|
||||
}
|
||||
|
||||
int32_t getResultIndex(const Bundle& bundle) {
|
||||
@ -126,10 +126,10 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "AlertDialog",
|
||||
.name = "Alert Dialog",
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "AlertDialog",
|
||||
.appName = "Alert Dialog",
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<AlertDialogApp>
|
||||
};
|
||||
|
||||
|
||||
@ -13,12 +13,12 @@ class AppListApp : public App {
|
||||
|
||||
static void onAppPressed(lv_event_t* e) {
|
||||
const auto* manifest = static_cast<const AppManifest*>(lv_event_get_user_data(e));
|
||||
service::loader::startApp(manifest->id);
|
||||
service::loader::startApp(manifest->appId);
|
||||
}
|
||||
|
||||
static void createAppWidget(const std::shared_ptr<AppManifest>& manifest, lv_obj_t* list) {
|
||||
const void* icon = !manifest->icon.empty() ? manifest->icon.c_str() : TT_ASSETS_APP_ICON_FALLBACK;
|
||||
lv_obj_t* btn = lv_list_add_button(list, icon, manifest->name.c_str());
|
||||
const void* icon = !manifest->appIcon.empty() ? manifest->appIcon.c_str() : TT_ASSETS_APP_ICON_FALLBACK;
|
||||
lv_obj_t* btn = lv_list_add_button(list, icon, manifest->appName.c_str());
|
||||
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, manifest.get());
|
||||
}
|
||||
|
||||
@ -40,8 +40,8 @@ public:
|
||||
std::ranges::sort(manifests, SortAppManifestByName);
|
||||
|
||||
for (const auto& manifest: manifests) {
|
||||
bool is_valid_category = (manifest->category == Category::User) || (manifest->category == Category::System);
|
||||
bool is_visible = (manifest->flags & AppManifest::Flags::Hidden) == 0u;
|
||||
bool is_valid_category = (manifest->appCategory == Category::User) || (manifest->appCategory == Category::System);
|
||||
bool is_visible = (manifest->appFlags & AppManifest::Flags::Hidden) == 0u;
|
||||
if (is_valid_category && is_visible) {
|
||||
createAppWidget(manifest, list);
|
||||
}
|
||||
@ -50,10 +50,10 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "AppList",
|
||||
.name = "Apps",
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "AppList",
|
||||
.appName = "Apps",
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<AppListApp>,
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include <Tactility/TactilityCore.h>
|
||||
#include <Tactility/TactilityPrivate.h>
|
||||
#include <Tactility/app/AppContext.h>
|
||||
#include <Tactility/app/AppPaths.h>
|
||||
#include <Tactility/CpuAffinity.h>
|
||||
#include <Tactility/hal/display/DisplayDevice.h>
|
||||
#include <Tactility/hal/usb/Usb.h>
|
||||
@ -163,21 +164,21 @@ public:
|
||||
const char* logo;
|
||||
// TODO: Replace with automatic asset buckets like on Android
|
||||
if (getSmallestDimension() < 150) { // e.g. Cardputer
|
||||
logo = hal::usb::isUsbBootMode() ? "assets/logo_usb.png" : "assets/logo_small.png";
|
||||
logo = hal::usb::isUsbBootMode() ? "logo_usb.png" : "logo_small.png";
|
||||
} else {
|
||||
logo = hal::usb::isUsbBootMode() ? "assets/logo_usb.png" : "assets/logo.png";
|
||||
logo = hal::usb::isUsbBootMode() ? "logo_usb.png" : "logo.png";
|
||||
}
|
||||
const auto logo_path = paths->getSystemPathLvgl(logo);
|
||||
const auto logo_path = "A:" + paths->getAssetsPath(logo);
|
||||
TT_LOG_I(TAG, "%s", logo_path.c_str());
|
||||
lv_image_set_src(image, logo_path.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Boot",
|
||||
.name = "Boot",
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden,
|
||||
.appId = "Boot",
|
||||
.appName = "Boot",
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden,
|
||||
.createApp = create<BootApp>
|
||||
};
|
||||
|
||||
|
||||
@ -224,9 +224,9 @@ class CalculatorApp : public App {
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Calculator",
|
||||
.name = "Calculator",
|
||||
.icon = TT_ASSETS_APP_ICON_CALCULATOR,
|
||||
.appId = "Calculator",
|
||||
.appName = "Calculator",
|
||||
.appIcon = TT_ASSETS_APP_ICON_CALCULATOR,
|
||||
.createApp = create<CalculatorApp>
|
||||
};
|
||||
|
||||
|
||||
@ -136,9 +136,9 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Chat",
|
||||
.name = "Chat",
|
||||
.icon = TT_ASSETS_APP_ICON_CHAT,
|
||||
.appId = "Chat",
|
||||
.appName = "Chat",
|
||||
.appIcon = TT_ASSETS_APP_ICON_CHAT,
|
||||
.createApp = create<ChatApp>
|
||||
};
|
||||
|
||||
|
||||
@ -122,15 +122,15 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "CrashDiagnostics",
|
||||
.name = "Crash Diagnostics",
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "CrashDiagnostics",
|
||||
.appName = "Crash Diagnostics",
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<CrashDiagnosticsApp>
|
||||
};
|
||||
|
||||
void start() {
|
||||
service::loader::startApp(manifest.id);
|
||||
service::loader::startApp(manifest.appId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -160,14 +160,14 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Development",
|
||||
.name = "Development",
|
||||
.category = Category::Settings,
|
||||
.appId = "Development",
|
||||
.appName = "Development",
|
||||
.appCategory = Category::Settings,
|
||||
.createApp = create<DevelopmentApp>
|
||||
};
|
||||
|
||||
void start() {
|
||||
app::start(manifest.id);
|
||||
app::start(manifest.appId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -164,10 +164,10 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Display",
|
||||
.name = "Display",
|
||||
.icon = TT_ASSETS_APP_ICON_DISPLAY_SETTINGS,
|
||||
.category = Category::Settings,
|
||||
.appId = "Display",
|
||||
.appName = "Display",
|
||||
.appIcon = TT_ASSETS_APP_ICON_DISPLAY_SETTINGS,
|
||||
.appCategory = Category::Settings,
|
||||
.createApp = create<DisplayApp>
|
||||
};
|
||||
|
||||
|
||||
@ -33,16 +33,16 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Files",
|
||||
.name = "Files",
|
||||
.icon = TT_ASSETS_APP_ICON_FILES,
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "Files",
|
||||
.appName = "Files",
|
||||
.appIcon = TT_ASSETS_APP_ICON_FILES,
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<FilesApp>
|
||||
};
|
||||
|
||||
void start() {
|
||||
service::loader::startApp(manifest.id);
|
||||
service::loader::startApp(manifest.appId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -56,24 +56,24 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "FileSelection",
|
||||
.name = "File Selection",
|
||||
.icon = TT_ASSETS_APP_ICON_FILES,
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "FileSelection",
|
||||
.appName = "File Selection",
|
||||
.appIcon = TT_ASSETS_APP_ICON_FILES,
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<FileSelection>
|
||||
};
|
||||
|
||||
LaunchId startForExistingFile() {
|
||||
auto bundle = std::make_shared<Bundle>();
|
||||
setMode(*bundle, Mode::Existing);
|
||||
return service::loader::startApp(manifest.id, bundle);
|
||||
return service::loader::startApp(manifest.appId, bundle);
|
||||
}
|
||||
|
||||
LaunchId startForExistingOrNewFile() {
|
||||
auto bundle = std::make_shared<Bundle>();
|
||||
setMode(*bundle, Mode::ExistingOrNew);
|
||||
return service::loader::startApp(manifest.id, bundle);
|
||||
return service::loader::startApp(manifest.appId, bundle);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -188,10 +188,10 @@ void GpioApp::onHide(AppContext& app) {
|
||||
}
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Gpio",
|
||||
.name = "GPIO",
|
||||
.icon = TT_ASSETS_APP_ICON_GPIO,
|
||||
.category = Category::System,
|
||||
.appId = "Gpio",
|
||||
.appName = "GPIO",
|
||||
.appIcon = TT_ASSETS_APP_ICON_GPIO,
|
||||
.appCategory = Category::System,
|
||||
.createApp = create<GpioApp>
|
||||
};
|
||||
|
||||
|
||||
@ -58,7 +58,7 @@ class GpsSettingsApp final : public App {
|
||||
}
|
||||
|
||||
void onAddGps() {
|
||||
app::start(addgps::manifest.id);
|
||||
app::start(addgps::manifest.appId);
|
||||
}
|
||||
|
||||
void startReceivingUpdates() {
|
||||
@ -344,15 +344,15 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "GpsSettings",
|
||||
.name = "GPS",
|
||||
.icon = LV_SYMBOL_GPS,
|
||||
.category = Category::Settings,
|
||||
.appId = "GpsSettings",
|
||||
.appName = "GPS",
|
||||
.appIcon = LV_SYMBOL_GPS,
|
||||
.appCategory = Category::Settings,
|
||||
.createApp = create<GpsSettingsApp>
|
||||
};
|
||||
|
||||
void start() {
|
||||
app::start(manifest.id);
|
||||
app::start(manifest.appId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -69,7 +69,7 @@ public:
|
||||
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
|
||||
std::shared_ptr<I2cScannerApp> _Nullable optApp() {
|
||||
auto appContext = getCurrentAppContext();
|
||||
if (appContext != nullptr && appContext->getManifest().id == manifest.id) {
|
||||
if (appContext != nullptr && appContext->getManifest().appId == manifest.appId) {
|
||||
return std::static_pointer_cast<I2cScannerApp>(appContext->getApp());
|
||||
} else {
|
||||
return nullptr;
|
||||
@ -403,15 +403,15 @@ void I2cScannerApp::onScanTimerFinished() {
|
||||
}
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "I2cScanner",
|
||||
.name = "I2C Scanner",
|
||||
.icon = TT_ASSETS_APP_ICON_I2C_SETTINGS,
|
||||
.category = Category::System,
|
||||
.appId = "I2cScanner",
|
||||
.appName = "I2C Scanner",
|
||||
.appIcon = TT_ASSETS_APP_ICON_I2C_SETTINGS,
|
||||
.appCategory = Category::System,
|
||||
.createApp = create<I2cScannerApp>
|
||||
};
|
||||
|
||||
void start() {
|
||||
service::loader::startApp(manifest.id);
|
||||
service::loader::startApp(manifest.appId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -93,10 +93,10 @@ class I2cSettingsApp : public App {
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "I2cSettings",
|
||||
.name = "I2C",
|
||||
.icon = TT_ASSETS_APP_ICON_I2C_SETTINGS,
|
||||
.category = Category::Settings,
|
||||
.appId = "I2cSettings",
|
||||
.appName = "I2C",
|
||||
.appIcon = TT_ASSETS_APP_ICON_I2C_SETTINGS,
|
||||
.appCategory = Category::Settings,
|
||||
.createApp = create<I2cSettingsApp>
|
||||
};
|
||||
|
||||
|
||||
@ -60,17 +60,17 @@ class ImageViewerApp : public App {
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "ImageViewer",
|
||||
.name = "Image Viewer",
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "ImageViewer",
|
||||
.appName = "Image Viewer",
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<ImageViewerApp>
|
||||
};
|
||||
|
||||
void start(const std::string& file) {
|
||||
auto parameters = std::make_shared<Bundle>();
|
||||
parameters->putString(IMAGE_VIEWER_FILE_ARGUMENT, file);
|
||||
service::loader::startApp(manifest.id, parameters);
|
||||
service::loader::startApp(manifest.appId, parameters);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -26,7 +26,7 @@ void start(const std::string& title, const std::string& message, const std::stri
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title);
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message);
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_PREFILLED, prefilled);
|
||||
service::loader::startApp(manifest.id, bundle);
|
||||
service::loader::startApp(manifest.appId, bundle);
|
||||
}
|
||||
|
||||
std::string getResult(const Bundle& bundle) {
|
||||
@ -118,10 +118,10 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "InputDialog",
|
||||
.name = "Input Dialog",
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "InputDialog",
|
||||
.appName = "Input Dialog",
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<InputDialogApp>
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include <Tactility/Tactility.h>
|
||||
|
||||
#include <Tactility/app/AppContext.h>
|
||||
#include <Tactility/app/AppPaths.h>
|
||||
#include <Tactility/app/AppRegistration.h>
|
||||
#include <Tactility/hal/power/PowerDevice.h>
|
||||
#include <Tactility/service/loader/Loader.h>
|
||||
@ -112,9 +113,9 @@ public:
|
||||
const int32_t margin = is_landscape_display ? std::min<int32_t>(available_width / 16, button_size) : 0;
|
||||
|
||||
const auto paths = app.getPaths();
|
||||
const auto apps_icon_path = paths->getSystemPathLvgl("assets/icon_apps.png");
|
||||
const auto files_icon_path = paths->getSystemPathLvgl("assets/icon_files.png");
|
||||
const auto settings_icon_path = paths->getSystemPathLvgl("assets/icon_settings.png");
|
||||
const auto apps_icon_path = "A:" + paths->getAssetsPath("icon_apps.png");
|
||||
const auto files_icon_path = "A:" + paths->getAssetsPath("icon_files.png");
|
||||
const auto settings_icon_path = "A:" + paths->getAssetsPath("icon_settings.png");
|
||||
|
||||
createAppButton(buttons_wrapper, ui_scale, apps_icon_path.c_str(), "AppList", margin);
|
||||
createAppButton(buttons_wrapper, ui_scale, files_icon_path.c_str(), "Files", margin);
|
||||
@ -136,14 +137,15 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Launcher",
|
||||
.name = "Launcher",
|
||||
.category = Category::System,
|
||||
.appId = "Launcher",
|
||||
.appName = "Launcher",
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<LauncherApp>
|
||||
};
|
||||
|
||||
void start() {
|
||||
service::loader::startApp(manifest.id);
|
||||
service::loader::startApp(manifest.appId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -162,15 +162,15 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "LocaleSettings",
|
||||
.name = "Region & Language",
|
||||
.icon = TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS,
|
||||
.category = Category::Settings,
|
||||
.appId = "LocaleSettings",
|
||||
.appName = "Region & Language",
|
||||
.appIcon = TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS,
|
||||
.appCategory = Category::Settings,
|
||||
.createApp = create<LocaleSettingsApp>
|
||||
};
|
||||
|
||||
void start() {
|
||||
service::loader::startApp(manifest.id);
|
||||
service::loader::startApp(manifest.appId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -120,10 +120,10 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Log",
|
||||
.name = "Log",
|
||||
.icon = LV_SYMBOL_LIST,
|
||||
.category = Category::System,
|
||||
.appId = "Log",
|
||||
.appName = "Log",
|
||||
.appIcon = LV_SYMBOL_LIST,
|
||||
.appCategory = Category::System,
|
||||
.createApp = create<LogApp>
|
||||
};
|
||||
|
||||
|
||||
@ -207,15 +207,15 @@ class NotesApp : public App {
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Notes",
|
||||
.name = "Notes",
|
||||
.icon = TT_ASSETS_APP_ICON_NOTES,
|
||||
.appId = "Notes",
|
||||
.appName = "Notes",
|
||||
.appIcon = TT_ASSETS_APP_ICON_NOTES,
|
||||
.createApp = create<NotesApp>
|
||||
};
|
||||
|
||||
void start(const std::string& filePath) {
|
||||
auto parameters = std::make_shared<Bundle>();
|
||||
parameters->putString(NOTES_FILE_ARGUMENT, filePath);
|
||||
service::loader::startApp(manifest.id, parameters);
|
||||
service::loader::startApp(manifest.appId, parameters);
|
||||
}
|
||||
} // namespace tt::app::notes
|
||||
@ -22,7 +22,7 @@ class PowerApp;
|
||||
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
|
||||
std::shared_ptr<PowerApp> _Nullable optApp() {
|
||||
auto appContext = getCurrentAppContext();
|
||||
if (appContext != nullptr && appContext->getManifest().id == manifest.id) {
|
||||
if (appContext != nullptr && appContext->getManifest().appId == manifest.appId) {
|
||||
return std::static_pointer_cast<PowerApp>(appContext->getApp());
|
||||
} else {
|
||||
return nullptr;
|
||||
@ -189,10 +189,10 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Power",
|
||||
.name = "Power",
|
||||
.icon = TT_ASSETS_APP_ICON_POWER_SETTINGS,
|
||||
.category = Category::Settings,
|
||||
.appId = "Power",
|
||||
.appName = "Power",
|
||||
.appIcon = TT_ASSETS_APP_ICON_POWER_SETTINGS,
|
||||
.appCategory = Category::Settings,
|
||||
.createApp = create<PowerApp>
|
||||
};
|
||||
|
||||
|
||||
@ -48,7 +48,7 @@ public:
|
||||
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
|
||||
std::shared_ptr<ScreenshotApp> _Nullable optApp() {
|
||||
auto appContext = getCurrentAppContext();
|
||||
if (appContext != nullptr && appContext->getManifest().id == manifest.id) {
|
||||
if (appContext != nullptr && appContext->getManifest().appId == manifest.appId) {
|
||||
return std::static_pointer_cast<ScreenshotApp>(appContext->getApp());
|
||||
} else {
|
||||
return nullptr;
|
||||
@ -278,10 +278,10 @@ void ScreenshotApp::onShow(AppContext& appContext, lv_obj_t* parent) {
|
||||
}
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Screenshot",
|
||||
.name = "Screenshot",
|
||||
.icon = LV_SYMBOL_IMAGE,
|
||||
.category = Category::System,
|
||||
.appId = "Screenshot",
|
||||
.appName = "Screenshot",
|
||||
.appIcon = LV_SYMBOL_IMAGE,
|
||||
.appCategory = Category::System,
|
||||
.createApp = create<ScreenshotApp>
|
||||
};
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ void start(const std::string& title, const std::vector<std::string>& items) {
|
||||
auto bundle = std::make_shared<Bundle>();
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title);
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_ITEMS, items_joined);
|
||||
service::loader::startApp(manifest.id, bundle);
|
||||
service::loader::startApp(manifest.appId, bundle);
|
||||
}
|
||||
|
||||
int32_t getResultIndex(const Bundle& bundle) {
|
||||
@ -112,10 +112,10 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "SelectionDialog",
|
||||
.name = "Selection Dialog",
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "SelectionDialog",
|
||||
.appName = "Selection Dialog",
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<SelectionDialogApp>
|
||||
};
|
||||
|
||||
|
||||
@ -85,10 +85,10 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "SerialConsole",
|
||||
.name = "Serial Console",
|
||||
.icon = LV_SYMBOL_LIST,
|
||||
.category = Category::System,
|
||||
.appId = "SerialConsole",
|
||||
.appName = "Serial Console",
|
||||
.appIcon = LV_SYMBOL_LIST,
|
||||
.appCategory = Category::System,
|
||||
.createApp = create<SerialConsoleApp>
|
||||
};
|
||||
|
||||
|
||||
@ -12,14 +12,14 @@ namespace tt::app::settings {
|
||||
|
||||
static void onAppPressed(lv_event_t* e) {
|
||||
const auto* manifest = static_cast<const AppManifest*>(lv_event_get_user_data(e));
|
||||
service::loader::startApp(manifest->id);
|
||||
service::loader::startApp(manifest->appId);
|
||||
}
|
||||
|
||||
static void createWidget(const std::shared_ptr<AppManifest>& manifest, void* parent) {
|
||||
tt_check(parent);
|
||||
auto* list = (lv_obj_t*)parent;
|
||||
const void* icon = !manifest->icon.empty() ? manifest->icon.c_str() : TT_ASSETS_APP_ICON_FALLBACK;
|
||||
auto* btn = lv_list_add_button(list, icon, manifest->name.c_str());
|
||||
const void* icon = !manifest->appIcon.empty() ? manifest->appIcon.c_str() : TT_ASSETS_APP_ICON_FALLBACK;
|
||||
auto* btn = lv_list_add_button(list, icon, manifest->appName.c_str());
|
||||
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)manifest.get());
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ class SettingsApp : public App {
|
||||
auto manifests = getApps();
|
||||
std::sort(manifests.begin(), manifests.end(), SortAppManifestByName);
|
||||
for (const auto& manifest: manifests) {
|
||||
if (manifest->category == Category::Settings) {
|
||||
if (manifest->appCategory == Category::Settings) {
|
||||
createWidget(manifest, list);
|
||||
}
|
||||
}
|
||||
@ -46,11 +46,11 @@ class SettingsApp : public App {
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "Settings",
|
||||
.name = "Settings",
|
||||
.icon = TT_ASSETS_APP_ICON_SETTINGS,
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "Settings",
|
||||
.appName = "Settings",
|
||||
.appIcon = TT_ASSETS_APP_ICON_SETTINGS,
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<SettingsApp>
|
||||
};
|
||||
|
||||
|
||||
@ -292,10 +292,10 @@ class SystemInfoApp final : public App {
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "SystemInfo",
|
||||
.name = "System Info",
|
||||
.icon = TT_ASSETS_APP_ICON_SYSTEM_INFO,
|
||||
.category = Category::System,
|
||||
.appId = "SystemInfo",
|
||||
.appName = "System Info",
|
||||
.appIcon = TT_ASSETS_APP_ICON_SYSTEM_INFO,
|
||||
.appCategory = Category::System,
|
||||
.createApp = create<SystemInfoApp>
|
||||
};
|
||||
|
||||
|
||||
@ -57,15 +57,15 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "TimeDateSettings",
|
||||
.name = "Time & Date",
|
||||
.icon = TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS,
|
||||
.category = Category::Settings,
|
||||
.appId = "TimeDateSettings",
|
||||
.appName = "Time & Date",
|
||||
.appIcon = TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS,
|
||||
.appCategory = Category::Settings,
|
||||
.createApp = create<TimeDateSettingsApp>
|
||||
};
|
||||
|
||||
void start() {
|
||||
service::loader::startApp(manifest.id);
|
||||
service::loader::startApp(manifest.appId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include <Tactility/app/AppContext.h>
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
#include <Tactility/app/AppPaths.h>
|
||||
#include <Tactility/app/timezone/TimeZone.h>
|
||||
#include <Tactility/lvgl/Toolbar.h>
|
||||
#include <Tactility/lvgl/LvglSync.h>
|
||||
@ -200,7 +201,7 @@ public:
|
||||
lv_obj_set_style_image_recolor_opa(icon, 255, 0);
|
||||
lv_obj_set_style_image_recolor(icon, lv_theme_get_color_primary(parent), 0);
|
||||
|
||||
std::string icon_path = app.getPaths()->getSystemPathLvgl("search.png");
|
||||
std::string icon_path = "A:" + app.getPaths()->getAssetsPath("search.png");
|
||||
lv_image_set_src(icon, icon_path.c_str());
|
||||
lv_obj_set_style_image_recolor(icon, lv_theme_get_color_primary(parent), 0);
|
||||
|
||||
@ -226,15 +227,15 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "TimeZone",
|
||||
.name = "Select timezone",
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "TimeZone",
|
||||
.appName = "Select timezone",
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<TimeZoneApp>
|
||||
};
|
||||
|
||||
void start() {
|
||||
service::loader::startApp(manifest.id);
|
||||
service::loader::startApp(manifest.appId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -42,10 +42,10 @@ class UsbSettingsApp : public App {
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "UsbSettings",
|
||||
.name = "USB",
|
||||
.icon = LV_SYMBOL_USB,
|
||||
.category = Category::Settings,
|
||||
.appId = "UsbSettings",
|
||||
.appName = "USB",
|
||||
.appIcon = LV_SYMBOL_USB,
|
||||
.appCategory = Category::Settings,
|
||||
.createApp = create<UsbSettingsApp>
|
||||
};
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ extern const AppManifest manifest;
|
||||
void start(const std::string& ssid) {
|
||||
auto bundle = std::make_shared<Bundle>();
|
||||
bundle->putString("ssid", ssid);
|
||||
app::start(manifest.id, bundle);
|
||||
app::start(manifest.appId, bundle);
|
||||
}
|
||||
|
||||
class WifiApSettings : public App {
|
||||
@ -241,11 +241,11 @@ public:
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "WifiApSettings",
|
||||
.name = "Wi-Fi AP Settings",
|
||||
.icon = LV_SYMBOL_WIFI,
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "WifiApSettings",
|
||||
.appName = "Wi-Fi AP Settings",
|
||||
.appIcon = LV_SYMBOL_WIFI,
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<WifiApSettings>
|
||||
};
|
||||
|
||||
|
||||
@ -94,11 +94,11 @@ void WifiConnect::onHide(TT_UNUSED AppContext& app) {
|
||||
}
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "WifiConnect",
|
||||
.name = "Wi-Fi Connect",
|
||||
.icon = LV_SYMBOL_WIFI,
|
||||
.category = Category::System,
|
||||
.flags = AppManifest::Flags::Hidden,
|
||||
.appId = "WifiConnect",
|
||||
.appName = "Wi-Fi Connect",
|
||||
.appIcon = LV_SYMBOL_WIFI,
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<WifiConnect>
|
||||
};
|
||||
|
||||
@ -106,7 +106,7 @@ void start(const std::string& ssid, const std::string& password) {
|
||||
auto parameters = std::make_shared<Bundle>();
|
||||
parameters->putString(WIFI_CONNECT_PARAM_SSID, ssid);
|
||||
parameters->putString(WIFI_CONNECT_PARAM_PASSWORD, password);
|
||||
service::loader::startApp(manifest.id, parameters);
|
||||
service::loader::startApp(manifest.appId, parameters);
|
||||
}
|
||||
|
||||
bool optSsidParameter(const std::shared_ptr<const Bundle>& bundle, std::string& ssid) {
|
||||
|
||||
@ -133,15 +133,15 @@ void WifiManage::onHide(TT_UNUSED AppContext& app) {
|
||||
}
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "WifiManage",
|
||||
.name = "Wi-Fi",
|
||||
.icon = LV_SYMBOL_WIFI,
|
||||
.category = Category::Settings,
|
||||
.appId = "WifiManage",
|
||||
.appName = "Wi-Fi",
|
||||
.appIcon = LV_SYMBOL_WIFI,
|
||||
.appCategory = Category::Settings,
|
||||
.createApp = create<WifiManage>
|
||||
};
|
||||
|
||||
void start() {
|
||||
service::loader::startApp(manifest.id);
|
||||
service::loader::startApp(manifest.appId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#include <Tactility/hal/sdcard/SdCardMounting.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
|
||||
#include <format>
|
||||
|
||||
namespace tt::hal::sdcard {
|
||||
|
||||
constexpr auto* TAG = "SdCardMounting";
|
||||
@ -15,24 +17,18 @@ static void mount(const std::shared_ptr<SdCardDevice>& sdcard, const std::string
|
||||
});
|
||||
}
|
||||
|
||||
static std::string getMountPath(int index, int count) {
|
||||
return (count == 1) ? TT_SDCARD_MOUNT_POINT : std::format("{}{}", TT_SDCARD_MOUNT_POINT, index);
|
||||
}
|
||||
|
||||
void mountAll() {
|
||||
auto sdcards = hal::findDevices<SdCardDevice>(Device::Type::SdCard);
|
||||
if (!sdcards.empty()) {
|
||||
if (sdcards.size() == 1) {
|
||||
// Fixed mount path name
|
||||
auto sdcard = sdcards[0];
|
||||
if (!sdcard->isMounted()) {
|
||||
mount(sdcard, TT_SDCARD_MOUNT_POINT);
|
||||
}
|
||||
} else {
|
||||
// Numbered mount path name
|
||||
for (int i = 0; i < sdcards.size(); i++) {
|
||||
auto sdcard = sdcards[i];
|
||||
if (!sdcard->isMounted()) {
|
||||
std::string mount_path = TT_SDCARD_MOUNT_POINT + std::to_string(i);
|
||||
mount(sdcard, mount_path);
|
||||
}
|
||||
}
|
||||
const auto sdcards = hal::findDevices<SdCardDevice>(Device::Type::SdCard);
|
||||
// Numbered mount path name
|
||||
for (int i = 0; i < sdcards.size(); i++) {
|
||||
auto sdcard = sdcards[i];
|
||||
if (!sdcard->isMounted() && sdcard->getMountBehaviour() == SdCardDevice::MountBehaviour::AtBoot) {
|
||||
std::string mount_path = getMountPath(i, sdcards.size());
|
||||
mount(sdcard, mount_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ lv_obj_t* toolbar_create(lv_obj_t* parent, const std::string& title) {
|
||||
}
|
||||
|
||||
lv_obj_t* toolbar_create(lv_obj_t* parent, const app::AppContext& app) {
|
||||
return toolbar_create(parent, app.getManifest().name);
|
||||
return toolbar_create(parent, app.getManifest().appName);
|
||||
}
|
||||
|
||||
void toolbar_set_title(lv_obj_t* obj, const std::string& title) {
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
#include "Tactility/service/ServiceInstance.h"
|
||||
#include "Tactility/service/ServiceInstancePaths.h"
|
||||
#include <Tactility/service/ServiceInstance.h>
|
||||
|
||||
#include <Tactility/service/ServiceManifest.h>
|
||||
#include <Tactility/service/ServicePaths.h>
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
ServiceInstance::ServiceInstance(std::shared_ptr<const service::ServiceManifest> manifest) :
|
||||
ServiceInstance::ServiceInstance(std::shared_ptr<const ServiceManifest> manifest) :
|
||||
manifest(manifest),
|
||||
service(manifest->createService())
|
||||
{}
|
||||
|
||||
const service::ServiceManifest& ServiceInstance::getManifest() const { return *manifest; }
|
||||
const ServiceManifest& ServiceInstance::getManifest() const { return *manifest; }
|
||||
|
||||
std::unique_ptr<Paths> ServiceInstance::getPaths() const {
|
||||
return std::make_unique<ServiceInstancePaths>(manifest);
|
||||
std::unique_ptr<ServicePaths> ServiceInstance::getPaths() const {
|
||||
return std::make_unique<ServicePaths>(manifest);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
#include "Tactility/service/ServiceInstancePaths.h"
|
||||
|
||||
#include "Tactility/MountPoints.h"
|
||||
|
||||
#define LVGL_PATH_PREFIX std::string("A:/")
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#define PARTITION_PREFIX std::string("/")
|
||||
#else
|
||||
#define PARTITION_PREFIX std::string("")
|
||||
#endif
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
std::string ServiceInstancePaths::getDataDirectory() const {
|
||||
return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/service/" + manifest->id;
|
||||
}
|
||||
|
||||
std::string ServiceInstancePaths::getDataDirectoryLvgl() const {
|
||||
return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/service/" + manifest->id;
|
||||
}
|
||||
|
||||
std::string ServiceInstancePaths::getDataPath(const std::string& childPath) const {
|
||||
assert(!childPath.starts_with('/'));
|
||||
return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath;
|
||||
}
|
||||
|
||||
std::string ServiceInstancePaths::getDataPathLvgl(const std::string& childPath) const {
|
||||
assert(!childPath.starts_with('/'));
|
||||
return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath;
|
||||
}
|
||||
|
||||
std::string ServiceInstancePaths::getSystemDirectory() const {
|
||||
return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/service/" + manifest->id;
|
||||
}
|
||||
|
||||
std::string ServiceInstancePaths::getSystemDirectoryLvgl() const {
|
||||
return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/service/" + manifest->id;
|
||||
}
|
||||
|
||||
std::string ServiceInstancePaths::getSystemPath(const std::string& childPath) const {
|
||||
assert(!childPath.starts_with('/'));
|
||||
return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath;
|
||||
}
|
||||
|
||||
std::string ServiceInstancePaths::getSystemPathLvgl(const std::string& childPath) const {
|
||||
return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath;
|
||||
}
|
||||
|
||||
}
|
||||
35
Tactility/Source/service/ServicePaths.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
#include <Tactility/service/ServicePaths.h>
|
||||
|
||||
#include <Tactility/service/ServiceManifest.h>
|
||||
#include <Tactility/MountPoints.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <format>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
constexpr auto PARTITION_PREFIX = std::string("/");
|
||||
#else
|
||||
constexpr auto PARTITION_PREFIX = std::string("");
|
||||
#endif
|
||||
|
||||
namespace tt::service {
|
||||
|
||||
std::string ServicePaths::getUserDataDirectory() const {
|
||||
return std::format("{}{}/service/{}", PARTITION_PREFIX, file::DATA_PARTITION_NAME, manifest->id);
|
||||
}
|
||||
|
||||
std::string ServicePaths::getUserDataPath(const std::string& childPath) const {
|
||||
assert(!childPath.starts_with('/'));
|
||||
return std::format("{}/{}", getUserDataDirectory(), childPath);
|
||||
}
|
||||
|
||||
std::string ServicePaths::getAssetsDirectory() const {
|
||||
return std::format("{}{}/service/{}/assets", PARTITION_PREFIX, file::SYSTEM_PARTITION_NAME, manifest->id);
|
||||
}
|
||||
|
||||
std::string ServicePaths::getAssetsPath(const std::string& childPath) const {
|
||||
assert(!childPath.starts_with('/'));
|
||||
return std::format("{}/{}", getAssetsDirectory(), childPath);
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,10 +4,10 @@
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
#include <Tactility/app/AppRegistration.h>
|
||||
#include <Tactility/app/ElfApp.h>
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/network/HttpdReq.h>
|
||||
#include <Tactility/network/Url.h>
|
||||
#include <Tactility/Paths.h>
|
||||
#include <Tactility/service/development/DevelopmentSettings.h>
|
||||
#include <Tactility/service/ServiceManifest.h>
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
@ -247,7 +247,7 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
|
||||
}
|
||||
content_left -= content_read;
|
||||
|
||||
const std::string tmp_path = app::getTempPath();
|
||||
const std::string tmp_path = getTempPath();
|
||||
auto lock = file::getLock(tmp_path)->asScopedLock();
|
||||
|
||||
lock.lock();
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#include "Tactility/service/gps/GpsService.h"
|
||||
|
||||
#include "Tactility/file/ObjectFile.h"
|
||||
#include <Tactility/file/ObjectFile.h>
|
||||
#include <Tactility/service/gps/GpsService.h>
|
||||
#include <Tactility/service/ServicePaths.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
@ -17,12 +17,12 @@ bool GpsService::getConfigurationFilePath(std::string& output) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file::findOrCreateDirectory(paths->getDataDirectory(), 0777)) {
|
||||
TT_LOG_E(TAG, "Failed to find or create path %s", paths->getDataDirectory().c_str());
|
||||
if (!file::findOrCreateDirectory(paths->getUserDataDirectory(), 0777)) {
|
||||
TT_LOG_E(TAG, "Failed to find or create path %s", paths->getUserDataDirectory().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
output = paths->getDataPath("config.bin");
|
||||
output = paths->getUserDataPath("config.bin");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
#include "Tactility/service/gps/GpsService.h"
|
||||
#include "Tactility/service/ServiceManifest.h"
|
||||
#include "Tactility/service/ServiceRegistration.h"
|
||||
#include <Tactility/service/gps/GpsService.h>
|
||||
|
||||
#include <Tactility/Log.h>
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/Log.h>
|
||||
#include <Tactility/service/ServicePaths.h>
|
||||
#include <Tactility/service/ServiceManifest.h>
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
|
||||
using tt::hal::gps::GpsDevice;
|
||||
|
||||
|
||||
@ -193,7 +193,7 @@ void GuiService::requestDraw() {
|
||||
void GuiService::showApp(std::shared_ptr<app::AppContext> app) {
|
||||
lock();
|
||||
if (!isStarted) {
|
||||
TT_LOG_W(TAG, "Failed to show app %s: GUI not started", app->getManifest().id.c_str());
|
||||
TT_LOG_W(TAG, "Failed to show app %s: GUI not started", app->getManifest().appId.c_str());
|
||||
} else {
|
||||
// Ensure previous app triggers onHide() logic
|
||||
if (appToRender != nullptr) {
|
||||
|
||||
@ -103,7 +103,7 @@ void LoaderService::onStartAppMessage(const std::string& id, app::LaunchId launc
|
||||
auto previous_app = !appStack.empty() ? appStack.top() : nullptr;
|
||||
auto new_app = std::make_shared<app::AppInstance>(app_manifest, launchId, parameters);
|
||||
|
||||
new_app->mutableFlags().hideStatusbar = (app_manifest->flags & app::AppManifest::Flags::HideStatusBar);
|
||||
new_app->mutableFlags().hideStatusbar = (app_manifest->appFlags & app::AppManifest::Flags::HideStatusBar);
|
||||
|
||||
appStack.push(new_app);
|
||||
transitionAppToState(new_app, app::State::Initial);
|
||||
@ -136,12 +136,12 @@ void LoaderService::onStopAppMessage(const std::string& id) {
|
||||
// Stop current app
|
||||
auto app_to_stop = appStack.top();
|
||||
|
||||
if (app_to_stop->getManifest().id != id) {
|
||||
TT_LOG_E(TAG, "Stop app: id mismatch (wanted %s but found %s on top of stack)", id.c_str(), app_to_stop->getManifest().id.c_str());
|
||||
if (app_to_stop->getManifest().appId != id) {
|
||||
TT_LOG_E(TAG, "Stop app: id mismatch (wanted %s but found %s on top of stack)", id.c_str(), app_to_stop->getManifest().appId.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (original_stack_size == 1 && app_to_stop->getManifest().name != "Boot") {
|
||||
if (original_stack_size == 1 && app_to_stop->getManifest().appName != "Boot") {
|
||||
TT_LOG_E(TAG, "Stop app: can't stop root app");
|
||||
return;
|
||||
}
|
||||
@ -162,12 +162,12 @@ void LoaderService::onStopAppMessage(const std::string& id) {
|
||||
|
||||
// We only expect the app to be referenced within the current scope
|
||||
if (app_to_stop.use_count() > 1) {
|
||||
TT_LOG_W(TAG, "Memory leak: Stopped %s, but use count is %ld", app_to_stop->getManifest().id.c_str(), app_to_stop.use_count() - 1);
|
||||
TT_LOG_W(TAG, "Memory leak: Stopped %s, but use count is %ld", app_to_stop->getManifest().appId.c_str(), app_to_stop.use_count() - 1);
|
||||
}
|
||||
|
||||
// Refcount is expected to be 2: 1 within app_to_stop and 1 within the current scope
|
||||
if (app_to_stop->getApp().use_count() > 2) {
|
||||
TT_LOG_W(TAG, "Memory leak: Stopped %s, but use count is %ld", app_to_stop->getManifest().id.c_str(), app_to_stop->getApp().use_count() - 2);
|
||||
TT_LOG_W(TAG, "Memory leak: Stopped %s, but use count is %ld", app_to_stop->getManifest().appId.c_str(), app_to_stop->getApp().use_count() - 2);
|
||||
}
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
@ -224,7 +224,7 @@ void LoaderService::transitionAppToState(const std::shared_ptr<app::AppInstance>
|
||||
TT_LOG_I(
|
||||
TAG,
|
||||
"App \"%s\" state: %s -> %s",
|
||||
app_manifest.id.c_str(),
|
||||
app_manifest.appId.c_str(),
|
||||
appStateToString(old_state),
|
||||
appStateToString(state)
|
||||
);
|
||||
@ -263,7 +263,7 @@ app::LaunchId LoaderService::startApp(const std::string& id, std::shared_ptr<con
|
||||
|
||||
void LoaderService::stopApp() {
|
||||
TT_LOG_I(TAG, "stopApp()");
|
||||
auto id = getCurrentAppContext()->getManifest().id;
|
||||
auto id = getCurrentAppContext()->getManifest().appId;
|
||||
dispatcherThread->dispatch([this, id]() {
|
||||
onStopAppMessage(id);
|
||||
});
|
||||
|
||||
@ -84,10 +84,10 @@ void ScreenshotTask::taskMain() {
|
||||
auto appContext = app::getCurrentAppContext();
|
||||
if (appContext != nullptr) {
|
||||
const app::AppManifest& manifest = appContext->getManifest();
|
||||
if (manifest.id != last_app_id) {
|
||||
if (manifest.appId != last_app_id) {
|
||||
kernel::delayMillis(100);
|
||||
last_app_id = manifest.id;
|
||||
auto filename = std::format("{}/screenshot-{}.png", work.path, manifest.id);
|
||||
last_app_id = manifest.appId;
|
||||
auto filename = std::format("{}/screenshot-{}.png", work.path, manifest.appId);
|
||||
makeScreenshot(filename);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
#include <Tactility/lvgl/Statusbar.h>
|
||||
#include <Tactility/lvgl/LvglSync.h>
|
||||
|
||||
#include <Tactility/hal/power/PowerDevice.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
#include <Tactility/lvgl/Lvgl.h>
|
||||
#include <Tactility/service/gps/GpsService.h>
|
||||
#include <Tactility/lvgl/LvglSync.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/Timer.h>
|
||||
#include <Tactility/service/gps/GpsService.h>
|
||||
#include <Tactility/service/ServiceContext.h>
|
||||
#include <Tactility/service/ServicePaths.h>
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
#include <Tactility/service/wifi/Wifi.h>
|
||||
#include <Tactility/Timer.h>
|
||||
|
||||
namespace tt::service::statusbar {
|
||||
|
||||
@ -148,7 +148,7 @@ class StatusbarService final : public Service {
|
||||
int8_t power_icon_id;
|
||||
const char* power_last_icon = nullptr;
|
||||
|
||||
std::unique_ptr<Paths> paths;
|
||||
std::unique_ptr<ServicePaths> paths;
|
||||
|
||||
void lock() const {
|
||||
mutex.lock();
|
||||
@ -163,7 +163,7 @@ class StatusbarService final : public Service {
|
||||
bool show_icon = (gps_state == gps::State::OnPending) || (gps_state == gps::State::On);
|
||||
if (gps_last_state != show_icon) {
|
||||
if (show_icon) {
|
||||
auto icon_path = paths->getSystemPathLvgl(STATUSBAR_ICON_GPS);
|
||||
auto icon_path = "A:" + paths->getAssetsPath(STATUSBAR_ICON_GPS);
|
||||
lvgl::statusbar_icon_set_image(gps_icon_id, icon_path);
|
||||
lvgl::statusbar_icon_set_visibility(gps_icon_id, true);
|
||||
} else {
|
||||
@ -179,7 +179,7 @@ class StatusbarService final : public Service {
|
||||
const char* desired_icon = getWifiStatusIcon(radio_state, is_secure);
|
||||
if (wifi_last_icon != desired_icon) {
|
||||
if (desired_icon != nullptr) {
|
||||
auto icon_path = paths->getSystemPathLvgl(desired_icon);
|
||||
auto icon_path = "A:" + paths->getAssetsPath(desired_icon);
|
||||
lvgl::statusbar_icon_set_image(wifi_icon_id, icon_path);
|
||||
lvgl::statusbar_icon_set_visibility(wifi_icon_id, true);
|
||||
} else {
|
||||
@ -193,7 +193,7 @@ class StatusbarService final : public Service {
|
||||
const char* desired_icon = getPowerStatusIcon();
|
||||
if (power_last_icon != desired_icon) {
|
||||
if (desired_icon != nullptr) {
|
||||
auto icon_path = paths->getSystemPathLvgl(desired_icon);
|
||||
auto icon_path = "A:" + paths->getAssetsPath(desired_icon);
|
||||
lvgl::statusbar_icon_set_image(power_icon_id, icon_path);
|
||||
lvgl::statusbar_icon_set_visibility(power_icon_id, true);
|
||||
} else {
|
||||
@ -212,7 +212,7 @@ class StatusbarService final : public Service {
|
||||
if (state != hal::sdcard::SdCardDevice::State::Timeout) {
|
||||
auto* desired_icon = getSdCardStatusIcon(state);
|
||||
if (sdcard_last_icon != desired_icon) {
|
||||
auto icon_path = paths->getSystemPathLvgl(desired_icon);
|
||||
auto icon_path = "A:" + paths->getAssetsPath(desired_icon);
|
||||
lvgl::statusbar_icon_set_image(sdcard_icon_id, icon_path);
|
||||
lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true);
|
||||
sdcard_last_icon = desired_icon;
|
||||
|
||||