mirror of
https://github.com/opentx/opentx.git
synced 2025-07-26 01:35:21 +03:00
* [simu] simpgmspace: Populate file date/time/size in f_stat(); Add f_utime(); Fix f_getcwd() on Windows; Fix f_mkdir() build error on Windows. * [Lua] Add runtime support for script pre-compilation. Changes behavior with LUA_COMPILER (Re: https://github.com/opentx/opentx/issues/3318): All .lua scripts are now compiled and saved to binary "bytecode" file (.luac extension) upon first execution; Scripts are also automatically re-compiled if .lua source file is newer than existing .luac file; The pre-compiled .luac version is loaded if modification time is newer or equivalent to .lua source file; If a .luac version of a script exists, then the .lua version does not need to be present; Guards against bytecode compatibility issues (binaries from 64-bit sim will not run on 32-bit sim/radio); In SIMU and DEBUG builds, the source .lua file is always preferred in order to preserve full debug info (this is is controlled with new LUA_SCRIPT_LOAD_MODE macro, see lua_api.h); GC is now run after each script is loaded. * [Lua] Add loadScript() API function as alternative to loadfile() from Lua base. This can take advantage of the new OTx script pre-compilation features to reduce memory footprint when loading functions dynamically. This is an interface to luaLoadScriptFileToState(). Fully documented. * [SD][Lua] Flexible file extensions support: Allow for variable length file extensions throughout system (no longer hard-coded in LEN_FILE_EXTENSION); Fixes issues with renaming files in SD manager which have file extensions longer than ".ext"; Expand general support for multiple file extensions per file type in sdListFiles() (eg. .lua and .luac for scripts); Lua scripts with .luac extensions can now be selected in custom/telemetry/function menus even if no .lua version exists (duplicates are not shown); .luac files can now also be executed from SD file manager UI. * [Build] Added CMake options for LUA_COMPILER and LUA_SCRIPT_LOAD_MODE. * Cosmetics. * [SD][gui] Improve efficiency of some file name handling routines in sdmanager GUI and sdListFiles() by extending getFileExtension() function. Use shared isExtensionMatching() in place of isImageFileExtension(). Only allow executing .luac files when LUA_COMPILER defined (as per request). * [simpgmspace] Fix f_mkdir() for MSVC build and misc. cleanup. * [Lua] Use getFileExtension() in script loader to determine file type and check for buffer overflow.
454 lines
13 KiB
C++
454 lines
13 KiB
C++
/*
|
|
* Copyright (C) OpenTX
|
|
*
|
|
* Based on code named
|
|
* th9x - http://code.google.com/p/th9x
|
|
* er9x - http://code.google.com/p/er9x
|
|
* gruvin9x - http://code.google.com/p/gruvin9x
|
|
*
|
|
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include "opentx.h"
|
|
#include "bin_allocator.h"
|
|
#include "lua/lua_api.h"
|
|
|
|
#define WIDGET_SCRIPTS_MAX_INSTRUCTIONS (10000/100)
|
|
#define MANUAL_SCRIPTS_MAX_INSTRUCTIONS (20000/100)
|
|
#define LUA_WARNING_INFO_LEN 64
|
|
|
|
lua_State *lsWidgets = NULL;
|
|
extern int custom_lua_atpanic(lua_State *L);
|
|
|
|
#define LUA_FULLPATH_MAXLEN (LEN_FILE_PATH_MAX + LEN_SCRIPT_FILENAME + LEN_FILE_EXTENSION_MAX) // max length (example: /SCRIPTS/THEMES/mytheme.lua)
|
|
|
|
void exec(int function, int nresults=0)
|
|
{
|
|
if (lsWidgets == 0) return;
|
|
|
|
if (function) {
|
|
luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, function);
|
|
if (lua_pcall(lsWidgets, 0, nresults, 0) != 0) {
|
|
TRACE("Error in theme %s", lua_tostring(lsWidgets, -1));
|
|
}
|
|
}
|
|
}
|
|
|
|
ZoneOption * createOptionsArray(int reference)
|
|
{
|
|
if (reference == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
int count = 0;
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, reference);
|
|
for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
|
|
count++;
|
|
}
|
|
|
|
ZoneOption * options = (ZoneOption *)malloc(sizeof(ZoneOption) * (count+1));
|
|
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, reference);
|
|
ZoneOption * option = options;
|
|
for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
|
|
luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
|
|
luaL_checktype(lsWidgets, -1, LUA_TTABLE); // value is table
|
|
uint8_t field = 0;
|
|
for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2) && field<5; lua_pop(lsWidgets, 1), field++) {
|
|
switch (field) {
|
|
case 0:
|
|
luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
|
|
luaL_checktype(lsWidgets, -1, LUA_TSTRING); // value is string
|
|
option->name = lua_tostring(lsWidgets, -1);
|
|
break;
|
|
case 1:
|
|
luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
|
|
luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
|
|
option->type = (ZoneOption::Type)lua_tointeger(lsWidgets, -1);
|
|
break;
|
|
case 2:
|
|
luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
|
|
luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
|
|
option->deflt.signedValue = lua_tointeger(lsWidgets, -1);
|
|
break;
|
|
case 3:
|
|
luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
|
|
luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
|
|
option->min.signedValue = lua_tointeger(lsWidgets, -1);
|
|
break;
|
|
case 4:
|
|
luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
|
|
luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
|
|
option->max.signedValue = lua_tointeger(lsWidgets, -1);
|
|
break;
|
|
}
|
|
}
|
|
option++;
|
|
}
|
|
|
|
option->name = NULL; // sentinel
|
|
|
|
return options;
|
|
}
|
|
|
|
class LuaTheme: public Theme
|
|
{
|
|
friend void luaLoadThemeCallback();
|
|
|
|
public:
|
|
LuaTheme(const char * name, int options):
|
|
Theme(name, createOptionsArray(options)),
|
|
loadFunction(0),
|
|
drawBackgroundFunction(0),
|
|
drawTopbarBackgroundFunction(0),
|
|
drawAlertBoxFunction(0)
|
|
{
|
|
}
|
|
|
|
virtual void load() const
|
|
{
|
|
luaLcdAllowed = true;
|
|
exec(loadFunction);
|
|
}
|
|
|
|
virtual void drawBackground() const
|
|
{
|
|
exec(drawBackgroundFunction);
|
|
}
|
|
|
|
virtual void drawTopbarBackground(uint8_t icon) const
|
|
{
|
|
exec(drawTopbarBackgroundFunction);
|
|
}
|
|
|
|
#if 0
|
|
virtual void drawAlertBox(const char * title, const char * text, const char * action) const
|
|
{
|
|
exec(drawAlertBoxFunction);
|
|
}
|
|
#endif
|
|
|
|
protected:
|
|
int loadFunction;
|
|
int drawBackgroundFunction;
|
|
int drawTopbarBackgroundFunction;
|
|
int drawAlertBoxFunction;
|
|
};
|
|
|
|
void luaLoadThemeCallback()
|
|
{
|
|
TRACE("luaLoadThemeCallback()");
|
|
const char * name=NULL;
|
|
int themeOptions=0, loadFunction=0, drawBackgroundFunction=0, drawTopbarBackgroundFunction=0;
|
|
|
|
luaL_checktype(lsWidgets, -1, LUA_TTABLE);
|
|
|
|
for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
|
|
const char * key = lua_tostring(lsWidgets, -2);
|
|
if (!strcmp(key, "name")) {
|
|
name = luaL_checkstring(lsWidgets, -1);
|
|
}
|
|
else if (!strcmp(key, "options")) {
|
|
themeOptions = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
|
|
lua_pushnil(lsWidgets);
|
|
}
|
|
else if (!strcmp(key, "load")) {
|
|
loadFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
|
|
lua_pushnil(lsWidgets);
|
|
}
|
|
else if (!strcmp(key, "drawBackground")) {
|
|
drawBackgroundFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
|
|
lua_pushnil(lsWidgets);
|
|
}
|
|
else if (!strcmp(key, "drawTopbarBackground")) {
|
|
drawTopbarBackgroundFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
|
|
lua_pushnil(lsWidgets);
|
|
}
|
|
}
|
|
|
|
if (name) {
|
|
LuaTheme * theme = new LuaTheme(name, themeOptions);
|
|
theme->loadFunction = loadFunction;
|
|
theme->drawBackgroundFunction = drawBackgroundFunction;
|
|
theme->drawTopbarBackgroundFunction = drawTopbarBackgroundFunction;
|
|
TRACE("Loaded Lua theme %s", name);
|
|
}
|
|
}
|
|
|
|
class LuaWidget: public Widget
|
|
{
|
|
public:
|
|
LuaWidget(const WidgetFactory * factory, const Zone & zone, Widget::PersistentData * persistentData, int widgetData):
|
|
Widget(factory, zone, persistentData),
|
|
widgetData(widgetData)
|
|
{
|
|
}
|
|
|
|
virtual ~LuaWidget()
|
|
{
|
|
luaL_unref(lsWidgets, LUA_REGISTRYINDEX, widgetData);
|
|
}
|
|
|
|
virtual void update() const;
|
|
|
|
virtual void refresh();
|
|
|
|
virtual void background();
|
|
|
|
protected:
|
|
int widgetData;
|
|
};
|
|
|
|
void l_pushtableint(const char * key, int value)
|
|
{
|
|
lua_pushstring(lsWidgets, key);
|
|
lua_pushinteger(lsWidgets, value);
|
|
lua_settable(lsWidgets, -3);
|
|
}
|
|
|
|
class LuaWidgetFactory: public WidgetFactory
|
|
{
|
|
friend void luaLoadWidgetCallback();
|
|
friend class LuaWidget;
|
|
|
|
public:
|
|
LuaWidgetFactory(const char * name, int widgetOptions, int createFunction):
|
|
WidgetFactory(name, createOptionsArray(widgetOptions)),
|
|
createFunction(createFunction),
|
|
updateFunction(0),
|
|
refreshFunction(0),
|
|
backgroundFunction(0)
|
|
{
|
|
}
|
|
|
|
virtual Widget * create(const Zone & zone, Widget::PersistentData * persistentData, bool init=true) const
|
|
{
|
|
if (lsWidgets == 0) return 0;
|
|
if (init) {
|
|
initPersistentData(persistentData);
|
|
}
|
|
|
|
luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, createFunction);
|
|
|
|
lua_newtable(lsWidgets);
|
|
l_pushtableint("x", zone.x);
|
|
l_pushtableint("y", zone.y);
|
|
l_pushtableint("w", zone.w);
|
|
l_pushtableint("h", zone.h);
|
|
|
|
lua_newtable(lsWidgets);
|
|
int i = 0;
|
|
for (const ZoneOption * option = options; option->name; option++, i++) {
|
|
l_pushtableint(option->name, persistentData->options[i].signedValue);
|
|
}
|
|
|
|
if (lua_pcall(lsWidgets, 2, 1, 0) != 0) {
|
|
TRACE("Error in widget %s create() function: %s", getName(), lua_tostring(lsWidgets, -1));
|
|
}
|
|
int widgetData = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
|
|
Widget * widget = new LuaWidget(this, zone, persistentData, widgetData);
|
|
return widget;
|
|
}
|
|
|
|
protected:
|
|
int createFunction;
|
|
int updateFunction;
|
|
int refreshFunction;
|
|
int backgroundFunction;
|
|
};
|
|
|
|
void LuaWidget::update() const
|
|
{
|
|
if (lsWidgets == 0) return;
|
|
|
|
luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
|
|
LuaWidgetFactory * factory = (LuaWidgetFactory *)this->factory;
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, factory->updateFunction);
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, widgetData);
|
|
|
|
lua_newtable(lsWidgets);
|
|
int i = 0;
|
|
for (const ZoneOption * option = getOptions(); option->name; option++, i++) {
|
|
l_pushtableint(option->name, persistentData->options[i].signedValue);
|
|
}
|
|
|
|
if (lua_pcall(lsWidgets, 2, 0, 0) != 0) {
|
|
TRACE("Error in widget %s update() function: %s", factory->getName(), lua_tostring(lsWidgets, -1));
|
|
}
|
|
}
|
|
|
|
void LuaWidget::refresh()
|
|
{
|
|
if (lsWidgets == 0) return;
|
|
|
|
luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
|
|
LuaWidgetFactory * factory = (LuaWidgetFactory *)this->factory;
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, factory->refreshFunction);
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, widgetData);
|
|
if (lua_pcall(lsWidgets, 1, 0, 0) != 0) {
|
|
TRACE("Error in widget %s refresh() function: %s", factory->getName(), lua_tostring(lsWidgets, -1));
|
|
}
|
|
}
|
|
|
|
void LuaWidget::background()
|
|
{
|
|
if (lsWidgets == 0) return;
|
|
|
|
luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
|
|
LuaWidgetFactory * factory = (LuaWidgetFactory *)this->factory;
|
|
if (factory->backgroundFunction) {
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, factory->backgroundFunction);
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, widgetData);
|
|
if (lua_pcall(lsWidgets, 1, 0, 0) != 0) {
|
|
TRACE("Error in widget %s background() function: %s", factory->getName(), lua_tostring(lsWidgets, -1));
|
|
}
|
|
}
|
|
}
|
|
|
|
void luaLoadWidgetCallback()
|
|
{
|
|
TRACE("luaLoadWidgetCallback()");
|
|
const char * name=NULL;
|
|
int widgetOptions=0, createFunction=0, updateFunction=0, refreshFunction=0, backgroundFunction=0;
|
|
|
|
luaL_checktype(lsWidgets, -1, LUA_TTABLE);
|
|
|
|
for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
|
|
const char * key = lua_tostring(lsWidgets, -2);
|
|
if (!strcmp(key, "name")) {
|
|
name = luaL_checkstring(lsWidgets, -1);
|
|
}
|
|
else if (!strcmp(key, "options")) {
|
|
widgetOptions = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
|
|
lua_pushnil(lsWidgets);
|
|
}
|
|
else if (!strcmp(key, "create")) {
|
|
createFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
|
|
lua_pushnil(lsWidgets);
|
|
}
|
|
else if (!strcmp(key, "update")) {
|
|
updateFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
|
|
lua_pushnil(lsWidgets);
|
|
}
|
|
else if (!strcmp(key, "refresh")) {
|
|
refreshFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
|
|
lua_pushnil(lsWidgets);
|
|
}
|
|
else if (!strcmp(key, "background")) {
|
|
backgroundFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
|
|
lua_pushnil(lsWidgets);
|
|
}
|
|
}
|
|
|
|
if (name && createFunction) {
|
|
LuaWidgetFactory * factory = new LuaWidgetFactory(name, widgetOptions, createFunction);
|
|
factory->updateFunction = updateFunction;
|
|
factory->refreshFunction = refreshFunction;
|
|
factory->backgroundFunction = backgroundFunction;
|
|
TRACE("Loaded Lua widget %s", name);
|
|
}
|
|
}
|
|
|
|
void luaLoadFile(const char * filename, void (*callback)())
|
|
{
|
|
if (lsWidgets == NULL || callback == NULL)
|
|
return;
|
|
|
|
TRACE("luaLoadFile(%s)", filename);
|
|
|
|
luaSetInstructionsLimit(lsWidgets, MANUAL_SCRIPTS_MAX_INSTRUCTIONS);
|
|
|
|
PROTECT_LUA() {
|
|
if (luaLoadScriptFileToState(lsWidgets, filename, LUA_SCRIPT_LOAD_MODE) == SCRIPT_OK) {
|
|
if (lua_pcall(lsWidgets, 0, 1, 0) == LUA_OK && lua_istable(lsWidgets, -1)) {
|
|
(*callback)();
|
|
}
|
|
else {
|
|
TRACE("luaLoadFile(%s): Error parsing script: %s", filename, lua_tostring(lsWidgets, -1));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// luaDisable();
|
|
lsWidgets = 0;
|
|
return;
|
|
}
|
|
UNPROTECT_LUA();
|
|
}
|
|
|
|
void luaLoadFiles(const char * directory, void (*callback)())
|
|
{
|
|
char path[LUA_FULLPATH_MAXLEN+1];
|
|
FILINFO fno;
|
|
DIR dir;
|
|
|
|
strcpy(path, directory);
|
|
TRACE("luaLoadFiles() %s", path);
|
|
|
|
FRESULT res = f_opendir(&dir, path); /* Open the directory */
|
|
|
|
if (res == FR_OK) {
|
|
int pathlen = strlen(path);
|
|
path[pathlen++] = '/';
|
|
for (;;) {
|
|
res = f_readdir(&dir, &fno); /* Read a directory item */
|
|
if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */
|
|
uint8_t len = strlen(fno.fname);
|
|
if (len > 0 && fno.fname[0]!='.' && (fno.fattrib & AM_DIR)) {
|
|
strcpy(&path[pathlen], fno.fname);
|
|
strcat(&path[pathlen], "/main.lua");
|
|
if (isFileAvailable(path)) {
|
|
luaLoadFile(path, callback);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
TRACE("f_opendir(%s) failed, code=%d", path, res);
|
|
}
|
|
|
|
f_closedir(&dir);
|
|
}
|
|
|
|
void luaInitThemesAndWidgets()
|
|
{
|
|
TRACE("luaInitThemesAndWidgets");
|
|
|
|
#if defined(USE_BIN_ALLOCATOR)
|
|
lsWidgets = lua_newstate(bin_l_alloc, NULL); //we use our own allocator!
|
|
#else
|
|
lsWidgets = lua_newstate(l_alloc, NULL); //we use Lua default allocator
|
|
#endif
|
|
if (lsWidgets) {
|
|
// install our panic handler
|
|
lua_atpanic(lsWidgets, &custom_lua_atpanic);
|
|
|
|
// protect libs and constants registration
|
|
PROTECT_LUA() {
|
|
luaRegisterLibraries(lsWidgets);
|
|
}
|
|
else {
|
|
// if we got panic during registration
|
|
// we disable Lua for this session
|
|
// luaDisable();
|
|
lsWidgets = 0;
|
|
}
|
|
UNPROTECT_LUA();
|
|
TRACE("lsWidgets %p", lsWidgets);
|
|
luaLoadFiles(THEMES_PATH, luaLoadThemeCallback);
|
|
luaLoadFiles(WIDGETS_PATH, luaLoadWidgetCallback);
|
|
}
|
|
}
|