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.
24 #include "bin_allocator.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;
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
)
54 // TRACE("createOptionsArray() no options");
59 lua_rawgeti(lsWidgets
, LUA_REGISTRYINDEX
, reference
);
60 for (lua_pushnil(lsWidgets
); lua_next(lsWidgets
, -2); lua_pop(lsWidgets
, 1)) {
64 // TRACE("we have %d options", count);
65 if (count
> maxOptions
) {
67 // TRACE("limited to %d options", count);
70 ZoneOption
* options
= (ZoneOption
*)malloc(sizeof(ZoneOption
) * (count
+1));
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
83 for (lua_pushnil(lsWidgets
); lua_next(lsWidgets
, -2) && field
<5; lua_pop(lsWidgets
, 1), field
++) {
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);
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
) {
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;
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));
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);
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);
149 option
->name
= NULL
; // sentinel
152 TRACE("error in theme/widget options");
160 class LuaTheme
: public Theme
162 friend void luaLoadThemeCallback();
165 LuaTheme(const char * name
, ZoneOption
* options
):
166 Theme(name
, options
),
168 drawBackgroundFunction(0),
169 drawTopbarBackgroundFunction(0),
170 drawAlertBoxFunction(0)
174 virtual void load() const
176 luaLcdAllowed
= true;
180 virtual void drawBackground() const
182 exec(drawBackgroundFunction
);
185 virtual void drawTopbarBackground(uint8_t icon
) const
187 exec(drawTopbarBackgroundFunction
);
191 virtual void drawAlertBox(const char * title
, const char * text
, const char * action
) const
193 exec(drawAlertBoxFunction
);
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
);
236 ZoneOption
* options
= NULL
;
238 options
= createOptionsArray(themeOptions
, MAX_THEME_OPTIONS
);
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
253 LuaWidget(const WidgetFactory
* factory
, const Zone
& zone
, Widget::PersistentData
* persistentData
, int widgetData
):
254 Widget(factory
, zone
, persistentData
),
255 widgetData(widgetData
),
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;
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
;
294 LuaWidgetFactory(const char * name
, ZoneOption
* widgetOptions
, int createFunction
):
295 WidgetFactory(name
, widgetOptions
),
296 createFunction(createFunction
),
299 backgroundFunction(0)
303 virtual Widget
* create(const Zone
& zone
, Widget::PersistentData
* persistentData
, bool init
=true) const
305 if (lsWidgets
== 0) return 0;
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
);
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
);
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
);
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
);
367 snprintf(errorMessage
, needed
, "%s: %s", funcName
, lua_tostring(lsWidgets
, -1));
371 const char * LuaWidget::getErrorMessage() const
376 void LuaWidget::refresh()
378 if (lsWidgets
== 0) return;
382 lcdDrawText(zone
.x
, zone
.y
, "Disabled", SMLSIZE
|CUSTOM_COLOR
);
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
);
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
)
462 TRACE("luaLoadFile(%s)", filename
);
464 luaSetInstructionsLimit(lsWidgets
, MANUAL_SCRIPTS_MAX_INSTRUCTIONS
);
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)) {
472 TRACE("luaLoadFile(%s): Error parsing script: %s", filename
, lua_tostring(lsWidgets
, -1));
477 // error while loading Lua widget/theme,
478 // do not disable whole Lua state, just ingnore bad widget/theme
484 void luaLoadFiles(const char * directory
, void (*callback
)())
486 char path
[LUA_FULLPATH_MAXLEN
+1];
490 strcpy(path
, directory
);
491 TRACE("luaLoadFiles() %s", path
);
493 FRESULT res
= f_opendir(&dir
, path
); /* Open the directory */
496 int pathlen
= strlen(path
);
497 path
[pathlen
++] = '/';
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
);
513 TRACE("f_opendir(%s) failed, code=%d", path
, res
);
519 #if defined(LUA_ALLOCATOR_TRACER)
520 LuaMemTracer lsWidgetsTrace
;
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
534 lsWidgets
= lua_newstate(l_alloc
, NULL
); //we use Lua default allocator
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);
544 // protect libs and constants registration
546 luaRegisterLibraries(lsWidgets
);
549 // if we got panic during registration
550 // we disable Lua for this session
552 luaClose(&lsWidgets
);
556 TRACE("lsWidgets %p", lsWidgets
);
557 luaLoadFiles(THEMES_PATH
, luaLoadThemeCallback
);
558 luaLoadFiles(WIDGETS_PATH
, luaLoadWidgetCallback
);
559 luaDoGc(lsWidgets
, true);