Improve multi (#7136)
[opentx.git] / radio / src / lua / widgets.cpp
blob165d95eff95a98cb78865780c5025212bfcd3495
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include <ctype.h>
22 #include <stdio.h>
23 #include "opentx.h"
24 #include "bin_allocator.h"
25 #include "lua_api.h"
27 #define WIDGET_SCRIPTS_MAX_INSTRUCTIONS (10000/100)
28 #define MANUAL_SCRIPTS_MAX_INSTRUCTIONS (20000/100)
29 #define LUA_WARNING_INFO_LEN 64
31 lua_State *lsWidgets = NULL;
32 extern int custom_lua_atpanic(lua_State *L);
34 #define LUA_WIDGET_FILENAME "/main.lua"
35 #define LUA_FULLPATH_MAXLEN (LEN_FILE_PATH_MAX + LEN_SCRIPT_FILENAME + LEN_FILE_EXTENSION_MAX) // max length (example: /SCRIPTS/THEMES/mytheme.lua)
37 void exec(int function, int nresults=0)
39 if (lsWidgets == 0) return;
41 if (function) {
42 luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
43 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, function);
44 if (lua_pcall(lsWidgets, 0, nresults, 0) != 0) {
45 TRACE("Error in theme %s", lua_tostring(lsWidgets, -1));
46 // TODO disable theme - revert back to default theme???
51 ZoneOption * createOptionsArray(int reference, uint8_t maxOptions)
53 if (reference == 0) {
54 // TRACE("createOptionsArray() no options");
55 return NULL;
58 int count = 0;
59 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, reference);
60 for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
61 count++;
64 // TRACE("we have %d options", count);
65 if (count > maxOptions) {
66 count = maxOptions;
67 // TRACE("limited to %d options", count);
70 ZoneOption * options = (ZoneOption *)malloc(sizeof(ZoneOption) * (count+1));
71 if (!options) {
72 return NULL;
75 PROTECT_LUA() {
76 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, reference);
77 ZoneOption * option = options;
78 for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2), count-- > 0; lua_pop(lsWidgets, 1)) {
79 // TRACE("parsing option %d", count);
80 luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
81 luaL_checktype(lsWidgets, -1, LUA_TTABLE); // value is table
82 uint8_t field = 0;
83 for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2) && field<5; lua_pop(lsWidgets, 1), field++) {
84 switch (field) {
85 case 0:
86 luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
87 luaL_checktype(lsWidgets, -1, LUA_TSTRING); // value is string
88 option->name = lua_tostring(lsWidgets, -1);
89 // TRACE("name = %s", option->name);
90 break;
91 case 1:
92 luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
93 luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
94 option->type = (ZoneOption::Type)lua_tointeger(lsWidgets, -1);
95 // TRACE("type = %d", option->type);
96 if (option->type > ZoneOption::Color) {
97 // wrong type
98 option->type = ZoneOption::Integer;
100 if (option->type == ZoneOption::Integer) {
101 // set some sensible defaults (only Integer actually uses them)
102 option->deflt.signedValue = 0;
103 option->min.signedValue = -100;
104 option->max.signedValue = 100;
106 break;
107 case 2:
108 luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
109 if (option->type == ZoneOption::Integer) {
110 luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
111 option->deflt.signedValue = lua_tointeger(lsWidgets, -1);
112 // TRACE("default signed = %d", option->deflt.signedValue);
114 else if (option->type == ZoneOption::Source ||
115 option->type == ZoneOption::TextSize ||
116 option->type == ZoneOption::Color) {
117 luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
118 option->deflt.unsignedValue = lua_tounsigned(lsWidgets, -1);
119 // TRACE("default unsigned = %u", option->deflt.unsignedValue);
121 else if (option->type == ZoneOption::Bool) {
122 luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
123 option->deflt.boolValue = (lua_tounsigned(lsWidgets, -1) != 0);
124 // TRACE("default bool = %d", (int)(option->deflt.boolValue));
126 else if (option->type == ZoneOption::String) {
127 str2zchar(option->deflt.stringValue, lua_tostring(lsWidgets, -1), sizeof(option->deflt.stringValue)); // stringValue is ZCHAR
128 // TRACE("default string = %s", lua_tostring(lsWidgets, -1));
130 break;
131 case 3:
132 if (option->type == ZoneOption::Integer) {
133 luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
134 luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
135 option->min.signedValue = lua_tointeger(lsWidgets, -1);
137 break;
138 case 4:
139 if (option->type == ZoneOption::Integer) {
140 luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
141 luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
142 option->max.signedValue = lua_tointeger(lsWidgets, -1);
144 break;
147 option++;
149 option->name = NULL; // sentinel
151 else {
152 TRACE("error in theme/widget options");
153 free(options);
154 return NULL;
156 UNPROTECT_LUA();
157 return options;
160 class LuaTheme: public Theme
162 friend void luaLoadThemeCallback();
164 public:
165 LuaTheme(const char * name, ZoneOption * options):
166 Theme(name, options),
167 loadFunction(0),
168 drawBackgroundFunction(0),
169 drawTopbarBackgroundFunction(0),
170 drawAlertBoxFunction(0)
174 virtual void load() const
176 luaLcdAllowed = true;
177 exec(loadFunction);
180 virtual void drawBackground() const
182 exec(drawBackgroundFunction);
185 virtual void drawTopbarBackground(uint8_t icon) const
187 exec(drawTopbarBackgroundFunction);
190 #if 0
191 virtual void drawAlertBox(const char * title, const char * text, const char * action) const
193 exec(drawAlertBoxFunction);
195 #endif
197 protected:
198 int loadFunction;
199 int drawBackgroundFunction;
200 int drawTopbarBackgroundFunction;
201 int drawAlertBoxFunction;
204 void luaLoadThemeCallback()
206 TRACE("luaLoadThemeCallback()");
207 const char * name=NULL;
208 int themeOptions=0, loadFunction=0, drawBackgroundFunction=0, drawTopbarBackgroundFunction=0;
210 luaL_checktype(lsWidgets, -1, LUA_TTABLE);
212 for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
213 const char * key = lua_tostring(lsWidgets, -2);
214 if (!strcmp(key, "name")) {
215 name = luaL_checkstring(lsWidgets, -1);
217 else if (!strcmp(key, "options")) {
218 themeOptions = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
219 lua_pushnil(lsWidgets);
221 else if (!strcmp(key, "load")) {
222 loadFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
223 lua_pushnil(lsWidgets);
225 else if (!strcmp(key, "drawBackground")) {
226 drawBackgroundFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
227 lua_pushnil(lsWidgets);
229 else if (!strcmp(key, "drawTopbarBackground")) {
230 drawTopbarBackgroundFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
231 lua_pushnil(lsWidgets);
235 if (name) {
236 ZoneOption * options = NULL;
237 if (themeOptions) {
238 options = createOptionsArray(themeOptions, MAX_THEME_OPTIONS);
239 if (!options)
240 return;
242 LuaTheme * theme = new LuaTheme(name, options);
243 theme->loadFunction = loadFunction;
244 theme->drawBackgroundFunction = drawBackgroundFunction;
245 theme->drawTopbarBackgroundFunction = drawTopbarBackgroundFunction; // NOSONAR
246 TRACE("Loaded Lua theme %s", name);
250 class LuaWidget: public Widget
252 public:
253 LuaWidget(const WidgetFactory * factory, const Zone & zone, Widget::PersistentData * persistentData, int widgetData):
254 Widget(factory, zone, persistentData),
255 widgetData(widgetData),
256 errorMessage(0)
260 virtual ~LuaWidget()
262 luaL_unref(lsWidgets, LUA_REGISTRYINDEX, widgetData);
263 if (errorMessage) free(errorMessage);
266 virtual void update();
268 virtual void refresh();
270 virtual void background();
272 virtual const char * getErrorMessage() const;
274 protected:
275 int widgetData;
276 char * errorMessage;
278 void setErrorMessage(const char * funcName);
281 void l_pushtableint(const char * key, int value)
283 lua_pushstring(lsWidgets, key);
284 lua_pushinteger(lsWidgets, value);
285 lua_settable(lsWidgets, -3);
288 class LuaWidgetFactory: public WidgetFactory
290 friend void luaLoadWidgetCallback();
291 friend class LuaWidget;
293 public:
294 LuaWidgetFactory(const char * name, ZoneOption * widgetOptions, int createFunction):
295 WidgetFactory(name, widgetOptions),
296 createFunction(createFunction),
297 updateFunction(0),
298 refreshFunction(0),
299 backgroundFunction(0)
303 virtual Widget * create(const Zone & zone, Widget::PersistentData * persistentData, bool init=true) const
305 if (lsWidgets == 0) return 0;
306 if (init) {
307 initPersistentData(persistentData);
310 luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
311 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, createFunction);
313 lua_newtable(lsWidgets);
314 l_pushtableint("x", zone.x);
315 l_pushtableint("y", zone.y);
316 l_pushtableint("w", zone.w);
317 l_pushtableint("h", zone.h);
319 lua_newtable(lsWidgets);
320 int i = 0;
321 for (const ZoneOption * option = options; option->name; option++, i++) {
322 l_pushtableint(option->name, persistentData->options[i].signedValue);
325 if (lua_pcall(lsWidgets, 2, 1, 0) != 0) {
326 TRACE("Error in widget %s create() function: %s", getName(), lua_tostring(lsWidgets, -1));
328 int widgetData = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
329 Widget * widget = new LuaWidget(this, zone, persistentData, widgetData);
330 return widget;
333 protected:
334 int createFunction;
335 int updateFunction;
336 int refreshFunction;
337 int backgroundFunction;
340 void LuaWidget::update()
342 if (lsWidgets == 0 || errorMessage) return;
344 luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
345 LuaWidgetFactory * factory = (LuaWidgetFactory *)this->factory;
346 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, factory->updateFunction);
347 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, widgetData);
349 lua_newtable(lsWidgets);
350 int i = 0;
351 for (const ZoneOption * option = getOptions(); option->name; option++, i++) {
352 l_pushtableint(option->name, persistentData->options[i].signedValue);
355 if (lua_pcall(lsWidgets, 2, 0, 0) != 0) {
356 setErrorMessage("update()");
360 void LuaWidget::setErrorMessage(const char * funcName)
362 TRACE("Error in widget %s %s function: %s", factory->getName(), funcName, lua_tostring(lsWidgets, -1));
363 TRACE("Widget disabled");
364 size_t needed = snprintf(NULL, 0, "%s: %s", funcName, lua_tostring(lsWidgets, -1)) + 1;
365 errorMessage = (char *)malloc(needed);
366 if (errorMessage) {
367 snprintf(errorMessage, needed, "%s: %s", funcName, lua_tostring(lsWidgets, -1));
371 const char * LuaWidget::getErrorMessage() const
373 return errorMessage;
376 void LuaWidget::refresh()
378 if (lsWidgets == 0) return;
380 if (errorMessage) {
381 lcdSetColor(RED);
382 lcdDrawText(zone.x, zone.y, "Disabled", SMLSIZE|CUSTOM_COLOR);
383 return;
386 luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
387 LuaWidgetFactory * factory = (LuaWidgetFactory *)this->factory;
388 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, factory->refreshFunction);
389 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, widgetData);
390 if (lua_pcall(lsWidgets, 1, 0, 0) != 0) {
391 setErrorMessage("refresh()");
395 void LuaWidget::background()
397 if (lsWidgets == 0 || errorMessage) return;
399 luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
400 LuaWidgetFactory * factory = (LuaWidgetFactory *)this->factory;
401 if (factory->backgroundFunction) {
402 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, factory->backgroundFunction);
403 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, widgetData);
404 if (lua_pcall(lsWidgets, 1, 0, 0) != 0) {
405 setErrorMessage("background()");
410 void luaLoadWidgetCallback()
412 TRACE("luaLoadWidgetCallback()");
413 const char * name=NULL;
414 int widgetOptions=0, createFunction=0, updateFunction=0, refreshFunction=0, backgroundFunction=0;
416 luaL_checktype(lsWidgets, -1, LUA_TTABLE);
418 for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
419 const char * key = lua_tostring(lsWidgets, -2);
420 if (!strcmp(key, "name")) {
421 name = luaL_checkstring(lsWidgets, -1);
423 else if (!strcmp(key, "options")) {
424 widgetOptions = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
425 lua_pushnil(lsWidgets);
427 else if (!strcmp(key, "create")) {
428 createFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
429 lua_pushnil(lsWidgets);
431 else if (!strcmp(key, "update")) {
432 updateFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
433 lua_pushnil(lsWidgets);
435 else if (!strcmp(key, "refresh")) {
436 refreshFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
437 lua_pushnil(lsWidgets);
439 else if (!strcmp(key, "background")) {
440 backgroundFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
441 lua_pushnil(lsWidgets);
445 if (name && createFunction) {
446 ZoneOption * options = createOptionsArray(widgetOptions, MAX_WIDGET_OPTIONS);
447 if (options) {
448 LuaWidgetFactory * factory = new LuaWidgetFactory(name, options, createFunction);
449 factory->updateFunction = updateFunction;
450 factory->refreshFunction = refreshFunction;
451 factory->backgroundFunction = backgroundFunction; // NOSONAR
452 TRACE("Loaded Lua widget %s", name);
457 void luaLoadFile(const char * filename, void (*callback)())
459 if (lsWidgets == NULL || callback == NULL)
460 return;
462 TRACE("luaLoadFile(%s)", filename);
464 luaSetInstructionsLimit(lsWidgets, MANUAL_SCRIPTS_MAX_INSTRUCTIONS);
466 PROTECT_LUA() {
467 if (luaLoadScriptFileToState(lsWidgets, filename, LUA_SCRIPT_LOAD_MODE) == SCRIPT_OK) {
468 if (lua_pcall(lsWidgets, 0, 1, 0) == LUA_OK && lua_istable(lsWidgets, -1)) {
469 (*callback)();
471 else {
472 TRACE("luaLoadFile(%s): Error parsing script: %s", filename, lua_tostring(lsWidgets, -1));
476 else {
477 // error while loading Lua widget/theme,
478 // do not disable whole Lua state, just ingnore bad widget/theme
479 return;
481 UNPROTECT_LUA();
484 void luaLoadFiles(const char * directory, void (*callback)())
486 char path[LUA_FULLPATH_MAXLEN+1];
487 FILINFO fno;
488 DIR dir;
490 strcpy(path, directory);
491 TRACE("luaLoadFiles() %s", path);
493 FRESULT res = f_opendir(&dir, path); /* Open the directory */
495 if (res == FR_OK) {
496 int pathlen = strlen(path);
497 path[pathlen++] = '/';
498 for (;;) {
499 res = f_readdir(&dir, &fno); /* Read a directory item */
500 if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */
501 uint8_t len = strlen(fno.fname);
502 if (len > 0 && (unsigned int)(len + pathlen + sizeof(LUA_WIDGET_FILENAME)) <= sizeof(path) &&
503 fno.fname[0]!='.' && (fno.fattrib & AM_DIR)) {
504 strcpy(&path[pathlen], fno.fname);
505 strcat(&path[pathlen], LUA_WIDGET_FILENAME);
506 if (isFileAvailable(path)) {
507 luaLoadFile(path, callback);
512 else {
513 TRACE("f_opendir(%s) failed, code=%d", path, res);
516 f_closedir(&dir);
519 #if defined(LUA_ALLOCATOR_TRACER)
520 LuaMemTracer lsWidgetsTrace;
521 #endif
523 void luaInitThemesAndWidgets()
525 TRACE("luaInitThemesAndWidgets");
527 #if defined(USE_BIN_ALLOCATOR)
528 lsWidgets = lua_newstate(bin_l_alloc, NULL); //we use our own allocator!
529 #elif defined(LUA_ALLOCATOR_TRACER)
530 memset(&lsWidgetsTrace, 0 , sizeof(lsWidgetsTrace));
531 lsWidgetsTrace.script = "lua_newstate(widgets)";
532 lsWidgets = lua_newstate(tracer_alloc, &lsWidgetsTrace); //we use tracer allocator
533 #else
534 lsWidgets = lua_newstate(l_alloc, NULL); //we use Lua default allocator
535 #endif
536 if (lsWidgets) {
537 // install our panic handler
538 lua_atpanic(lsWidgets, &custom_lua_atpanic);
540 #if defined(LUA_ALLOCATOR_TRACER)
541 lua_sethook(lsWidgets, luaHook, LUA_MASKLINE, 0);
542 #endif
544 // protect libs and constants registration
545 PROTECT_LUA() {
546 luaRegisterLibraries(lsWidgets);
548 else {
549 // if we got panic during registration
550 // we disable Lua for this session
551 // luaDisable();
552 luaClose(&lsWidgets);
553 lsWidgets = 0;
555 UNPROTECT_LUA();
556 TRACE("lsWidgets %p", lsWidgets);
557 luaLoadFiles(THEMES_PATH, luaLoadThemeCallback);
558 luaLoadFiles(WIDGETS_PATH, luaLoadWidgetCallback);
559 luaDoGc(lsWidgets, true);