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::Source
||
117 option
->type
== ZoneOption::Color
) {
118 luaL_checktype(lsWidgets
, -1, LUA_TNUMBER
); // value is number
119 option
->deflt
.unsignedValue
= lua_tounsigned(lsWidgets
, -1);
120 // TRACE("default unsigned = %u", option->deflt.unsignedValue);
122 else if (option
->type
== ZoneOption::Bool
) {
123 luaL_checktype(lsWidgets
, -1, LUA_TNUMBER
); // value is number
124 option
->deflt
.boolValue
= (lua_tounsigned(lsWidgets
, -1) != 0);
125 // TRACE("default bool = %d", (int)(option->deflt.boolValue));
127 else if (option
->type
== ZoneOption::String
) {
128 str2zchar(option
->deflt
.stringValue
, lua_tostring(lsWidgets
, -1), sizeof(option
->deflt
.stringValue
)); // stringValue is ZCHAR
129 // TRACE("default string = %s", lua_tostring(lsWidgets, -1));
133 if (option
->type
== ZoneOption::Integer
) {
134 luaL_checktype(lsWidgets
, -2, LUA_TNUMBER
); // key is number
135 luaL_checktype(lsWidgets
, -1, LUA_TNUMBER
); // value is number
136 option
->min
.signedValue
= lua_tointeger(lsWidgets
, -1);
140 if (option
->type
== ZoneOption::Integer
) {
141 luaL_checktype(lsWidgets
, -2, LUA_TNUMBER
); // key is number
142 luaL_checktype(lsWidgets
, -1, LUA_TNUMBER
); // value is number
143 option
->max
.signedValue
= lua_tointeger(lsWidgets
, -1);
150 option
->name
= NULL
; // sentinel
153 TRACE("error in theme/widget options");
161 class LuaTheme
: public Theme
163 friend void luaLoadThemeCallback();
166 LuaTheme(const char * name
, ZoneOption
* options
):
167 Theme(name
, options
),
169 drawBackgroundFunction(0),
170 drawTopbarBackgroundFunction(0),
171 drawAlertBoxFunction(0)
175 virtual void load() const
177 luaLcdAllowed
= true;
181 virtual void drawBackground() const
183 exec(drawBackgroundFunction
);
186 virtual void drawTopbarBackground(uint8_t icon
) const
188 exec(drawTopbarBackgroundFunction
);
192 virtual void drawAlertBox(const char * title
, const char * text
, const char * action
) const
194 exec(drawAlertBoxFunction
);
200 int drawBackgroundFunction
;
201 int drawTopbarBackgroundFunction
;
202 int drawAlertBoxFunction
;
205 void luaLoadThemeCallback()
207 TRACE("luaLoadThemeCallback()");
208 const char * name
=NULL
;
209 int themeOptions
=0, loadFunction
=0, drawBackgroundFunction
=0, drawTopbarBackgroundFunction
=0;
211 luaL_checktype(lsWidgets
, -1, LUA_TTABLE
);
213 for (lua_pushnil(lsWidgets
); lua_next(lsWidgets
, -2); lua_pop(lsWidgets
, 1)) {
214 const char * key
= lua_tostring(lsWidgets
, -2);
215 if (!strcmp(key
, "name")) {
216 name
= luaL_checkstring(lsWidgets
, -1);
218 else if (!strcmp(key
, "options")) {
219 themeOptions
= luaL_ref(lsWidgets
, LUA_REGISTRYINDEX
);
220 lua_pushnil(lsWidgets
);
222 else if (!strcmp(key
, "load")) {
223 loadFunction
= luaL_ref(lsWidgets
, LUA_REGISTRYINDEX
);
224 lua_pushnil(lsWidgets
);
226 else if (!strcmp(key
, "drawBackground")) {
227 drawBackgroundFunction
= luaL_ref(lsWidgets
, LUA_REGISTRYINDEX
);
228 lua_pushnil(lsWidgets
);
230 else if (!strcmp(key
, "drawTopbarBackground")) {
231 drawTopbarBackgroundFunction
= luaL_ref(lsWidgets
, LUA_REGISTRYINDEX
);
232 lua_pushnil(lsWidgets
);
237 ZoneOption
* options
= createOptionsArray(themeOptions
, MAX_THEME_OPTIONS
);
239 LuaTheme
* theme
= new LuaTheme(name
, options
);
240 theme
->loadFunction
= loadFunction
;
241 theme
->drawBackgroundFunction
= drawBackgroundFunction
;
242 theme
->drawTopbarBackgroundFunction
= drawTopbarBackgroundFunction
;
243 TRACE("Loaded Lua theme %s", name
);
248 class LuaWidget
: public Widget
251 LuaWidget(const WidgetFactory
* factory
, const Zone
& zone
, Widget::PersistentData
* persistentData
, int widgetData
):
252 Widget(factory
, zone
, persistentData
),
253 widgetData(widgetData
),
260 luaL_unref(lsWidgets
, LUA_REGISTRYINDEX
, widgetData
);
261 if (errorMessage
) free(errorMessage
);
264 virtual void update();
266 virtual void refresh();
268 virtual void background();
270 virtual const char * getErrorMessage() const;
276 void setErrorMessage(const char * funcName
);
279 void l_pushtableint(const char * key
, int value
)
281 lua_pushstring(lsWidgets
, key
);
282 lua_pushinteger(lsWidgets
, value
);
283 lua_settable(lsWidgets
, -3);
286 class LuaWidgetFactory
: public WidgetFactory
288 friend void luaLoadWidgetCallback();
289 friend class LuaWidget
;
292 LuaWidgetFactory(const char * name
, ZoneOption
* widgetOptions
, int createFunction
):
293 WidgetFactory(name
, widgetOptions
),
294 createFunction(createFunction
),
297 backgroundFunction(0)
301 virtual Widget
* create(const Zone
& zone
, Widget::PersistentData
* persistentData
, bool init
=true) const
303 if (lsWidgets
== 0) return 0;
305 initPersistentData(persistentData
);
308 luaSetInstructionsLimit(lsWidgets
, WIDGET_SCRIPTS_MAX_INSTRUCTIONS
);
309 lua_rawgeti(lsWidgets
, LUA_REGISTRYINDEX
, createFunction
);
311 lua_newtable(lsWidgets
);
312 l_pushtableint("x", zone
.x
);
313 l_pushtableint("y", zone
.y
);
314 l_pushtableint("w", zone
.w
);
315 l_pushtableint("h", zone
.h
);
317 lua_newtable(lsWidgets
);
319 for (const ZoneOption
* option
= options
; option
->name
; option
++, i
++) {
320 l_pushtableint(option
->name
, persistentData
->options
[i
].signedValue
);
323 if (lua_pcall(lsWidgets
, 2, 1, 0) != 0) {
324 TRACE("Error in widget %s create() function: %s", getName(), lua_tostring(lsWidgets
, -1));
326 int widgetData
= luaL_ref(lsWidgets
, LUA_REGISTRYINDEX
);
327 Widget
* widget
= new LuaWidget(this, zone
, persistentData
, widgetData
);
335 int backgroundFunction
;
338 void LuaWidget::update()
340 if (lsWidgets
== 0 || errorMessage
) return;
342 luaSetInstructionsLimit(lsWidgets
, WIDGET_SCRIPTS_MAX_INSTRUCTIONS
);
343 LuaWidgetFactory
* factory
= (LuaWidgetFactory
*)this->factory
;
344 lua_rawgeti(lsWidgets
, LUA_REGISTRYINDEX
, factory
->updateFunction
);
345 lua_rawgeti(lsWidgets
, LUA_REGISTRYINDEX
, widgetData
);
347 lua_newtable(lsWidgets
);
349 for (const ZoneOption
* option
= getOptions(); option
->name
; option
++, i
++) {
350 l_pushtableint(option
->name
, persistentData
->options
[i
].signedValue
);
353 if (lua_pcall(lsWidgets
, 2, 0, 0) != 0) {
354 setErrorMessage("update()");
358 void LuaWidget::setErrorMessage(const char * funcName
)
360 TRACE("Error in widget %s %s function: %s", factory
->getName(), funcName
, lua_tostring(lsWidgets
, -1));
361 TRACE("Widget disabled");
362 size_t needed
= snprintf(NULL
, 0, "%s: %s", funcName
, lua_tostring(lsWidgets
, -1)) + 1;
363 errorMessage
= (char *)malloc(needed
);
365 snprintf(errorMessage
, needed
, "%s: %s", funcName
, lua_tostring(lsWidgets
, -1));
369 const char * LuaWidget::getErrorMessage() const
374 void LuaWidget::refresh()
376 if (lsWidgets
== 0) return;
380 lcdDrawText(zone
.x
, zone
.y
, "Disabled", SMLSIZE
|CUSTOM_COLOR
);
384 luaSetInstructionsLimit(lsWidgets
, WIDGET_SCRIPTS_MAX_INSTRUCTIONS
);
385 LuaWidgetFactory
* factory
= (LuaWidgetFactory
*)this->factory
;
386 lua_rawgeti(lsWidgets
, LUA_REGISTRYINDEX
, factory
->refreshFunction
);
387 lua_rawgeti(lsWidgets
, LUA_REGISTRYINDEX
, widgetData
);
388 if (lua_pcall(lsWidgets
, 1, 0, 0) != 0) {
389 setErrorMessage("refresh()");
393 void LuaWidget::background()
395 if (lsWidgets
== 0 || errorMessage
) return;
397 luaSetInstructionsLimit(lsWidgets
, WIDGET_SCRIPTS_MAX_INSTRUCTIONS
);
398 LuaWidgetFactory
* factory
= (LuaWidgetFactory
*)this->factory
;
399 if (factory
->backgroundFunction
) {
400 lua_rawgeti(lsWidgets
, LUA_REGISTRYINDEX
, factory
->backgroundFunction
);
401 lua_rawgeti(lsWidgets
, LUA_REGISTRYINDEX
, widgetData
);
402 if (lua_pcall(lsWidgets
, 1, 0, 0) != 0) {
403 setErrorMessage("background()");
408 void luaLoadWidgetCallback()
410 TRACE("luaLoadWidgetCallback()");
411 const char * name
=NULL
;
412 int widgetOptions
=0, createFunction
=0, updateFunction
=0, refreshFunction
=0, backgroundFunction
=0;
414 luaL_checktype(lsWidgets
, -1, LUA_TTABLE
);
416 for (lua_pushnil(lsWidgets
); lua_next(lsWidgets
, -2); lua_pop(lsWidgets
, 1)) {
417 const char * key
= lua_tostring(lsWidgets
, -2);
418 if (!strcmp(key
, "name")) {
419 name
= luaL_checkstring(lsWidgets
, -1);
421 else if (!strcmp(key
, "options")) {
422 widgetOptions
= luaL_ref(lsWidgets
, LUA_REGISTRYINDEX
);
423 lua_pushnil(lsWidgets
);
425 else if (!strcmp(key
, "create")) {
426 createFunction
= luaL_ref(lsWidgets
, LUA_REGISTRYINDEX
);
427 lua_pushnil(lsWidgets
);
429 else if (!strcmp(key
, "update")) {
430 updateFunction
= luaL_ref(lsWidgets
, LUA_REGISTRYINDEX
);
431 lua_pushnil(lsWidgets
);
433 else if (!strcmp(key
, "refresh")) {
434 refreshFunction
= luaL_ref(lsWidgets
, LUA_REGISTRYINDEX
);
435 lua_pushnil(lsWidgets
);
437 else if (!strcmp(key
, "background")) {
438 backgroundFunction
= luaL_ref(lsWidgets
, LUA_REGISTRYINDEX
);
439 lua_pushnil(lsWidgets
);
443 if (name
&& createFunction
) {
444 ZoneOption
* options
= createOptionsArray(widgetOptions
, MAX_WIDGET_OPTIONS
);
446 LuaWidgetFactory
* factory
= new LuaWidgetFactory(name
, options
, createFunction
);
447 factory
->updateFunction
= updateFunction
;
448 factory
->refreshFunction
= refreshFunction
;
449 factory
->backgroundFunction
= backgroundFunction
;
450 TRACE("Loaded Lua widget %s", name
);
455 void luaLoadFile(const char * filename
, void (*callback
)())
457 if (lsWidgets
== NULL
|| callback
== NULL
)
460 TRACE("luaLoadFile(%s)", filename
);
462 luaSetInstructionsLimit(lsWidgets
, MANUAL_SCRIPTS_MAX_INSTRUCTIONS
);
465 if (luaLoadScriptFileToState(lsWidgets
, filename
, LUA_SCRIPT_LOAD_MODE
) == SCRIPT_OK
) {
466 if (lua_pcall(lsWidgets
, 0, 1, 0) == LUA_OK
&& lua_istable(lsWidgets
, -1)) {
470 TRACE("luaLoadFile(%s): Error parsing script: %s", filename
, lua_tostring(lsWidgets
, -1));
475 // error while loading Lua widget/theme,
476 // do not disable whole Lua state, just ingnore bad widget/theme
482 void luaLoadFiles(const char * directory
, void (*callback
)())
484 char path
[LUA_FULLPATH_MAXLEN
+1];
488 strcpy(path
, directory
);
489 TRACE("luaLoadFiles() %s", path
);
491 FRESULT res
= f_opendir(&dir
, path
); /* Open the directory */
494 int pathlen
= strlen(path
);
495 path
[pathlen
++] = '/';
497 res
= f_readdir(&dir
, &fno
); /* Read a directory item */
498 if (res
!= FR_OK
|| fno
.fname
[0] == 0) break; /* Break on error or end of dir */
499 uint8_t len
= strlen(fno
.fname
);
500 if (len
> 0 && (unsigned int)(len
+ pathlen
+ sizeof(LUA_WIDGET_FILENAME
)) <= sizeof(path
) &&
501 fno
.fname
[0]!='.' && (fno
.fattrib
& AM_DIR
)) {
502 strcpy(&path
[pathlen
], fno
.fname
);
503 strcat(&path
[pathlen
], LUA_WIDGET_FILENAME
);
504 if (isFileAvailable(path
)) {
505 luaLoadFile(path
, callback
);
511 TRACE("f_opendir(%s) failed, code=%d", path
, res
);
517 #if defined(LUA_ALLOCATOR_TRACER)
518 LuaMemTracer lsWidgetsTrace
;
521 void luaInitThemesAndWidgets()
523 TRACE("luaInitThemesAndWidgets");
525 #if defined(USE_BIN_ALLOCATOR)
526 lsWidgets
= lua_newstate(bin_l_alloc
, NULL
); //we use our own allocator!
527 #elif defined(LUA_ALLOCATOR_TRACER)
528 memset(&lsWidgetsTrace
, 0 , sizeof(lsWidgetsTrace
));
529 lsWidgetsTrace
.script
= "lua_newstate(widgets)";
530 lsWidgets
= lua_newstate(tracer_alloc
, &lsWidgetsTrace
); //we use tracer allocator
532 lsWidgets
= lua_newstate(l_alloc
, NULL
); //we use Lua default allocator
535 // install our panic handler
536 lua_atpanic(lsWidgets
, &custom_lua_atpanic
);
538 #if defined(LUA_ALLOCATOR_TRACER)
539 lua_sethook(lsWidgets
, luaHook
, LUA_MASKLINE
, 0);
542 // protect libs and constants registration
544 luaRegisterLibraries(lsWidgets
);
547 // if we got panic during registration
548 // we disable Lua for this session
550 luaClose(&lsWidgets
);
554 TRACE("lsWidgets %p", lsWidgets
);
555 luaLoadFiles(THEMES_PATH
, luaLoadThemeCallback
);
556 luaLoadFiles(WIDGETS_PATH
, luaLoadWidgetCallback
);
557 luaDoGc(lsWidgets
, true);