b3b5a9b2a3
Keeps one writer for saved state: ui.setTheme() writes through settings.setTheme() and then reloads the palette C cannot see.
129 lines
3.5 KiB
C++
129 lines
3.5 KiB
C++
#include "settings.h"
|
|
|
|
#include <SD.h>
|
|
|
|
extern "C" {
|
|
#include <lauxlib.h>
|
|
#include <lua.h>
|
|
}
|
|
|
|
Settings settings;
|
|
|
|
static constexpr const char* PATH = "/settings.lua";
|
|
static constexpr const char* TEMP_PATH = "/settings.tmp";
|
|
static constexpr const char* BACKUP_PATH = "/settings.bak";
|
|
|
|
static int16_t fieldOr(lua_State* L, const char* key, int16_t fallback) {
|
|
lua_getfield(L, -1, key);
|
|
int16_t value = lua_isinteger(L, -1) ? lua_tointeger(L, -1) : fallback;
|
|
lua_pop(L, 1);
|
|
return value;
|
|
}
|
|
|
|
static String stringFieldOr(lua_State* L, const char* key,
|
|
const String& fallback) {
|
|
lua_getfield(L, -1, key);
|
|
String value = lua_isstring(L, -1) ? lua_tostring(L, -1) : fallback;
|
|
lua_pop(L, 1);
|
|
return value;
|
|
}
|
|
|
|
static String luaString(const String& value) {
|
|
String out = "\"";
|
|
for (size_t i = 0; i < value.length(); i++) {
|
|
unsigned char c = value[i];
|
|
if (c == '\\' || c == '\"') {
|
|
out += '\\';
|
|
out += (char)c;
|
|
} else if (c >= 32 && c < 127) {
|
|
out += (char)c;
|
|
} else {
|
|
char escaped[5];
|
|
snprintf(escaped, sizeof(escaped), "\\%03u", c);
|
|
out += escaped;
|
|
}
|
|
}
|
|
out += '\"';
|
|
return out;
|
|
}
|
|
|
|
bool Settings::setRotation(int16_t degrees) {
|
|
if (degrees % 90 != 0 || degrees < 0 || degrees > 270)
|
|
return false;
|
|
rotation = degrees;
|
|
return true;
|
|
}
|
|
|
|
bool Settings::load() {
|
|
File f = SD.open(PATH);
|
|
if (!f)
|
|
return false;
|
|
String source = f.readString();
|
|
f.close();
|
|
|
|
lua_State* L = luaL_newstate();
|
|
if (!L)
|
|
return false;
|
|
bool ok =
|
|
luaL_loadbuffer(L, source.c_str(), source.length(), PATH) == LUA_OK &&
|
|
lua_pcall(L, 0, 1, 0) == LUA_OK && lua_istable(L, -1);
|
|
if (ok) {
|
|
rotation = fieldOr(L, "rotation", rotation);
|
|
timezone = stringFieldOr(L, "timezone", timezone);
|
|
theme = stringFieldOr(L, "theme", theme);
|
|
lua_getfield(L, -1, "touch");
|
|
if (lua_istable(L, -1)) {
|
|
touchX0 = fieldOr(L, "x0", touchX0);
|
|
touchY0 = fieldOr(L, "y0", touchY0);
|
|
touchX1 = fieldOr(L, "x1", touchX1);
|
|
touchY1 = fieldOr(L, "y1", touchY1);
|
|
}
|
|
lua_pop(L, 1);
|
|
lua_getfield(L, -1, "wifi");
|
|
if (lua_istable(L, -1)) {
|
|
wifiSsid = stringFieldOr(L, "ssid", wifiSsid);
|
|
wifiPassword = stringFieldOr(L, "password", wifiPassword);
|
|
}
|
|
lua_pop(L, 1);
|
|
}
|
|
lua_close(L);
|
|
return ok;
|
|
}
|
|
|
|
bool Settings::save() const {
|
|
String source = "return {\n rotation = " + String(rotation) + ",\n";
|
|
source += " timezone = " + luaString(timezone) + ",\n";
|
|
source += " theme = " + luaString(theme) + ",\n";
|
|
source += " touch = { x0 = " + String(touchX0) +
|
|
", y0 = " + String(touchY0) + ", x1 = " + String(touchX1) +
|
|
", y1 = " + String(touchY1) + " },\n";
|
|
source += " wifi = { ssid = " + luaString(wifiSsid) +
|
|
", password = " + luaString(wifiPassword) + " },\n}\n";
|
|
|
|
if (SD.exists(TEMP_PATH))
|
|
SD.remove(TEMP_PATH);
|
|
File f = SD.open(TEMP_PATH, FILE_WRITE);
|
|
if (!f)
|
|
return false;
|
|
bool ok = f.write(reinterpret_cast<const uint8_t*>(source.c_str()),
|
|
source.length()) == source.length();
|
|
f.close();
|
|
if (!ok) {
|
|
SD.remove(TEMP_PATH);
|
|
return false;
|
|
}
|
|
if (SD.exists(BACKUP_PATH))
|
|
SD.remove(BACKUP_PATH);
|
|
bool hadSettings = SD.exists(PATH);
|
|
if (hadSettings && !SD.rename(PATH, BACKUP_PATH))
|
|
return false;
|
|
if (SD.rename(TEMP_PATH, PATH)) {
|
|
if (hadSettings)
|
|
SD.remove(BACKUP_PATH);
|
|
return true;
|
|
}
|
|
if (hadSettings)
|
|
SD.rename(BACKUP_PATH, PATH);
|
|
return false;
|
|
}
|