Fixes and improvements (#616)
This commit is contained in:
committed by
GitHub
parent
f943c4dd69
commit
b03759a111
@@ -13,6 +13,7 @@ extern "C" {
|
||||
#define APP_METADATA_APP_ID_LENGTH 32
|
||||
#define APP_METADATA_APP_NAME_LENGTH 32
|
||||
#define APP_METADATA_APP_VERSION_NAME_LENGTH 16
|
||||
#define APP_METADATA_REQUIRES_DEVICE_ID_LENGTH 64
|
||||
|
||||
struct AppMetadata {
|
||||
|
||||
@@ -42,6 +43,13 @@ struct AppMetadata {
|
||||
|
||||
/** The technical version (must be incremented with new releases of the app) */
|
||||
uint64_t app_version_code;
|
||||
|
||||
/**
|
||||
* Comma-separated list of device ids the app is restricted to (e.g. "m5stack-tab5"), matching
|
||||
* the folder names under Devices/. Empty means unrestricted.
|
||||
* Must be NULL-terminated.
|
||||
*/
|
||||
char requires_device_id[APP_METADATA_REQUIRES_DEVICE_ID_LENGTH + 1];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,9 @@ bool app_metadata_is_valid_name(const std::string& name);
|
||||
bool app_metadata_is_valid_version_name(const std::string& version);
|
||||
bool app_metadata_is_valid_version_code(const std::string& version);
|
||||
|
||||
/** Validates a comma-separated list of device ids (alphanumeric + '-' items, matching Devices/<id> folder names). */
|
||||
bool app_metadata_is_valid_device_id_list(const std::string& value);
|
||||
|
||||
/** Copies @a value into @a dest (a fixed-size buffer of @a dest_size bytes, including the NULL
|
||||
* terminator) if it fits.
|
||||
* @retval false @a value doesn't fit in @a dest_size bytes - @a dest is left untouched */
|
||||
|
||||
@@ -37,6 +37,26 @@ bool validate_string(const std::string& value, bool (*is_valid_char)(char)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Validates a comma-separated list: non-empty, no leading/trailing/double commas (which would
|
||||
* produce an empty item), and every item passing @a is_valid_item. */
|
||||
bool validate_csv_list(const std::string& value, bool (*is_valid_item)(const std::string&)) {
|
||||
if (value.empty()) {
|
||||
return false;
|
||||
}
|
||||
size_t start = 0;
|
||||
while (true) {
|
||||
auto comma = value.find(',', start);
|
||||
auto end = comma == std::string::npos ? value.size() : comma;
|
||||
if (end == start || !is_valid_item(value.substr(start, end - start))) {
|
||||
return false;
|
||||
}
|
||||
if (comma == std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
start = comma + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** manifest.properties format: "key=value" lines, "[section]" lines prefix every following key
|
||||
* until the next section, "#" lines are comments, blank lines are skipped. Deliberately a local,
|
||||
* minimal re-implementation rather than depending on Tactility's file::loadPropertiesFile() -
|
||||
@@ -130,6 +150,20 @@ bool app_metadata_is_valid_version_code(const std::string& version) {
|
||||
});
|
||||
}
|
||||
|
||||
bool app_metadata_is_valid_device_id_list(const std::string& value) {
|
||||
return validate_csv_list(value, [](const std::string& item) {
|
||||
bool has_alnum = false;
|
||||
for (char c: item) {
|
||||
if (std::isalnum(static_cast<unsigned char>(c)) != 0) {
|
||||
has_alnum = true;
|
||||
} else if (c != '-') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return has_alnum;
|
||||
});
|
||||
}
|
||||
|
||||
bool app_metadata_copy_bounded(char* dest, size_t dest_size, const std::string& value) {
|
||||
if (value.size() >= dest_size) {
|
||||
return false;
|
||||
@@ -141,6 +175,10 @@ bool app_metadata_copy_bounded(char* dest, size_t dest_size, const std::string&
|
||||
error_t app_metadata_parse(const char* path, struct AppMetadata* out_metadata) {
|
||||
LOG_I(TAG, "Parsing manifest %s", path);
|
||||
|
||||
// requires_device_id is optional in V2 and unwritten by V1; zeroing here (rather than relying
|
||||
// on the caller) guarantees it reads back as empty ("unrestricted") either way.
|
||||
*out_metadata = {};
|
||||
|
||||
std::map<std::string, std::string> properties;
|
||||
std::string first_line;
|
||||
if (!load_properties(path, properties, first_line)) {
|
||||
|
||||
@@ -99,5 +99,20 @@ bool app_metadata_parse_v2(const std::map<std::string, std::string>& properties,
|
||||
return false;
|
||||
}
|
||||
|
||||
// requires.device.id (optional; if present, must be a non-empty comma-separated list)
|
||||
|
||||
auto device_id_iterator = properties.find("requires.device.id");
|
||||
if (device_id_iterator != properties.end()) {
|
||||
const std::string& device_id = device_id_iterator->second;
|
||||
if (!app_metadata_is_valid_device_id_list(device_id)) {
|
||||
LOG_E(TAG, "Invalid requires.device.id");
|
||||
return false;
|
||||
}
|
||||
if (!app_metadata_copy_bounded(out_metadata.requires_device_id, sizeof(out_metadata.requires_device_id), device_id)) {
|
||||
LOG_E(TAG, "requires.device.id too long");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ error_t app_paths_get_user_data_directory(const char* app_id, char* out_path, si
|
||||
if (error != ERROR_NONE) {
|
||||
return error;
|
||||
}
|
||||
int written = std::snprintf(out_path, out_path_size, "%s/app/%s", root, app_id);
|
||||
int written = std::snprintf(out_path, out_path_size, "%s/user/app/%s", root, app_id);
|
||||
if (written < 0 || (size_t)written >= out_path_size) {
|
||||
return ERROR_BUFFER_OVERFLOW;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ constexpr auto* TAG = "gps_settings";
|
||||
|
||||
// Storage key for the persisted configuration file (services would use their own service ID for
|
||||
// this; gps_settings has no service backing it, so it defines its own).
|
||||
constexpr auto* GPS_SETTINGS_STORAGE_ID = "gps";
|
||||
constexpr auto* GPS_SETTINGS_STORAGE_ID = "tactility.gps";
|
||||
|
||||
// region Configuration persistence
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
constexpr auto* TAG = "lvgl_keyboard";
|
||||
|
||||
static LvglSoftwareKeyboard last_software_keyboard = {
|
||||
.object = nullptr
|
||||
};
|
||||
@@ -106,16 +109,23 @@ void lvgl_keyboard_disable(lv_indev_t* indev) {
|
||||
lv_indev_set_group(indev, nullptr);
|
||||
}
|
||||
|
||||
bool lvgl_hardware_keyboard_is_available() {
|
||||
Device* keyboard_device;
|
||||
if (device_get_first_active_by_type(&KEYBOARD_TYPE, &keyboard_device) != ERROR_NONE) {
|
||||
return false;
|
||||
static bool lvgl_hardware_keyboard_check_present(Device* device, void* context) {
|
||||
bool ready = device_is_ready(device);
|
||||
bool present = ready && keyboard_is_present(device);
|
||||
LOG_D(TAG, "keyboard device %s: ready=%d present=%d", device->name, (int)ready, (int)present);
|
||||
if (!present) {
|
||||
return true; // keep looking
|
||||
}
|
||||
*static_cast<bool*>(context) = true;
|
||||
return false; // found one, stop iterating
|
||||
}
|
||||
|
||||
bool lvgl_hardware_keyboard_is_available() {
|
||||
// TODO: Refactor the driver subsystem to so it does proper probing/releasing of such devices
|
||||
// This work-around exists for the Tab5 keyboard driver.
|
||||
bool present = keyboard_is_present(keyboard_device);
|
||||
device_put(keyboard_device);
|
||||
bool present = false;
|
||||
device_for_each_of_type(&KEYBOARD_TYPE, &present, lvgl_hardware_keyboard_check_present);
|
||||
LOG_D(TAG, "lvgl_hardware_keyboard_is_available() -> %d", (int)present);
|
||||
return present;
|
||||
}
|
||||
|
||||
@@ -138,6 +148,11 @@ void lvgl_hardware_keyboard_remove_custom(lv_indev_t* indev) {
|
||||
}
|
||||
|
||||
static void textarea_show_keyboard(lv_event_t* event) {
|
||||
// Re-checked here rather than gated once at lvgl_keyboard_add_textarea() time, so a hardware
|
||||
// keyboard that connects/disconnects after the textarea was created is honored immediately.
|
||||
if (!lvgl_software_keyboard_is_enabled()) {
|
||||
return;
|
||||
}
|
||||
lv_obj_t* target = lv_event_get_current_target_obj(event);
|
||||
if (last_software_keyboard.object != nullptr) {
|
||||
lvgl_software_keyboard_show(&last_software_keyboard, target);
|
||||
@@ -192,18 +207,10 @@ LvglSoftwareKeyboard* lvgl_software_keyboard_get_last() {
|
||||
}
|
||||
|
||||
void lvgl_keyboard_add_textarea(LvglSoftwareKeyboard* keyboard, lv_obj_t* textarea) {
|
||||
// Only the on-screen keyboard's show/hide wiring is specific to "no hardware keyboard"
|
||||
// mode. Group membership must NOT be gated on it: a hardware keypad indev (see
|
||||
// lvgl_keyboard_enable()/lvgl_software_keyboard_activate()) is bound to keyboard_group
|
||||
// regardless of whether a software keyboard is in use, so skipping lv_group_add_obj()
|
||||
// here left every textarea unreachable from a hardware keyboard - it was never a member
|
||||
// of the group its indev delivers key events through.
|
||||
if (lvgl_software_keyboard_is_enabled()) {
|
||||
lv_obj_add_event_cb(textarea, textarea_show_keyboard, LV_EVENT_FOCUSED, nullptr);
|
||||
lv_obj_add_event_cb(textarea, textarea_hide_keyboard, LV_EVENT_DEFOCUSED, nullptr);
|
||||
lv_obj_add_event_cb(textarea, textarea_hide_keyboard, LV_EVENT_READY, nullptr);
|
||||
lv_obj_add_event_cb(textarea, textarea_hide_keyboard, LV_EVENT_DELETE, nullptr);
|
||||
}
|
||||
lv_obj_add_event_cb(textarea, textarea_show_keyboard, LV_EVENT_FOCUSED, nullptr);
|
||||
lv_obj_add_event_cb(textarea, textarea_hide_keyboard, LV_EVENT_DEFOCUSED, nullptr);
|
||||
lv_obj_add_event_cb(textarea, textarea_hide_keyboard, LV_EVENT_READY, nullptr);
|
||||
lv_obj_add_event_cb(textarea, textarea_hide_keyboard, LV_EVENT_DELETE, nullptr);
|
||||
|
||||
// lv_obj_t auto-remove themselves from the group when they are destroyed (last checked in LVGL 8.3)
|
||||
lv_group_add_obj(keyboard_group, textarea);
|
||||
|
||||
Reference in New Issue
Block a user