Fixes and improvements (#616)
This commit is contained in:
committed by
GitHub
parent
f943c4dd69
commit
b03759a111
@@ -25,6 +25,7 @@
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/hal/SdCard.h>
|
||||
#include <Tactility/lvgl/KeyboardDeviceListener.h>
|
||||
#include <Tactility/lvgl/Statusbar.h>
|
||||
#include <Tactility/lvgl/TrackballInit.h>
|
||||
#include <Tactility/lvgl/UsbHidInput.h>
|
||||
@@ -441,6 +442,7 @@ static void onLvglStarted() {
|
||||
#endif
|
||||
|
||||
lvgl::startUsbHidInput();
|
||||
lvgl::startKeyboardDeviceListener();
|
||||
lvgl::initTrackball();
|
||||
|
||||
#ifdef CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED
|
||||
@@ -457,6 +459,7 @@ static void onLvglStopped() {
|
||||
|
||||
module_stop(&lvgl_window_manager_module);
|
||||
|
||||
lvgl::stopKeyboardDeviceListener();
|
||||
lvgl::stopUsbHidInput();
|
||||
|
||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||
|
||||
@@ -237,7 +237,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "AddGps",
|
||||
.id = "tactility.addgps",
|
||||
.name = "Add GPS",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -152,7 +152,7 @@ uint32_t start(uint32_t callerAppInstanceId, const std::string& title, const std
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "AlertDialog",
|
||||
.id = "tactility.alertdialog",
|
||||
.name = "Alert Dialog",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -162,7 +162,7 @@ void start(const std::string& appId) {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "AppDetails",
|
||||
.id = "tactility.appdetails",
|
||||
.name = "App Details",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -36,8 +36,11 @@ struct Context {
|
||||
lv_obj_t* contentWrapper = nullptr;
|
||||
lv_obj_t* refreshButton = nullptr;
|
||||
std::string cachedAppsJsonFile = std::format("{}/app_hub.json", getTempPath());
|
||||
std::vector<AppHubEntry> entries;
|
||||
AppHubEntryList entries;
|
||||
Mutex mutex;
|
||||
|
||||
// Survives across a bury/resurface cycle (e.g. opening AppHubDetailsApp and returning),
|
||||
int32_t scrollY = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -46,10 +49,6 @@ void refresh(Context* ctx);
|
||||
|
||||
void onBackPressed(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
}
|
||||
@@ -86,9 +85,25 @@ void showNoInternet(Context* ctx) {
|
||||
}
|
||||
|
||||
void showApps(Context* ctx) {
|
||||
// Refresh rebuilds the list from scratch (cached copy, then again once the network fetch
|
||||
// lands), which would otherwise reset the user's scroll position each time.
|
||||
auto scrollY = lv_obj_get_scroll_y(ctx->contentWrapper);
|
||||
lv_obj_clean(ctx->contentWrapper);
|
||||
ctx->mutex.lock();
|
||||
if (parseJson(ctx->cachedAppsJsonFile, ctx->entries)) {
|
||||
// An empty targetPlatforms list means the entry runs everywhere; otherwise it must name
|
||||
// this build's own target to be installable here. The simulator isn't a real MCU target,
|
||||
// so it has nothing to match against and skips this filter entirely.
|
||||
std::erase_if(ctx->entries, [](const AppHubEntry& entry) {
|
||||
#ifdef ESP_PLATFORM
|
||||
return !entry.targetPlatforms.empty() &&
|
||||
std::ranges::find(entry.targetPlatforms, CONFIG_IDF_TARGET) == entry.targetPlatforms.end();
|
||||
#else
|
||||
(void)entry;
|
||||
return false;
|
||||
#endif
|
||||
});
|
||||
|
||||
std::ranges::sort(ctx->entries, [](auto left, auto right) {
|
||||
return left.appName < right.appName;
|
||||
});
|
||||
@@ -106,6 +121,8 @@ void showApps(Context* ctx) {
|
||||
lv_obj_set_user_data(entry_button, int_as_voidptr);
|
||||
lv_obj_add_event_cb(entry_button, onAppPressed, LV_EVENT_SHORT_CLICKED, ctx);
|
||||
}
|
||||
|
||||
lv_obj_scroll_to_y(ctx->contentWrapper, scrollY, LV_ANIM_OFF);
|
||||
} else {
|
||||
showRefreshFailedError(ctx, "Failed to load content");
|
||||
}
|
||||
@@ -169,6 +186,15 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
lv_obj_set_style_pad_ver(ctx->contentWrapper, 0, LV_STATE_DEFAULT);
|
||||
|
||||
refresh(ctx);
|
||||
|
||||
lv_obj_scroll_to_y(ctx->contentWrapper, ctx->scrollY, LV_ANIM_OFF);
|
||||
}
|
||||
|
||||
void destroyWidgets(void* userData) {
|
||||
auto* ctx = static_cast<Context*>(userData);
|
||||
ctx->scrollY = lv_obj_get_scroll_y(ctx->contentWrapper);
|
||||
ctx->contentWrapper = nullptr;
|
||||
ctx->refreshButton = nullptr;
|
||||
}
|
||||
|
||||
int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
@@ -179,7 +205,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
sub.app_instance_id = appInstanceId;
|
||||
app_event_subscribe(&sub);
|
||||
|
||||
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
|
||||
WindowId window = window_manager_create_ext(appInstanceId, createWidgets, destroyWidgets, &ctx);
|
||||
|
||||
bool shouldClose = false;
|
||||
while (!shouldClose) {
|
||||
@@ -206,7 +232,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "AppHub",
|
||||
.id = "tactility.apphub",
|
||||
.name = "App Hub",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -20,7 +20,7 @@ static bool parseEntry(const cJSON* object, AppHubEntry& entry) {
|
||||
reader.readStringArray("targetPlatforms", entry.targetPlatforms);
|
||||
}
|
||||
|
||||
bool parseJson(const std::string& filePath, std::vector<AppHubEntry>& entries) {
|
||||
bool parseJson(const std::string& filePath, AppHubEntryList& entries) {
|
||||
file::FileMutexGuard guard(filePath);
|
||||
|
||||
auto data = file::readString(filePath);
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
#include "../../../../Modules/app-module/private/app/private/app_ledger.h"
|
||||
#include "app/metadata.h"
|
||||
|
||||
|
||||
#include <Tactility/DeprecatedPaths.h>
|
||||
#include <Tactility/StringUtils.h>
|
||||
#include <Tactility/app/alertdialog/AlertDialog.h>
|
||||
@@ -12,6 +8,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/install.h>
|
||||
#include <app/metadata.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/manifest.h>
|
||||
|
||||
@@ -64,10 +61,6 @@ uint32_t showConfirmDialog(Context* ctx, const char* action) {
|
||||
|
||||
void onBackPressed(lv_event_t* e) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(e));
|
||||
// Async, non-blocking - must NOT call app_manager_stop() directly here: that bound-waits
|
||||
// (thread_join) for this app's own thread to finish, which needs the LVGL lock
|
||||
// (window_manager_remove()) - but this callback runs ON the LVGL task, which would
|
||||
// deadlock against itself.
|
||||
AppEvent closeEvent { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
|
||||
app_event_emit(ctx->appInstanceId, &closeEvent);
|
||||
}
|
||||
@@ -308,7 +301,7 @@ void start(const apphub::AppHubEntry& entry) {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "AppHubDetails",
|
||||
.id = "tactility.apphubdetails",
|
||||
.name = "App Details",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -106,7 +106,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "AppList",
|
||||
.id = "tactility.applist",
|
||||
.name = "Apps",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -121,7 +121,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "AppSettings",
|
||||
.id = "tactility.appsettings",
|
||||
.name = "Apps",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -176,7 +176,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "ApWebServer",
|
||||
.id = "tactility.apwebserver",
|
||||
.name = "AP Web Server",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -251,7 +251,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "AudioSettings",
|
||||
.id = "tactility.audiosettings",
|
||||
.name = "Audio",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -326,7 +326,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "Boot",
|
||||
.id = "tactility.boot",
|
||||
.name = "Boot",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -283,7 +283,7 @@ uint32_t start() {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "BtManage",
|
||||
.id = "tactility.btmanage",
|
||||
.name = "Bluetooth",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -256,7 +256,7 @@ void start(const std::string& addrHex) {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "BtPeerSettings",
|
||||
.id = "tactility.btpeersettings",
|
||||
.name = "BT Device Settings",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -219,7 +219,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "Chat",
|
||||
.id = "tactility.chat",
|
||||
.name = "Chat",
|
||||
.category = APP_CATEGORY_USER,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#include <Tactility/app/chat/ChatSettings.h>
|
||||
#include <Tactility/app/chat/ChatProtocol.h>
|
||||
|
||||
#include <Tactility/DeprecatedPaths.h>
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
|
||||
#include <app/paths.h>
|
||||
#include <crypt/crypt.h>
|
||||
|
||||
#include <esp_random.h>
|
||||
@@ -28,7 +28,11 @@ namespace tt::app::chat {
|
||||
constexpr auto* TAG = "ChatSettings";
|
||||
|
||||
static std::string getSettingsFilePath() {
|
||||
return getUserDataPath() + "/settings/chat.properties";
|
||||
char path[256];
|
||||
if (app_paths_get_user_data_path("tactility.chat", "chat.properties", path, sizeof(path)) != ERROR_NONE) {
|
||||
return "";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
constexpr auto* KEY_SENDER_ID = "senderId";
|
||||
|
||||
@@ -199,7 +199,7 @@ void start() {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "CrashDiagnostics",
|
||||
.id = "tactility.crashdiagnostics",
|
||||
.name = "Crash Diagnostics",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -226,7 +226,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "Development",
|
||||
.id = "tactility.development",
|
||||
.name = "Development",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -66,7 +66,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "Files",
|
||||
.id = "tactility.files",
|
||||
.name = "Files",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -106,7 +106,7 @@ uint32_t startForExistingOrNewFile(uint32_t callerAppInstanceId) {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "FileSelection",
|
||||
.id = "tactility.fileselection",
|
||||
.name = "File Selection",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -320,7 +320,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "GpsSettings",
|
||||
.id = "tactility.gpssettings",
|
||||
.name = "GPS",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -124,7 +124,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "GroveSettings",
|
||||
.id = "tactility.grovesettings",
|
||||
.name = "Grove",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -419,7 +419,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "I2cScanner",
|
||||
.id = "tactility.i2cscanner",
|
||||
.name = "I2C Scanner",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -126,7 +126,7 @@ void start(const std::string& file) {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "ImageViewer",
|
||||
.id = "tactility.imageviewer",
|
||||
.name = "Image Viewer",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -148,7 +148,7 @@ std::string getLastText() {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "InputDialog",
|
||||
.id = "tactility.inputdialog",
|
||||
.name = "Input Dialog",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -340,7 +340,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "Display",
|
||||
.id = "tactility.display",
|
||||
.name = "Display",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -241,7 +241,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "KeyboardSettings",
|
||||
.id = "tactility.keyboardsettings",
|
||||
.name = "Keyboard",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -168,9 +168,9 @@ void createWidgets(lv_obj_t* parent, void*) {
|
||||
? computeButtonMargin(lv_display_get_horizontal_resolution(display), total_button_size)
|
||||
: computeButtonMargin(lv_display_get_vertical_resolution(display), total_button_size);
|
||||
|
||||
auto* app_list_button = createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_APPS, "AppList", margin, is_landscape_display);
|
||||
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_FOLDER, "Files", margin, is_landscape_display);
|
||||
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_SETTINGS, "Settings", margin, is_landscape_display);
|
||||
auto* app_list_button = createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_APPS, "tactility.applist", margin, is_landscape_display);
|
||||
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_FOLDER, "tactility.files", margin, is_landscape_display);
|
||||
createAppButton(buttons_wrapper, ui_density, LVGL_ICON_LAUNCHER_SETTINGS, "tactility.settings", margin, is_landscape_display);
|
||||
|
||||
// The launcher's container is several levels below the screen, and LVGL only sends
|
||||
// LV_EVENT_SIZE_CHANGED to the screen object itself on a resolution change - so the
|
||||
@@ -184,7 +184,7 @@ void createWidgets(lv_obj_t* parent, void*) {
|
||||
auto* power_button = lv_button_create(parent);
|
||||
lv_obj_set_style_pad_all(power_button, 8, 0);
|
||||
lv_obj_align(power_button, LV_ALIGN_BOTTOM_MID, 0, -10);
|
||||
lv_obj_add_event_cb(power_button, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)"PowerOff");
|
||||
lv_obj_add_event_cb(power_button, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)"tactility.poweroff");
|
||||
lv_obj_set_style_shadow_width(power_button, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_bg_opa(power_button, 0, LV_PART_MAIN);
|
||||
|
||||
@@ -261,7 +261,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "Launcher",
|
||||
.id = "tactility.launcher",
|
||||
.name = "Launcher",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -176,7 +176,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "LocaleSettings",
|
||||
.id = "tactility.localesettings",
|
||||
.name = "Region & Language",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -249,7 +249,7 @@ void start(const std::string& filePath) {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "Notes",
|
||||
.id = "tactility.notes",
|
||||
.name = "Notes",
|
||||
.category = APP_CATEGORY_USER,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -273,7 +273,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "Power",
|
||||
.id = "tactility.power",
|
||||
.name = "Power",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -167,7 +167,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "PowerOff",
|
||||
.id = "tactility.poweroff",
|
||||
.name = "Power Off",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -285,7 +285,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "Screenshot",
|
||||
.id = "tactility.screenshot",
|
||||
.name = "Screenshot",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -150,7 +150,7 @@ AppInstanceId start(AppInstanceId callerAppInstanceId, const std::string& title,
|
||||
}
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "SelectionDialog",
|
||||
.id = "tactility.selectiondialog",
|
||||
.name = "Selection Dialog",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -104,7 +104,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "Settings",
|
||||
.id = "tactility.settings",
|
||||
.name = "Settings",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -271,7 +271,7 @@ void start() {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "Setup",
|
||||
.id = "tactility.setup",
|
||||
.name = "Setup",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -452,7 +452,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "SystemInfo",
|
||||
.id = "tactility.systeminfo",
|
||||
.name = "System Info",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -209,7 +209,7 @@ uint32_t start() {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "TimeDateSettings",
|
||||
.id = "tactility.timedatesettings",
|
||||
.name = "Time & Date",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -283,7 +283,7 @@ std::string getLastCode() {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "TimeZone",
|
||||
.id = "tactility.timezone",
|
||||
.name = "Select Time zone",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -288,7 +288,7 @@ uint32_t start(uint32_t callerAppInstanceId) {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "TouchCalibration",
|
||||
.id = "tactility.touchcalibration",
|
||||
.name = "Touch Calibration",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -304,7 +304,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "TrackballSettings",
|
||||
.id = "tactility.trackballsettings",
|
||||
.name = "Trackball",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -116,7 +116,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "UsbSettings",
|
||||
.id = "tactility.usbsettings",
|
||||
.name = "USB",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -431,7 +431,7 @@ int32_t appMain(uint32_t appInstanceId, int argc, char* argv[]) {
|
||||
} // namespace
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "WebServerSettings",
|
||||
.id = "tactility.webserversettings",
|
||||
.name = "Web Server",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -272,7 +272,7 @@ void start(const std::string& ssid) {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "WifiApSettings",
|
||||
.id = "tactility.wifiapsettings",
|
||||
.name = "Wi-Fi AP Settings",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -360,7 +360,7 @@ void start(const std::string& ssid, const std::string& password) {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "WifiConnect",
|
||||
.id = "tactility.wificonnect",
|
||||
.name = "Wi-Fi Connect",
|
||||
.category = APP_CATEGORY_SYSTEM,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||
|
||||
@@ -188,7 +188,7 @@ uint32_t start(uint32_t callerAppInstanceId) {
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
.id = "WifiManage",
|
||||
.id = "tactility.wifimanage",
|
||||
.name = "Wi-Fi",
|
||||
.category = APP_CATEGORY_SETTINGS,
|
||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) }
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
#include <Tactility/lvgl/KeyboardDeviceListener.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/device_listener.h>
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <lvgl/devices/keyboard.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
constexpr auto* TAG = "KeyboardDeviceListener";
|
||||
|
||||
namespace {
|
||||
|
||||
struct KeyboardBinding {
|
||||
Device* device;
|
||||
lv_indev_t* indev;
|
||||
};
|
||||
|
||||
Mutex& bindingsMutex() {
|
||||
static Mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
std::vector<KeyboardBinding>& bindings() {
|
||||
static std::vector<KeyboardBinding> list;
|
||||
return list;
|
||||
}
|
||||
|
||||
// Guards, under bindingsMutex, against onKeyboardDeviceStarted() registering a binding after
|
||||
// stopKeyboardDeviceListener() has already drained bindings() and returned.
|
||||
bool& listenerActive() {
|
||||
static bool active = false;
|
||||
return active;
|
||||
}
|
||||
|
||||
void onKeyboardDeviceStarted(Device* device) {
|
||||
// Held for the whole add+register step so a same-device STOPPING can't observe the binding
|
||||
// as neither-registered-nor-added: onKeyboardDeviceStopped() and shutdown's drain both take
|
||||
// this same lock, so they see either the fully-registered binding or nothing at all.
|
||||
auto lock = bindingsMutex().asScopedLock();
|
||||
lock.lock();
|
||||
if (!listenerActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
lv_indev_t* indev = nullptr;
|
||||
lvgl_lock();
|
||||
error_t error = lvgl_keyboard_add(device, lv_display_get_default(), &indev);
|
||||
lvgl_unlock();
|
||||
if (error != ERROR_NONE) {
|
||||
LOG_E(TAG, "failed to bind keyboard device %s to LVGL", device->name);
|
||||
return;
|
||||
}
|
||||
bindings().push_back({ device, indev });
|
||||
}
|
||||
|
||||
void onKeyboardDeviceStopped(Device* device) {
|
||||
auto lock = bindingsMutex().asScopedLock();
|
||||
lock.lock();
|
||||
lv_indev_t* indev = nullptr;
|
||||
auto& list = bindings();
|
||||
for (auto it = list.begin(); it != list.end(); ++it) {
|
||||
if (it->device == device) {
|
||||
indev = it->indev;
|
||||
list.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (indev == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
lvgl_keyboard_remove(indev);
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
void onDeviceEvent(Device* device, DeviceEvent event, void* context) {
|
||||
(void)context;
|
||||
if (device_get_type(device) != &KEYBOARD_TYPE) {
|
||||
return;
|
||||
}
|
||||
// Detach on STOPPING (before stop_device() frees the device's resources), not STOPPED (which
|
||||
// only fires after stop_device() already ran) - see DEVICE_EVENT_STOPPING's doc comment.
|
||||
if (event == DEVICE_EVENT_STARTED) {
|
||||
onKeyboardDeviceStarted(device);
|
||||
} else if (event == DEVICE_EVENT_STOPPING) {
|
||||
onKeyboardDeviceStopped(device);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void startKeyboardDeviceListener() {
|
||||
auto lock = bindingsMutex().asScopedLock();
|
||||
lock.lock();
|
||||
listenerActive() = true;
|
||||
device_listener_add(onDeviceEvent, nullptr);
|
||||
}
|
||||
|
||||
void stopKeyboardDeviceListener() {
|
||||
std::vector<KeyboardBinding> remaining;
|
||||
{
|
||||
// device_listener_remove() doesn't wait for an in-flight onDeviceEvent() to finish, so a
|
||||
// STARTED callback already past this point can still land after we drain below; it takes
|
||||
// this same lock and checks listenerActive() before registering, so it backs off instead
|
||||
// of adding a binding nothing will ever remove.
|
||||
auto lock = bindingsMutex().asScopedLock();
|
||||
lock.lock();
|
||||
listenerActive() = false;
|
||||
device_listener_remove(onDeviceEvent);
|
||||
remaining = std::move(bindings());
|
||||
bindings().clear();
|
||||
}
|
||||
|
||||
if (remaining.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
for (auto& binding : remaining) {
|
||||
lvgl_keyboard_remove(binding.indev);
|
||||
}
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
} // namespace tt::lvgl
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <lvgl/devices/keyboard.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <new>
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
@@ -286,12 +287,19 @@ static void usbHidInputTask(void* arg) {
|
||||
void startUsbHidInput() {
|
||||
if (s_ctx != nullptr) return;
|
||||
|
||||
auto* ctx = new UsbHidInputCtx();
|
||||
static constexpr MemoryPolicy CTX_POLICY = { 0, MEMORY_CAPABILITY_EXTERNAL, 0 };
|
||||
auto* ctx_mem = memory_alloc_with_policy(sizeof(UsbHidInputCtx), &CTX_POLICY);
|
||||
if (!ctx_mem) {
|
||||
LOG_E(TAG, "failed to allocate context");
|
||||
return;
|
||||
}
|
||||
auto* ctx = new (ctx_mem) UsbHidInputCtx();
|
||||
|
||||
ctx->hid_queue = xQueueCreate(HID_EVENT_QUEUE_SIZE, sizeof(UsbHidEvent));
|
||||
if (!ctx->hid_queue) {
|
||||
LOG_E(TAG, "failed to create HID event queue");
|
||||
delete ctx;
|
||||
ctx->~UsbHidInputCtx();
|
||||
memory_free(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -299,7 +307,8 @@ void startUsbHidInput() {
|
||||
if (!ctx->key_queue) {
|
||||
LOG_E(TAG, "failed to create key event queue");
|
||||
vQueueDelete(ctx->hid_queue);
|
||||
delete ctx;
|
||||
ctx->~UsbHidInputCtx();
|
||||
memory_free(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -308,7 +317,8 @@ void startUsbHidInput() {
|
||||
LOG_E(TAG, "failed to create task done semaphore");
|
||||
vQueueDelete(ctx->hid_queue);
|
||||
vQueueDelete(ctx->key_queue);
|
||||
delete ctx;
|
||||
ctx->~UsbHidInputCtx();
|
||||
memory_free(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -371,7 +381,8 @@ void startUsbHidInput() {
|
||||
vQueueDelete(ctx->hid_queue);
|
||||
vQueueDelete(ctx->key_queue);
|
||||
vSemaphoreDelete(ctx->task_done);
|
||||
delete ctx;
|
||||
ctx->~UsbHidInputCtx();
|
||||
memory_free(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -429,7 +440,8 @@ void stopUsbHidInput() {
|
||||
vQueueDelete(ctx->hid_queue);
|
||||
vQueueDelete(ctx->key_queue);
|
||||
vSemaphoreDelete(ctx->task_done);
|
||||
delete ctx;
|
||||
ctx->~UsbHidInputCtx();
|
||||
memory_free(ctx);
|
||||
|
||||
LOG_I(TAG, "stopped");
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ std::shared_ptr<AudioService> findAudioService() {
|
||||
}
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "Audio",
|
||||
.id = "tactility.audio",
|
||||
.createService = create<AudioService>
|
||||
};
|
||||
|
||||
|
||||
@@ -252,7 +252,7 @@ std::shared_ptr<DevelopmentService> findService() {
|
||||
}
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "Development",
|
||||
.id = "tactility.development",
|
||||
.createService = create<DevelopmentService>
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <Tactility/DeprecatedPaths.h>
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
#include <Tactility/service/development/DevelopmentSettings.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <app/paths.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
namespace tt::service::development {
|
||||
@@ -13,7 +13,11 @@ namespace tt::service::development {
|
||||
constexpr auto* TAG = "DevSettings";
|
||||
|
||||
static std::string getSettingsFilePath() {
|
||||
return getUserDataPath() + "/settings/development.properties";
|
||||
char path[256];
|
||||
if (app_paths_get_user_data_path("tactility.development", "development.properties", path, sizeof(path)) != ERROR_NONE) {
|
||||
return "";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
constexpr auto* SETTINGS_KEY_ENABLE_ON_BOOT = "enableOnBoot";
|
||||
|
||||
@@ -280,12 +280,12 @@ void DisplayIdleService::reloadSettings() {
|
||||
|
||||
std::shared_ptr<DisplayIdleService> findService() {
|
||||
return std::static_pointer_cast<DisplayIdleService>(
|
||||
findServiceById("DisplayIdle")
|
||||
findServiceById("tactility.displayidle")
|
||||
);
|
||||
}
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "DisplayIdle",
|
||||
.id = "tactility.displayidle",
|
||||
.createService = create<DisplayIdleService>
|
||||
};
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ std::shared_ptr<EspNowService> findService() {
|
||||
}
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "EspNow",
|
||||
.id = "tactility.espnow",
|
||||
.createService = create<EspNowService>
|
||||
};
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ public:
|
||||
};
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "KeyboardIdle",
|
||||
.id = "tactility.keyboardidle",
|
||||
.createService = create<KeyboardIdleService>
|
||||
};
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ void MemoryCheckerService::onTimerUpdate() {
|
||||
}
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "MemoryChecker",
|
||||
.id = "tactility.memorychecker",
|
||||
.createService = create<MemoryCheckerService>
|
||||
};
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ void RtcTimeService::onStop(ServiceContext& serviceContext) {
|
||||
}
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "RtcTime",
|
||||
.id = "tactility.rtctime",
|
||||
.createService = create<RtcTimeService>
|
||||
};
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ bool ScreenshotService::isTaskStarted() {
|
||||
}
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "Screenshot",
|
||||
.id = "tactility.screenshot",
|
||||
.createService = create<ScreenshotService>
|
||||
};
|
||||
|
||||
|
||||
@@ -359,7 +359,7 @@ public:
|
||||
};
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "Statusbar",
|
||||
.id = "tactility.statusbar",
|
||||
.createService = create<StatusbarService>
|
||||
};
|
||||
|
||||
|
||||
@@ -1797,7 +1797,7 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
|
||||
}
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "WebServer",
|
||||
.id = "tactility.webserver",
|
||||
.createService = create<WebServerService>
|
||||
};
|
||||
|
||||
|
||||
@@ -551,7 +551,7 @@ public:
|
||||
} // namespace
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "wifi",
|
||||
.id = "tactility.wifi",
|
||||
.createService = create<WifiService>
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
#include <Tactility/DeprecatedPaths.h>
|
||||
|
||||
#include <app/paths.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -11,7 +12,11 @@
|
||||
namespace tt::settings::display {
|
||||
|
||||
static std::string getSettingsFilePath() {
|
||||
return getUserDataPath() + "/settings/display.properties";
|
||||
char path[256];
|
||||
if (app_paths_get_user_data_path("tactility.display", "display.properties", path, sizeof(path)) != ERROR_NONE) {
|
||||
return "";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
constexpr auto* SETTINGS_KEY_ORIENTATION = "orientation";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include <Tactility/settings/TrackballSettings.h>
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
#include <Tactility/DeprecatedPaths.h>
|
||||
|
||||
#include <app/paths.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -10,7 +11,11 @@
|
||||
namespace tt::settings::trackball {
|
||||
|
||||
static std::string getSettingsFilePath() {
|
||||
return getUserDataPath() + "/settings/trackball.properties";
|
||||
char path[256];
|
||||
if (app_paths_get_user_data_path("tactility.trackballsettings", "trackball.properties", path, sizeof(path)) != ERROR_NONE) {
|
||||
return "";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
constexpr auto* KEY_TRACKBALL_ENABLED = "trackballEnabled";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include <Tactility/DeprecatedPaths.h>
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
#include <Tactility/settings/WebServerSettings.h>
|
||||
|
||||
#include <app/paths.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <charconv>
|
||||
@@ -22,7 +22,11 @@ namespace tt::settings::webserver {
|
||||
constexpr auto* TAG = "WebServerSettings";
|
||||
|
||||
static std::string getSettingsFilePath() {
|
||||
return getUserDataPath() + "/settings/webserver.properties";
|
||||
char path[256];
|
||||
if (app_paths_get_user_data_path("tactility.webserversettings", "webserver.properties", path, sizeof(path)) != ERROR_NONE) {
|
||||
return "";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// Property keys
|
||||
|
||||
Reference in New Issue
Block a user