summaryrefslogtreecommitdiff
path: root/apps/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins')
-rw-r--r--apps/plugins/CATEGORIES1
-rw-r--r--apps/plugins/SOURCES1
-rw-r--r--apps/plugins/SUBDIRS1
-rw-r--r--apps/plugins/SUBDIRS.app_build1
-rw-r--r--apps/plugins/lua/include_lua/printtable.lua385
-rw-r--r--apps/plugins/lua/loslib.c5
-rw-r--r--apps/plugins/lua/lua.make2
-rw-r--r--apps/plugins/lua/rocklib.c14
-rw-r--r--apps/plugins/lua/rocklib_events.c10
-rw-r--r--apps/plugins/lua/rocklib_img.c19
-rw-r--r--apps/plugins/lua/rocklua.c68
-rw-r--r--apps/plugins/lua_scripts.lua161
-rw-r--r--apps/plugins/lua_scripts/dbgettags.lua116
-rw-r--r--apps/plugins/lua_scripts/fade2sleep.lua107
-rwxr-xr-xapps/plugins/lua_scripts/filebrowse.lua190
-rwxr-xr-xapps/plugins/lua_scripts/fileview.lua79
-rwxr-xr-xapps/plugins/lua_scripts/fileviewers.lua465
-rw-r--r--apps/plugins/lua_scripts/lua_scripts.make24
-rw-r--r--apps/plugins/lua_scripts/print_lua_func.lua304
-rwxr-xr-xapps/plugins/lua_scripts/printmenu.lua83
-rw-r--r--apps/plugins/lua_scripts/tagnav.lua344
21 files changed, 2358 insertions, 22 deletions
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index ad99f3959f..bb8688347d 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -49,6 +49,7 @@ lamp,apps
49logo,demos 49logo,demos
50lrcplayer,apps 50lrcplayer,apps
51lua,viewers 51lua,viewers
52lua_scripts,demos
52fractals,demos 53fractals,demos
53main_menu_config,apps 54main_menu_config,apps
54matrix,demos 55matrix,demos
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index c3e56af254..6551df71a9 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -154,6 +154,7 @@ metronome.c
154/* Lua needs at least 160 KB to work in */ 154/* Lua needs at least 160 KB to work in */
155#if PLUGIN_BUFFER_SIZE >= 0x80000 155#if PLUGIN_BUFFER_SIZE >= 0x80000
156boomshine.lua 156boomshine.lua
157lua_scripts.lua
157#ifdef HAVE_LCD_COLOR 158#ifdef HAVE_LCD_COLOR
158pixel-painter.lua 159pixel-painter.lua
159#endif /* HAVE_LCD_COLOR */ 160#endif /* HAVE_LCD_COLOR */
diff --git a/apps/plugins/SUBDIRS b/apps/plugins/SUBDIRS
index 93cf8297b5..cf2ccff771 100644
--- a/apps/plugins/SUBDIRS
+++ b/apps/plugins/SUBDIRS
@@ -101,4 +101,5 @@ mpegplayer
101/* Lua needs at least 160 KB to work in */ 101/* Lua needs at least 160 KB to work in */
102#if PLUGIN_BUFFER_SIZE >= 0x80000 102#if PLUGIN_BUFFER_SIZE >= 0x80000
103lua 103lua
104lua_scripts
104#endif 105#endif
diff --git a/apps/plugins/SUBDIRS.app_build b/apps/plugins/SUBDIRS.app_build
index 954044146a..48a2d36d87 100644
--- a/apps/plugins/SUBDIRS.app_build
+++ b/apps/plugins/SUBDIRS.app_build
@@ -7,6 +7,7 @@
7 * In fact, most of the plugins aren't supposed to be used on a touch(mouse) device 7 * In fact, most of the plugins aren't supposed to be used on a touch(mouse) device
8 */ 8 */
9lua 9lua
10lua_scripts
10#ifdef HAVE_LCD_BITMAP 11#ifdef HAVE_LCD_BITMAP
11 12
12#if CONFIG_CODEC == SWCODEC && PLUGIN_BUFFER_SIZE > 0x20000 13#if CONFIG_CODEC == SWCODEC && PLUGIN_BUFFER_SIZE > 0x20000
diff --git a/apps/plugins/lua/include_lua/printtable.lua b/apps/plugins/lua/include_lua/printtable.lua
new file mode 100644
index 0000000000..24c4f73b0a
--- /dev/null
+++ b/apps/plugins/lua/include_lua/printtable.lua
@@ -0,0 +1,385 @@
1--[[
2/***************************************************************************
3 * __________ __ ___.
4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
8 * \/ \/ \/ \/ \/
9 * $Id$
10 *
11 * Copyright (C) 2017 William Wilgus
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22]]
23if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
24
25require("actions") -- Contains rb.actions & rb.contexts
26
27local _clr = require("color")
28local _print = require("print")
29local _timer = require("timer")
30
31-- Button definitions --
32local EXIT_BUTTON = rb.PLA_EXIT
33local CANCEL_BUTTON = rb.actions.PLA_CANCEL
34local DOWN_BUTTON = rb.actions.PLA_DOWN
35local DOWNR_BUTTON = rb.actions.PLA_DOWN_REPEAT
36local EXIT_BUTTON = rb.actions.PLA_EXIT
37local LEFT_BUTTON = rb.actions.PLA_LEFT
38local LEFTR_BUTTON = rb.actions.PLA_LEFT_REPEAT
39local RIGHT_BUTTON = rb.actions.PLA_RIGHT
40local RIGHTR_BUTTON = rb.actions.PLA_RIGHT_REPEAT
41local SEL_BUTTON = rb.actions.PLA_SELECT
42local SELREL_BUTTON = rb.actions.PLA_SELECT_REL
43local SELR_BUTTON = rb.actions.PLA_SELECT_REPEAT
44local UP_BUTTON = rb.actions.PLA_UP
45local UPR_BUTTON = rb.actions.PLA_UP_REPEAT
46
47-- clamps value to >= min and <= max
48local function clamp(iVal, iMin, iMax)
49 if iMin > iMax then
50 local swap = iMin
51 iMin, iMax = iMax, swap
52 end
53
54 if iVal < iMin then
55 return iMin
56 elseif iVal < iMax then
57 return iVal
58 end
59
60 return iMax
61end
62
63--------------------------------------------------------------------------------
64--[[ cursor style button routine
65-- left / right are x, xi is increment xir is increment when repeat
66-- up / down are y, yi is increment yir is increment when repeat
67-- cancel is returned as 0,1
68-- select as 0, 1, 2, 3 (none, pressed, repeat, relesed)
69-- x_chg and y_chg are the amount x or y changed
70-- timeout == nil or -1 loop waits indefinitely till button is pressed
71-- time since last button press is returned in ticks..
72-- make xi, xir, yi, yir negative to flip direction...
73]]
74
75local function dpad(x, xi, xir, y, yi, yir, timeout)
76 _timer("dpad") -- start a persistant timer; keeps time between button events
77 if timeout == nil then timeout = -1 end
78 local cancel, select = 0, 0
79 local x_chg, y_chg = 0, 0
80 local button
81 while true do
82 button = rb.get_plugin_action(timeout)
83
84 if button == CANCEL_BUTTON then
85 cancel = 1
86 break;
87 elseif button == EXIT_BUTTON then
88 cancel = 1
89 break;
90 elseif button == SEL_BUTTON then
91 select = 1
92 timeout = timeout + 1
93 elseif button == SELR_BUTTON then
94 select = 2
95 timeout = timeout + 1
96 elseif button == SELREL_BUTTON then
97 select = -1
98 timeout = timeout + 1
99 elseif button == LEFT_BUTTON then
100 x_chg = x_chg - xi
101 elseif button == LEFTR_BUTTON then
102 x_chg = x_chg - xir
103 elseif button == RIGHT_BUTTON then
104 x_chg = x_chg + xi
105 elseif button == RIGHTR_BUTTON then
106 x_chg = x_chg + xir
107 elseif button == UP_BUTTON then
108 y_chg = y_chg + yi
109 elseif button == UPR_BUTTON then
110 y_chg = y_chg + yir
111 elseif button == DOWN_BUTTON then
112 y_chg = y_chg - yi
113 elseif button == DOWNR_BUTTON then
114 y_chg = y_chg - yir
115 elseif timeout >= 0 then--and rb.button_queue_count() < 1 then
116 break;
117 end
118
119 if x_chg ~= 0 or y_chg ~= 0 then
120 timeout = timeout + 1
121 end
122 end
123
124 x = x + x_chg
125 y = y + y_chg
126
127 return cancel, select, x_chg, x, y_chg, y, _timer.check("dpad", true)
128end -- dpad
129
130
131
132--------------------------------------------------------------------------------
133--[[ prints a scrollable table to the screen;
134-- requires a contiguous table with only strings;
135-- 1st item in table is the title if hasheader == true
136-- returns select item indice if NOT m_sel..
137-- if m_sel == true a table of selected indices are returned ]]
138--------------------------------------------------------------------------------
139-- SECOND MODE OF OPERATION -- if co_routine is defined...
140-- prints values returned from a resumable factory in a coroutine this allows
141-- very large files etc to be displayed.. the downside is it takes time
142-- to load data when scrolling also NO multiple selection is allowed
143-- table is passed along with the final count t_count
144--------------------------------------------------------------------------------
145
146function print_table(t, t_count, settings)
147-- (table, t_count, {hasheader, wrap, m_sel, start, curpos, justify, co_routine})
148
149 if type(t) ~= "table" then
150 rb.splash(rb.HZ * 5, "table expected got ".. type(t))
151 return
152 end
153
154 local wrap, justify, start, curpos, co_routine, hasheader, m_sel
155 local header_fgc, header_bgc, item_fgc, item_bgc, item_selc
156 do
157 local s = settings or _print.get_settings()
158 wrap, justify = s.wrap, s.justify
159 start, curpos = s.start, s.curpos
160 co_routine = s.co_routine
161 hasheader = s.hasheader
162 m_sel = false
163 if co_routine == nil then
164 --no multi select in incremental mode
165 m_sel = s.msel
166 end
167 header_fgc = s.hfgc or _clr.set( 0, 000, 000, 000)
168 header_bgc = s.hbgc or _clr.set(-1, 255, 255, 255)
169 item_fgc = s.ifgc or _clr.set(-1, 000, 255, 060)
170 item_bgc = s.ibgc or _clr.set( 0, 000, 000, 000)
171 item_selc = s.iselc or _clr.set( 1, 000, 200, 100)
172 end
173
174 local table_p, line, maxline
175
176 local function set_vsb() end -- forward declaration; initialized below
177
178 local function init_position(acc_ticks, acc_steps)
179 if not acc_ticks then acc_ticks = 15 end-- accelerate scroll every this many ticks
180 if not acc_steps then acc_steps = 5 end -- default steps for an accelerated scroll
181
182 return {row = 1, row_scrl= acc_steps,
183 col = 0, col_scrl = acc_steps,
184 vcursor = 1, vcursor_min = 1,
185 acc_ticks = acc_ticks,
186 acc_steps = acc_steps}
187 end
188
189 local function set_accel(time, scrl, t_p)
190 if time < t_p.acc_ticks then -- accelerate scroll
191 scrl = scrl + 1
192 else
193 scrl = t_p.acc_steps
194 end
195 return scrl
196 end
197
198 --adds or removes \0 from end of table entry to mark selected items
199 local function select_item(item)
200 if item < 1 then item = 1 end
201 if not t[item] then return end
202 if t[item]:sub(-1) == "\0" then
203 t[item] = t[item]:sub(1, -2) -- de-select
204 else
205 t[item] = t[item] .. "\0" -- select
206 end
207 end
208
209 -- displays header text at top
210 local function disp_header(hstr)
211 local header = header or hstr
212 local opts = _print.opt.get()
213 _print.opt.overflow("none") -- don't scroll header; colors change
214 _print.opt.color(header_fgc, header_bgc)
215 _print.opt.line(1)
216
217 _print.f()
218 local line = _print.f(header)
219
220 _print.opt.set(opts)
221 _print.opt.line(2)
222 return 2
223 end
224
225 -- gets user input to select items, quit, scroll
226 local function get_input(t_p)
227 set_vsb(t_p.row + t_p.vcursor - 1)--t_p.row)
228 rb.lcd_update()
229
230 local quit, select, x_chg, xi, y_chg, yi, timeb =
231 dpad(t_p.col, -1, -t_p.col_scrl, t_p.row, -1, -t_p.row_scrl)
232
233 t_p.vcursor = t_p.vcursor + y_chg
234
235 if t_p.vcursor > maxline or t_p.vcursor < t_p.vcursor_min then
236 t_p.row = yi
237 end
238
239 if wrap == true and (y_chg == 1 or y_chg == -1) then
240
241 -- wraps list, stops at end if accelerated
242 if t_p.row < t_p.vcursor_min - 1 then
243 t_p.row = t_count - maxline + 1
244 t_p.vcursor = maxline
245 elseif t_p.row + maxline - 1 > t_count then
246 t_p.row, t_p.vcursor = t_p.vcursor_min - 1, t_p.vcursor_min - 1
247 end
248 end
249
250 t_p.row = clamp(t_p.row, 1, math.max(t_count - maxline + 1, 1))
251 t_p.vcursor = clamp(t_p.vcursor, t_p.vcursor_min, maxline)
252
253 if x_chg ~= 0 then
254
255 if x_chg ~= 1 and x_chg ~= -1 then --stop at the center if accelerated
256 if (t_p.col <= 0 and xi > 0) or (t_p.col >= 0 and xi < 0) then
257 xi = 0
258 end
259 end
260 t_p.col = xi
261
262 t_p.col_scrl = set_accel(timeb, t_p.col_scrl, t_p)
263
264 elseif y_chg ~= 0 then
265 --t_p.col = 0 -- reset column to the beginning
266 _print.clear()
267 _print.opt.sel_line(t_p.vcursor)
268
269 t_p.row_scrl = set_accel(timeb, t_p.row_scrl, t_p)
270
271 end
272
273 if select > 0 and timeb > 15 then --select may be sent multiple times
274 if m_sel == true then
275 select_item(t_p.row + t_p.vcursor - 1)
276 else
277 return -1, 0, 0, (t_p.row + t_p.vcursor - 1)
278 end
279 end
280 if quit > 0 then return -2, 0, 0, 0 end
281 return t_p.row, x_chg, y_chg, 0
282 end
283
284 -- displays the actual table
285 local function display_table(table_p, col_c, row_c, sel)
286 local i = table_p.row
287 while i >= 1 and i <= t_count do
288
289 -- only print if beginning or user scrolled up/down
290 if row_c ~= 0 then
291
292 if t[i] == nil and co_routine then
293 --value has been garbage collected or not created yet
294 coroutine.resume(co_routine, i)
295 end
296
297 if t[i] == nil then
298 rb.splash(1, string.format("ERROR %d is nil", i))
299 t[i] = "???"
300 if rb.get_plugin_action(10) == CANCEL_BUTTON then return 0 end
301 end
302
303 if m_sel == true and t[i]:sub(-1) == "\0" then
304 _print.opt.sel_line(line)
305 end
306
307 if i == 1 and hasheader == true then
308 line = disp_header(t[1])
309 else
310 line = _print.f("%s", tostring(t[i]))
311 end
312
313 end
314
315 i = i + 1 -- important!
316
317 if line == 1 or i > t_count or col_c ~= 0 then
318 _print.opt.column(table_p.col)
319 i, col_c, row_c, sel = get_input(table_p)
320 end
321
322 rb.button_clear_queue() -- keep the button queue from overflowing
323 end
324 return sel
325 end -- display_table
326--============================================================================--
327
328 _print.opt.defaults()
329 _print.opt.autoupdate(false)
330 _print.opt.color(item_fgc, item_bgc, item_selc)
331
332 table_p = init_position(15, 5)
333 line, maxline = _print.opt.area(5, 1, rb.LCD_WIDTH - 10, rb.LCD_HEIGHT - 2)
334 maxline = math.min(maxline, t_count)
335
336 -- allow user to start at a position other than the beginning
337 if start ~= nil then table_p.row = clamp(start, 1, t_count + 1) end
338
339 if hasheader == true then
340 table_p.vcursor_min = 2 -- lowest selectable item
341 table_p.vcursor = 2
342 end
343
344 table_p.vcursor = curpos or table_p.vcursor_min
345
346 if table_p.vcursor < 1 or table_p.vcursor > maxline then
347 table_p.vcursor = table_p.vcursor_min
348 end
349
350 _print.opt.sel_line(table_p.vcursor)
351 _print.opt.overflow("manual")
352 _print.opt.justify(justify)
353
354 -- initialize vertical scrollbar
355 set_vsb(); do
356 local vsb =_print.opt.get()
357 if rb.LCD_DEPTH == 2 then -- invert 2-bit screens
358 vsb.fg_pattern = 3 - vsb.fg_pattern
359 vsb.bg_pattern = 3 - vsb.bg_pattern
360 end
361
362 set_vsb = function (item)
363 if t_count > (maxline or t_count) then
364 rb.set_viewport(vsb)
365 item = item or 0
366 local m = maxline / 2 + 1
367 rb.gui_scrollbar_draw(vsb.width - 5, vsb.y, 5, vsb.height,
368 t_count, math.max(0, item - m),
369 math.min(item + m, t_count), 0)
370 end
371 end
372 end -- set_vsb
373 local selected = display_table(table_p, 0, 1, 0)
374
375 _print.opt.defaults()
376
377 if m_sel == true then -- walk the table to get selected items
378 selected = {}
379 for i = 1, t_count do
380 if t[i]:sub(-1) == "\0" then table.insert(selected, i) end
381 end
382 end
383 --rb.splash(100, string.format("#1 %d, %d, %d", row, vcursor_pos, sel))
384 return selected, table_p.row, table_p.vcursor
385end --print_table
diff --git a/apps/plugins/lua/loslib.c b/apps/plugins/lua/loslib.c
index dce8811fbe..a0ea8e8c24 100644
--- a/apps/plugins/lua/loslib.c
+++ b/apps/plugins/lua/loslib.c
@@ -172,8 +172,11 @@ static int os_time (lua_State *L) {
172 172
173 173
174static int os_exit (lua_State *L) { 174static int os_exit (lua_State *L) {
175 lua_settop(L, 2);
175 int status = luaL_optint(L, 1, EXIT_SUCCESS); 176 int status = luaL_optint(L, 1, EXIT_SUCCESS);
176 lua_close(L); 177 if (status != EXIT_SUCCESS && lua_type (L, 2) != LUA_TSTRING)
178 lua_pushfstring(L, "exit (%d)", status);
179 lua_pushvalue(L, 1); /* put exit status on top of stack */
177 exit(status); 180 exit(status);
178 return EXIT_SUCCESS; /* never reached, surpress warning */ 181 return EXIT_SUCCESS; /* never reached, surpress warning */
179} 182}
diff --git a/apps/plugins/lua/lua.make b/apps/plugins/lua/lua.make
index 16e25c3028..d5fb85afbc 100644
--- a/apps/plugins/lua/lua.make
+++ b/apps/plugins/lua/lua.make
@@ -19,7 +19,7 @@ LUA_INCLUDEDIR := $(LUA_SRCDIR)/include_lua
19LUA_INCLUDELIST := $(addprefix $(LUA_BUILDDIR)/,audio.lua blit.lua color.lua draw.lua \ 19LUA_INCLUDELIST := $(addprefix $(LUA_BUILDDIR)/,audio.lua blit.lua color.lua draw.lua \
20 image.lua lcd.lua math_ex.lua print.lua \ 20 image.lua lcd.lua math_ex.lua print.lua \
21 timer.lua playlist.lua pcm.lua sound.lua \ 21 timer.lua playlist.lua pcm.lua sound.lua \
22 rbcompat.lua ) 22 rbcompat.lua printtable.lua)
23 23
24 24
25ifndef APP_TYPE 25ifndef APP_TYPE
diff --git a/apps/plugins/lua/rocklib.c b/apps/plugins/lua/rocklib.c
index 1e3bc207b7..8c662b7359 100644
--- a/apps/plugins/lua/rocklib.c
+++ b/apps/plugins/lua/rocklib.c
@@ -771,6 +771,17 @@ RB_WRAP(audio_current_track)
771 return mem_read_write(L, address, maxsize); 771 return mem_read_write(L, address, maxsize);
772} 772}
773 773
774RB_WRAP(restart_lua)
775{
776 /*close lua state, open a new lua state, load script @ filename */
777 luaL_checktype (L, 1, LUA_TSTRING);
778 lua_settop(L, 1);
779 lua_pushlightuserdata(L, L); /* signal exit handler */
780 exit(1); /* atexit in rocklua.c */
781 return -1;
782}
783
784
774#define RB_FUNC(func) {#func, rock_##func} 785#define RB_FUNC(func) {#func, rock_##func}
775#define RB_ALIAS(name, func) {name, rock_##func} 786#define RB_ALIAS(name, func) {name, rock_##func}
776static const luaL_Reg rocklib[] = 787static const luaL_Reg rocklib[] =
@@ -843,6 +854,8 @@ static const luaL_Reg rocklib[] =
843 RB_FUNC(audio_next_track), 854 RB_FUNC(audio_next_track),
844 RB_FUNC(audio_current_track), 855 RB_FUNC(audio_current_track),
845 856
857 RB_FUNC(restart_lua),
858
846 {NULL, NULL} 859 {NULL, NULL}
847}; 860};
848#undef RB_FUNC 861#undef RB_FUNC
@@ -939,4 +952,3 @@ LUALIB_API int luaopen_rock(lua_State *L)
939#endif 952#endif
940 return 1; 953 return 1;
941} 954}
942
diff --git a/apps/plugins/lua/rocklib_events.c b/apps/plugins/lua/rocklib_events.c
index 9e363edbdd..270c4ab393 100644
--- a/apps/plugins/lua/rocklib_events.c
+++ b/apps/plugins/lua/rocklib_events.c
@@ -137,6 +137,7 @@ struct event_data {
137 int thread_state; 137 int thread_state;
138 long *event_stack; 138 long *event_stack;
139 long timer_ticks; 139 long timer_ticks;
140 short freq_input;
140 short next_input; 141 short next_input;
141 short next_event; 142 short next_event;
142 /* callbacks */ 143 /* callbacks */
@@ -171,8 +172,9 @@ static void init_event_data(lua_State *L, struct event_data *ev_data)
171 ev_data->thread_state = THREAD_YIELD; 172 ev_data->thread_state = THREAD_YIELD;
172 //ev_data->event_stack = NULL; 173 //ev_data->event_stack = NULL;
173 //ev_data->timer_ticks = 0; 174 //ev_data->timer_ticks = 0;
174 ev_data->next_input = EV_TICKS; 175 ev_data->freq_input = EV_INPUT;
175 ev_data->next_event = EV_INPUT; 176 ev_data->next_input = EV_INPUT;
177 ev_data->next_event = EV_TICKS;
176 /* callbacks */ 178 /* callbacks */
177 for (int i= 0; i < EVENT_CT; i++) 179 for (int i= 0; i < EVENT_CT; i++)
178 ev_data->cb[i] = NULL; 180 ev_data->cb[i] = NULL;
@@ -336,7 +338,7 @@ static void rev_timer_isr(void)
336 if (ev_data.next_input <=0) 338 if (ev_data.next_input <=0)
337 { 339 {
338 ev_data.thread_state |= ((ev_data.thread_state & THREAD_INPUTMASK) >> 16); 340 ev_data.thread_state |= ((ev_data.thread_state & THREAD_INPUTMASK) >> 16);
339 ev_data.next_input = EV_INPUT; 341 ev_data.next_input = ev_data.freq_input;
340 } 342 }
341 343
342 if (ev_data.cb[TIMEREVENT] != NULL && !is_suspend(TIMEREVENT)) 344 if (ev_data.cb[TIMEREVENT] != NULL && !is_suspend(TIMEREVENT))
@@ -535,6 +537,8 @@ static int rockev_register(lua_State *L)
535 case ACTEVENT: 537 case ACTEVENT:
536 /* fall through */ 538 /* fall through */
537 case BUTEVENT: 539 case BUTEVENT:
540 ev_data.freq_input = luaL_optinteger(L, 3, EV_INPUT);
541 if (ev_data.freq_input < HZ / 20) ev_data.freq_input = HZ / 20;
538 ev_data.thread_state |= (ev_flag | (ev_flag << 16)); 542 ev_data.thread_state |= (ev_flag | (ev_flag << 16));
539 break; 543 break;
540 case CUSTOMEVENT: 544 case CUSTOMEVENT:
diff --git a/apps/plugins/lua/rocklib_img.c b/apps/plugins/lua/rocklib_img.c
index aaecc4d64c..1150b511c3 100644
--- a/apps/plugins/lua/rocklib_img.c
+++ b/apps/plugins/lua/rocklib_img.c
@@ -1225,6 +1225,8 @@ static const struct luaL_reg rli_lib [] =
1225#define RB_WRAP(func) static int rock_##func(lua_State UNUSED_ATTR *L) 1225#define RB_WRAP(func) static int rock_##func(lua_State UNUSED_ATTR *L)
1226 1226
1227#if defined NB_SCREENS && (NB_SCREENS > 1) 1227#if defined NB_SCREENS && (NB_SCREENS > 1)
1228#define RB_SCREEN_STRUCT(luastate, narg) \
1229 rb->screens[get_screen(luastate, narg)]
1228#define RB_SCREENS(luastate, narg, func, ...) \ 1230#define RB_SCREENS(luastate, narg, func, ...) \
1229 rb->screens[get_screen(luastate, narg)]->func(__VA_ARGS__) 1231 rb->screens[get_screen(luastate, narg)]->func(__VA_ARGS__)
1230 1232
@@ -1240,6 +1242,8 @@ static int get_screen(lua_State *L, int narg)
1240 return screen; 1242 return screen;
1241} 1243}
1242#else /* only SCREEN_MAIN exists */ 1244#else /* only SCREEN_MAIN exists */
1245#define RB_SCREEN_STRUCT(luastate, narg) \
1246 rb->screens[SCREEN_MAIN]
1243#define RB_SCREENS(luastate, narg, func, ...) \ 1247#define RB_SCREENS(luastate, narg, func, ...) \
1244 rb->screens[SCREEN_MAIN]->func(__VA_ARGS__) 1248 rb->screens[SCREEN_MAIN]->func(__VA_ARGS__)
1245#endif 1249#endif
@@ -1376,7 +1380,6 @@ RB_WRAP(font_getstringsize)
1376} 1380}
1377 1381
1378#ifdef HAVE_LCD_BITMAP 1382#ifdef HAVE_LCD_BITMAP
1379
1380RB_WRAP(lcd_framebuffer) 1383RB_WRAP(lcd_framebuffer)
1381{ 1384{
1382 rli_wrap(L, rb->lcd_framebuffer, LCD_WIDTH, LCD_HEIGHT); 1385 rli_wrap(L, rb->lcd_framebuffer, LCD_WIDTH, LCD_HEIGHT);
@@ -1399,6 +1402,19 @@ static void get_rect_bounds(lua_State *L, int narg, int *x, int *y, int *w, int*
1399 *h = luaL_checkint(L, narg + 3); 1402 *h = luaL_checkint(L, narg + 3);
1400} 1403}
1401 1404
1405RB_WRAP(gui_scrollbar_draw)
1406{
1407 int x, y, width, height;
1408 get_rect_bounds(L, 1, &x, &y, &width, &height);
1409 int items = luaL_checkint(L, 5);
1410 int min_shown = luaL_checkint(L, 6);
1411 int max_shown = luaL_checkint(L, 7);
1412 unsigned flags = (unsigned) luaL_checkint(L, 8);
1413 rb->gui_scrollbar_draw(RB_SCREEN_STRUCT(L, 9), x, y, width, height,
1414 items, min_shown, max_shown, flags);
1415return 0;
1416}
1417
1402RB_WRAP(lcd_mono_bitmap_part) 1418RB_WRAP(lcd_mono_bitmap_part)
1403{ 1419{
1404 struct rocklua_image *src = rli_checktype(L, 1); 1420 struct rocklua_image *src = rli_checktype(L, 1);
@@ -1644,6 +1660,7 @@ static const luaL_Reg rocklib_img[] =
1644#ifdef HAVE_LCD_BITMAP 1660#ifdef HAVE_LCD_BITMAP
1645 R(lcd_framebuffer), 1661 R(lcd_framebuffer),
1646 R(lcd_setfont), 1662 R(lcd_setfont),
1663 R(gui_scrollbar_draw),
1647 R(lcd_mono_bitmap_part), 1664 R(lcd_mono_bitmap_part),
1648 R(lcd_mono_bitmap), 1665 R(lcd_mono_bitmap),
1649#if LCD_DEPTH > 1 1666#if LCD_DEPTH > 1
diff --git a/apps/plugins/lua/rocklua.c b/apps/plugins/lua/rocklua.c
index 0d0b1f63f7..eb48fa2799 100644
--- a/apps/plugins/lua/rocklua.c
+++ b/apps/plugins/lua/rocklua.c
@@ -28,6 +28,8 @@
28#include "luadir.h" 28#include "luadir.h"
29#include "rocklib_events.h" 29#include "rocklib_events.h"
30 30
31static lua_State *Ls = NULL;
32static int lu_status = 0;
31 33
32static const luaL_Reg lualibs[] = { 34static const luaL_Reg lualibs[] = {
33 {"", luaopen_base}, 35 {"", luaopen_base},
@@ -142,41 +144,77 @@ static int docall (lua_State *L) {
142 return status; 144 return status;
143} 145}
144 146
147static void lua_atexit(void);
148static int loadfile_newstate(lua_State **L, const char *filename)
149{
150 *L = luaL_newstate();
151 rb_atexit(lua_atexit);
152 rocklua_openlibs(*L);
153 return luaL_loadfile(*L, filename);
154}
155
156static void lua_atexit(void)
157{
158 char *filename;
159
160 if(Ls && lua_gettop(Ls) > 1)
161 {
162 if (Ls == lua_touserdata(Ls, -1)) /* signal from restart_lua */
163 {
164 filename = (char *) malloc(MAX_PATH);
165
166 if (filename) /* out of memory? */
167 rb->strlcpy(filename, lua_tostring(Ls, -2), MAX_PATH);
168 lua_close(Ls); /* close old state */
169
170 lu_status = loadfile_newstate(&Ls, filename);
171
172 free(filename);
173 plugin_start(NULL);
174 }
175 else if (lua_tointeger(Ls, -1) != 0) /* os.exit */
176 {
177 lu_status = LUA_ERRRUN;
178 lua_pop(Ls, 1); /* put exit string on top of stack */
179 plugin_start(NULL);
180 }
181 }
182 _exit(0); /* don't call exit handler */
183}
145 184
146/***************** Plugin Entry Point *****************/ 185/***************** Plugin Entry Point *****************/
147enum plugin_status plugin_start(const void* parameter) 186enum plugin_status plugin_start(const void* parameter)
148{ 187{
149 const char* filename; 188 const char* filename;
150 int status;
151 189
152 if (parameter == NULL) 190 if (parameter == NULL)
153 { 191 {
192 if (!Ls)
154 rb->splash(HZ, "Play a .lua file!"); 193 rb->splash(HZ, "Play a .lua file!");
155 return PLUGIN_ERROR;
156 } 194 }
157 else 195 else
158 { 196 {
159 filename = (char*) parameter; 197 filename = (char*) parameter;
198 lu_status = loadfile_newstate(&Ls, filename);
199 }
160 200
161 lua_State *L = luaL_newstate(); 201 if (Ls)
162 202 {
163 rocklua_openlibs(L); 203 if (!lu_status) {
164 status = luaL_loadfile(L, filename);
165 if (!status) {
166 rb->lcd_scroll_stop(); /* rb doesn't like bg change while scroll */ 204 rb->lcd_scroll_stop(); /* rb doesn't like bg change while scroll */
167 rb->lcd_clear_display(); 205 rb->lcd_clear_display();
168 status = docall(L); 206 lu_status= docall(Ls);
169 } 207 }
170 208
171 if (status) { 209 if (lu_status) {
172 DEBUGF("%s\n", lua_tostring(L, -1)); 210 DEBUGF("%s\n", lua_tostring(Ls, -1));
173 rb->splashf(5 * HZ, "%s", lua_tostring(L, -1)); 211 rb->splash(5 * HZ, lua_tostring(Ls, -1));
174 lua_pop(L, 1); 212 /*lua_pop(Ls, 1);*/
175 } 213 }
176 214 lua_close(Ls);
177 lua_close(L);
178 } 215 }
216 else
217 return PLUGIN_ERROR;
179 218
180 return PLUGIN_OK; 219 return PLUGIN_OK;
181} 220}
182
diff --git a/apps/plugins/lua_scripts.lua b/apps/plugins/lua_scripts.lua
new file mode 100644
index 0000000000..02fe50b327
--- /dev/null
+++ b/apps/plugins/lua_scripts.lua
@@ -0,0 +1,161 @@
1--[[
2/***************************************************************************
3 * __________ __ ___.
4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
8 * \/ \/ \/ \/ \/
9 * $Id$
10 *
11 * Copyright (C) 2017 William Wilgus
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22]]
23
24local scrpath = rb.current_path() .. "/lua_scripts/"
25
26package.path = scrpath .. "/?.lua;" .. package.path --add lua_scripts directory to path
27require("printtable")
28
29rb.actions = nil
30package.loaded["actions"] = nil
31
32local excludedsrc = ";filebrowse.lua;fileviewers.lua;printmenu.lua;dbgettags.lua;"
33--------------------------------------------------------------------------------
34
35local function get_files(path, norecurse, finddir, findfile, f_t, d_t)
36
37 local quit = false
38
39 local files = f_t or {}
40 local dirs = d_t or {}
41
42 local function f_filedir(name)
43 --default find function
44 -- example: return name:find(".mp3", 1, true) ~= nil
45 if name:len() <= 2 and (name == "." or name == "..") then
46 return false
47 end
48 if string.find(excludedsrc, ";" .. name .. ";") then
49 return false
50 end
51 if string.sub(name, -4) == ".lua" then
52 return true
53 end
54 return false
55 end
56 local function d_filedir(name)
57 --default discard function
58 return false
59 end
60
61 if finddir == nil then
62 finddir = f_filedir
63 elseif type(finddir) ~= "function" then
64 finddir = d_filedir
65 end
66
67 if findfile == nil then
68 findfile = f_filedir
69 elseif type(findfile) ~= "function" then
70 findfile = d_filedir
71 end
72
73 local function _get_files(path, cancelbtn)
74 local sep = ""
75 if string.sub(path, - 1) ~= "/" then sep = "/" end
76 for fname, isdir in luadir.dir(path) do
77
78 if isdir and finddir(fname) then
79 table.insert(dirs, path .. sep ..fname)
80 elseif not isdir and findfile(fname) then
81 table.insert(files, path .. sep ..fname)
82 end
83
84 if rb.get_plugin_action(0) == cancelbtn then
85 return true
86 end
87 end
88 end
89
90 local function cmp_alphanum (op1, op2)
91 local type1= type(op1)
92 local type2 = type(op2)
93
94 if type1 ~= type2 then
95 return type1 < type2
96 else
97 if type1 == "string" then
98 op1 = op1:upper()
99 op2 = op2:upper()
100 end
101 return op1 < op2
102 end
103 end
104
105 table.insert(dirs, path) -- root
106
107 for key,value in pairs(dirs) do
108 --luadir.dir may error out so we need to do the call protected
109 _, quit = pcall(_get_files, value, CANCEL_BUTTON)
110
111 if quit == true or norecurse then
112 break;
113 end
114 end
115
116 table.sort(files, cmp_alphanum)
117 table.sort(dirs, cmp_alphanum)
118
119 return dirs, files
120end -- get_files
121--------------------------------------------------------------------------------
122
123-- uses print_table and get_files to display simple file browser
124function script_choose(dir, title)
125 local dstr
126 local hstr = title
127
128 local norecurse = true
129 local f_finddir = false -- function to match directories; nil all, false none
130 local f_findfile = nil -- function to match files; nil all, false none
131
132 local p_settings = {wrap = true, hasheader = true}
133 local files = {}
134 local dirs = {}
135 local item = 1
136 rb.lcd_clear_display()
137
138 while item > 0 do
139 dirs, files = get_files(dir, norecurse, f_finddir, f_findfile, dirs, files)
140 for i=1, #dirs do dirs[i] = nil end -- empty table for reuse
141 table.insert(dirs, 1, hstr)
142 for i = 1, #files do
143 table.insert(dirs, "\t" .. string.gsub(files[i], ".*/",""))
144 end
145
146 item = print_table(dirs, #dirs, p_settings)
147
148 -- If item was selected follow directory or return filename
149 if item > 0 then
150 dir = files[item - 1]
151 if not rb.dir_exists("/" .. dir) then
152 return dir
153 end
154 end
155
156 end
157end -- file_choose
158--------------------------------------------------------------------------------
159
160local script_path = script_choose(scrpath, "lua scripts")
161if script_path then rb.restart_lua(script_path) end
diff --git a/apps/plugins/lua_scripts/dbgettags.lua b/apps/plugins/lua_scripts/dbgettags.lua
new file mode 100644
index 0000000000..06fa6e8830
--- /dev/null
+++ b/apps/plugins/lua_scripts/dbgettags.lua
@@ -0,0 +1,116 @@
1--dbgettags.lua Bilgus 2018
2--[[
3/***************************************************************************
4 * __________ __ ___.
5 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
6 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
7 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
8 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
9 * \/ \/ \/ \/ \/
10 * $Id$
11 *
12 * Copyright (C) 2017 William Wilgus
13 *
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
18 *
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
21 *
22 ****************************************************************************/
23]]
24
25require("actions")
26local CANCEL_BUTTON = rb.actions.PLA_CANCEL
27
28local sINVALIDDATABASE = "Invalid Database"
29local sERROROPENING = "Error opening"
30
31-- tag cache header
32local sTCVERSION = string.char(0x0F)
33local sTCHEADER = string.reverse("TCH" .. sTCVERSION)
34local DATASZ = 4 -- int32_t
35local TCHSIZE = 3 * DATASZ -- 3 x int32_t
36
37local function bytesLE_n(str)
38 str = str or ""
39 local tbyte={str:byte(1, -1)}
40 local bpos = 1
41 local num = 0
42 for k = 1,#tbyte do -- (k = #t, 1, -1 for BE)
43 num = num + tbyte[k] * bpos
44 bpos = bpos * 256
45 end
46 return num
47end
48
49-- uses database files to retrieve database tags
50-- adds all unique tags into a lua table
51function get_tags(filename, hstr)
52
53 if not filename then return end
54
55 hstr = hstr or filename
56
57 local file = io.open('/' .. filename or "", "r") --read
58 if not file then rb.splash(100, sERROROPENING .. " " .. filename) return end
59
60 local fsz = file:seek("end")
61
62 local posln = 0
63 local tag_len = TCHSIZE
64 local idx
65
66 local function readchrs(count)
67 if posln >= fsz then return nil end
68 file:seek("set", posln)
69 posln = posln + count
70 return file:read(count)
71 end
72
73 local tagcache_header = readchrs(DATASZ) or ""
74 local tagcache_sz = readchrs(DATASZ) or ""
75 local tagcache_entries = readchrs(DATASZ) or ""
76
77 if tagcache_header ~= sTCHEADER or
78 bytesLE_n(tagcache_sz) ~= (fsz - TCHSIZE) then
79 rb.splash(100, sINVALIDDATABASE .. " " .. filename)
80 return
81 end
82
83 -- local tag_entries = bytesLE_n(tagcache_entries)
84
85 local ftable = {}
86 table.insert(ftable, 1, hstr)
87
88 local tline = #ftable + 1
89 ftable[tline] = ""
90
91 local str = ""
92
93 while true do
94 tag_len = bytesLE_n(readchrs(DATASZ))
95 readchrs(DATASZ) -- idx = bytesLE_n(readchrs(DATASZ))
96 str = readchrs(tag_len) or ""
97 str = string.match(str, "(%Z+)%z")
98
99 if str then
100 if ftable[tline - 1] ~= str then -- Remove dupes
101 ftable[tline] = str
102 tline = tline + 1
103 end
104 elseif posln >= fsz then
105 break
106 end
107
108 if rb.get_plugin_action(0) == CANCEL_BUTTON then
109 break
110 end
111 end
112
113 file:close()
114
115 return ftable
116end -- get_tags
diff --git a/apps/plugins/lua_scripts/fade2sleep.lua b/apps/plugins/lua_scripts/fade2sleep.lua
new file mode 100644
index 0000000000..03a4533591
--- /dev/null
+++ b/apps/plugins/lua_scripts/fade2sleep.lua
@@ -0,0 +1,107 @@
1--Bilgus 12-2016
2--revisited 8-2019
3require "actions"
4require "buttons"
5require "sound"
6require "audio"
7TIMEOUT = 0
8
9local SOUND_VOLUME = rb.sound_settings.SOUND_VOLUME
10rb.sound_settings = nil
11package.loaded["sound_defines"] = nil
12
13function say_msg(message, timeout)
14 rb.splash(1, message)
15 rb.sleep(timeout * rb.HZ)
16end
17
18function say_value(value,message,timeout)
19 local message = string.format(message .. "%d", value)
20 say_msg(message, timeout)
21end
22
23function ShowMainMenu() -- we invoke this function every time we want to display the main menu of the script
24local s = 0
25local mult = 1
26local unit = " Minutes"
27
28
29 while s == 0 or s == 5 do -- don't exit of program until user selects Exit
30 if mult < 1 then
31 mult = 1
32 s = 0
33 end
34 mainmenu = {"More", mult * 1 .. unit, mult * 5 .. unit, mult * 10 .. unit, mult * 15 .. unit, "Less", "Exit"} -- define the items of the menu
35 s = rb.do_menu("Reduce volume + sleep over", mainmenu, s, false) -- actually tell Rockbox to draw the menu
36
37 -- In the line above: "Test" is the title of the menu, mainmenu is an array with the items
38 -- of the menu, nil is a null value that needs to be there, and the last parameter is
39 -- whether the theme should be drawn on the menu or not.
40 -- the variable s will hold the index of the selected item on the menu.
41 -- the index is zero based. This means that the first item is 0, the second one is 1, etc.
42 if s == 0 then mult = mult + 1
43 elseif s == 1 then TIMEOUT = mult
44 elseif s == 2 then TIMEOUT = mult * 5
45 elseif s == 3 then TIMEOUT = mult * 10
46 elseif s == 4 then TIMEOUT = mult * 15
47 elseif s == 5 then mult = mult - 1 -- User selected to exit
48 elseif s == 6 then os.exit() -- User selected to exit
49 elseif s == -2 then os.exit() -- -2 index is returned from do_menu() when user presses the key to exit the menu (on iPods, it's the left key).
50 -- In this case, user probably wants to exit (or go back to last menu).
51 else rb.splash(2 * rb.HZ, "Error! Selected index: " .. s) -- something strange happened. The program shows this message when
52 -- the selected item is not on the index from 0 to 3 (in this case), and displays
53 -- the selected index. Having this type of error handling is not
54 -- required, but it might be nice to have Especially while you're still
55 -- developing the plugin.
56 end
57 end
58end
59
60ShowMainMenu()
61rb.set_sleeptimer_duration(TIMEOUT)
62rb.lcd_clear_display()
63rb.lcd_update()
64
65local volume = rb.sound_current(SOUND_VOLUME)
66local vol_min = rb.sound_min(SOUND_VOLUME)
67local volsteps = -(vol_min - volume)
68local seconds = (TIMEOUT * 60) / volsteps
69local sec_left = (TIMEOUT * 60)
70local hb = 0
71local action = rb.get_action(rb.contexts.CONTEXT_STD, 0)
72 if rb.audio_status() == 1 then
73 while ((volume > vol_min) and (action ~= rb.actions.ACTION_STD_CANCEL)) do
74 rb.lcd_clear_display()
75 say_value(volume,sec_left .. " Sec, Volume: ", 1)
76 local i = seconds * 2
77 while ((i > 0) and (action ~= rb.actions.ACTION_STD_CANCEL)) do
78 i = i - 1
79 rb.lcd_drawline(hb, 1, hb, 1)
80 rb.lcd_update()
81 if hb >= rb.LCD_WIDTH then
82 hb = 0
83 rb.lcd_clear_display()
84 say_value(volume,sec_left .. " Sec, Volume: ", 1)
85 end
86 hb = hb + 1
87 rb.sleep(rb.HZ / 2)
88 action = rb.get_action(rb.contexts.CONTEXT_STD, 0)
89 rb.yield()
90 end
91 volume = volume - 1
92 rb.sound_set(SOUND_VOLUME, volume);
93 sec_left = sec_left - seconds
94
95 end
96 rb.audio_stop()
97 rb.lcd_clear_display()
98 rb.lcd_update()
99
100 os.exit(1, "Playback Stopped")
101
102 else
103 rb.lcd_clear_display()
104 rb.lcd_update()
105
106 os.exit(2, "Nothing is playing")
107 end
diff --git a/apps/plugins/lua_scripts/filebrowse.lua b/apps/plugins/lua_scripts/filebrowse.lua
new file mode 100755
index 0000000000..5e75365cf8
--- /dev/null
+++ b/apps/plugins/lua_scripts/filebrowse.lua
@@ -0,0 +1,190 @@
1--[[
2/***************************************************************************
3 * __________ __ ___.
4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
8 * \/ \/ \/ \/ \/
9 * $Id$
10 *
11 * Copyright (C) 2017 William Wilgus
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22]]
23if ... == nil then rb.splash(rb.HZ * 3, "use 'require'") end
24require("printtable")
25local _lcd = require("lcd")
26local _timer = require("timer")
27
28--------------------------------------------------------------------------------
29--[[ returns a sorted tables of directories and (another) of files
30-- path is the starting path; norecurse == true.. only that path will be searched
31-- findfile & finddir are definable search functions
32-- if not defined all files/dirs are returned if false is passed.. none
33-- or you can provide your own function see below..
34-- f_t and d_t allow you to pass your own tables for re-use but isn't necessary
35]]
36local function get_files(path, norecurse, finddir, findfile, f_t, d_t)
37
38 local quit = false
39
40 local files = f_t or {}
41 local dirs = d_t or {}
42
43 local function f_filedir(name)
44 --default find function
45 -- example: return name:find(".mp3", 1, true) ~= nil
46 if name:len() <= 2 and (name == "." or name == "..") then
47 return false
48 end
49 return true
50 end
51 local function d_filedir(name)
52 --default discard function
53 return false
54 end
55
56 if finddir == nil then
57 finddir = f_filedir
58 elseif type(finddir) ~= "function" then
59 finddir = d_filedir
60 end
61
62 if findfile == nil then
63 findfile = f_filedir
64 elseif type(findfile) ~= "function" then
65 findfile = d_filedir
66 end
67
68 local function _get_files(path, cancelbtn)
69 local sep = ""
70 if string.sub(path, - 1) ~= "/" then sep = "/" end
71 for fname, isdir in luadir.dir(path) do
72
73 if isdir and finddir(fname) then
74 table.insert(dirs, path .. sep ..fname)
75 elseif not isdir and findfile(fname) then
76 table.insert(files, path .. sep ..fname)
77 end
78
79 if rb.get_plugin_action(0) == cancelbtn then
80 return true
81 end
82 end
83 end
84
85 local function cmp_alphanum (op1, op2)
86 local type1= type(op1)
87 local type2 = type(op2)
88
89 if type1 ~= type2 then
90 return type1 < type2
91 else
92 if type1 == "string" then
93 op1 = op1:upper()
94 op2 = op2:upper()
95 end
96 return op1 < op2
97 end
98 end
99
100 _lcd:splashf(1, "Searching for Files")
101
102 table.insert(dirs, path) -- root
103
104 for key,value in pairs(dirs) do
105 --luadir.dir may error out so we need to do the call protected
106 _, quit = pcall(_get_files, value, CANCEL_BUTTON)
107
108 if quit == true or norecurse then
109 break;
110 end
111 end
112
113 table.sort(files, cmp_alphanum)
114 table.sort(dirs, cmp_alphanum)
115
116 return dirs, files
117end -- get_files
118--------------------------------------------------------------------------------
119
120-- uses print_table and get_files to display simple file browser
121function file_choose(dir, title)
122 local dstr, hstr = ""
123 if not title then
124 dstr = "%d items found in %0d.%02d seconds"
125 else
126 hstr = title
127 end
128
129 -- returns whole seconds and remainder
130 local function tick2seconds(ticks)
131 local secs = (ticks / rb.HZ)
132 local csecs = (ticks - (secs * rb.HZ))
133 return secs, csecs
134 end
135
136 local norecurse = true
137 local f_finddir = nil -- function to match directories; nil all, false none
138 local f_findfile = nil -- function to match files; nil all, false none
139
140 local p_settings = {wrap = true, hasheader = true}
141
142 local timer
143 local files = {}
144 local dirs = {}
145 local item = 1
146 _lcd:clear()
147
148 while item > 0 do
149 if not title then
150 timer = _timer()
151 end
152
153 dirs, files = get_files(dir, norecurse, f_finddir, f_findfile, dirs, files)
154
155 local parentdir = dirs[1]
156 for i = 1, #dirs do
157 dirs[i] = "\t" .. dirs[i]
158 end
159
160 for i = 1, #files do
161 table.insert(dirs, "\t" .. files[i])
162 end
163
164 for i=1, #files do files[i] = nil end -- empty table for reuse
165
166 if not title then
167 hstr = string.format(dstr, #dirs - 1, tick2seconds(timer:stop()))
168 end
169
170 table.insert(dirs, 1, hstr)
171
172 item = print_table(dirs, #dirs, p_settings)
173
174 -- If item was selected follow directory or return filename
175 if item > 0 then
176 dir = string.gsub(dirs[item], "%c+","")
177 if not rb.dir_exists("/" .. dir) then
178 return dir
179 end
180 end
181
182 if dir == parentdir then
183 dir = dir:sub(1, dir:match(".*()/") - 1)
184 if dir == "" then dir = "/" end
185 end
186 for i=1, #dirs do dirs[i] = nil end -- empty table for reuse
187
188 end
189end -- file_choose
190--------------------------------------------------------------------------------
diff --git a/apps/plugins/lua_scripts/fileview.lua b/apps/plugins/lua_scripts/fileview.lua
new file mode 100755
index 0000000000..920281bbef
--- /dev/null
+++ b/apps/plugins/lua_scripts/fileview.lua
@@ -0,0 +1,79 @@
1--[[
2 __________ __ ___.
3 Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 \/ \/ \/ \/ \/
8 $Id$
9 Example Lua File Viewer script
10 Copyright (C) 2017 William Wilgus
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License
13 as published by the Free Software Foundation; either version 2
14 of the License, or (at your option) any later version.
15 This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 KIND, either express or implied.
17]]--
18
19require("actions") -- Contains rb.actions & rb.contexts
20-- require("buttons") -- Contains rb.buttons -- not needed for this example
21
22--local _timer = require("timer")
23--local _clr = require("color") -- clrset, clrinc provides device independent colors
24local _lcd = require("lcd") -- lcd helper functions
25--local _print = require("print") -- advanced text printing
26--local _img = require("image") -- image manipulation save, rotate, resize, tile, new, load
27--local _blit = require("blit") -- handy list of blit operations
28--local _draw = require("draw") -- draw all the things (primitives)
29--local _math = require("math_ex") -- missing math sine cosine, sqrt, clamp functions
30
31
32local scrpath = rb.current_path()--rb.PLUGIN_DIR .. "/demos/lua_scripts/"
33
34package.path = scrpath .. "/?.lua;" .. package.path --add lua_scripts directory to path
35
36require("printmenu") --menu
37require("filebrowse") -- file browser
38require("fileviewers") -- fileviewer, hexviewer
39
40rb.actions = nil
41package.loaded["actions"] = nil
42
43-- uses print_table to display a menu
44function main_menu()
45
46 local mt = {
47 [1] = "Rocklua File View Example",
48 [2] = "File View",
49 [3] = "File Hex View",
50 [4] = "Simple Browser",
51 [5] = "Exit"
52 }
53
54 local ft = {
55 [0] = exit_now, --if user cancels do this function
56 [1] = function(TITLE) return true end, -- shouldn't happen title occupies this slot
57 [2] = function(VIEWF) -- view file
58 print_file_increment(file_choose("/", "Choose File"))
59 end,
60 [3] = function(VHEXF) -- view hex
61 print_file_hex(file_choose("/", "Choose File"), 8)
62 end,
63 [4] = function(BROWS) -- file browser
64 _lcd:splashf(rb.HZ, "%s", file_choose("/") or "None")
65 end,
66 [5] = function(EXIT_) return true end
67 }
68
69 print_menu(mt, ft)
70
71end
72
73function exit_now()
74 _lcd:update()
75 os.exit()
76end -- exit_now
77
78main_menu()
79exit_now()
diff --git a/apps/plugins/lua_scripts/fileviewers.lua b/apps/plugins/lua_scripts/fileviewers.lua
new file mode 100755
index 0000000000..5a9c417d36
--- /dev/null
+++ b/apps/plugins/lua_scripts/fileviewers.lua
@@ -0,0 +1,465 @@
1--[[
2/***************************************************************************
3 * __________ __ ___.
4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
8 * \/ \/ \/ \/ \/
9 * $Id$
10 *
11 * Copyright (C) 2017 William Wilgus
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22]]
23if ... == nil then rb.splash(rb.HZ * 3, "use 'require'") end
24require("printtable")
25local _clr = require("color")
26local _lcd = require("lcd")
27local _print = require("print")
28local _timer = require("timer")
29
30require("actions")
31local CANCEL_BUTTON = rb.actions.PLA_CANCEL
32--------------------------------------------------------------------------------
33-- builds an index of byte position of every line at each bufsz increment
34-- in filename; bufsz == 1 would be every line; saves to filename.ext.idx_ext
35-- lnbyte should be nil for text files and number of bytes per line for binary
36local function build_file_index(filename, idx_ext, bufsz, lnbyte)
37
38 if not filename then return end
39 local file = io.open('/' .. filename, "r") --read
40 if not file then _lcd:splashf(100, "Can't open %s", filename) return end
41 local fsz = file:seek("end")
42 local fsz_kb = fsz / 1024
43 local count
44 local ltable = {0} --first index is the beginning of the file
45 local timer = _timer()
46 local fread
47 _lcd:splashf(100, "Indexing file %d Kb", (fsz / 1024))
48
49 if lnbyte then
50 fread = function(f) return f:read(lnbyte) end
51 else
52 lnbyte = -1
53 fread = function(f) return f:read("*l") end
54 end
55
56 file:seek("set", 0)
57 for i = 1, fsz do
58 if i % bufsz == 0 then
59 local loc = file:seek()
60 ltable[#ltable + 1] = loc
61 _lcd:splashf(1, "Parsing %d of %d Kb", loc / 1024, fsz_kb)
62 end
63 if rb.get_plugin_action(0) == CANCEL_BUTTON then
64 return
65 end
66 if not fread(file) then
67 count = i
68 break
69 end
70 end
71
72 local fileidx = io.open('/' .. filename .. idx_ext, "w+") -- write/erase
73 if fileidx then
74 fileidx:write(fsz .. "\n")
75 fileidx:write(count .. "\n")
76 fileidx:write(bufsz .. "\n")
77 fileidx:write(lnbyte .. "\n")
78 fileidx:write(table.concat(ltable, "\n"))
79 fileidx:close()
80 _lcd:splashf(100, "Finished in %d seconds", timer.stop() / rb.HZ)
81 collectgarbage("collect")
82 else
83 error("unable to save index file")
84 end
85end -- build_file_index
86--------------------------------------------------------------------------------
87
88--- returns size of original file, total lines buffersize, and table filled
89-- with line offsets in index file -> filename
90local function load_index_file(filename)
91 local filesz, count, bufsz, lnbyte
92 local ltable
93 local fileidx = io.open('/' .. filename, "r") --read
94 if fileidx then
95 local idx = -3
96 ltable = {}
97 fileidx:seek("set", 0)
98 for line in fileidx:lines() do
99 if idx == -3 then
100 filesz = tonumber(line)
101 elseif idx == -2 then
102 count = tonumber(line)
103 elseif idx == -1 then
104 bufsz = tonumber(line)
105 elseif idx == 0 then
106 lnbyte = tonumber(line)
107 else
108 ltable[idx] = tonumber(line)
109 end
110 idx = idx + 1
111 end
112 fileidx:close()
113 end
114 return lnbyte, filesz, count, bufsz, ltable
115end -- load_index_file
116--------------------------------------------------------------------------------
117
118-- creates a fixed index with fixed line lengths, perfect for viewing hex files
119-- not so great for reading text files but works as a fallback
120local function load_fixed_index(bytesperline, filesz, bufsz)
121 local lnbyte = bytesperline
122 local count = (filesz + lnbyte - 1) / lnbyte + 1
123 local idx_t = {} -- build index
124 for i = 0, filesz, bufsz do
125 idx_t[#idx_t + 1] = lnbyte * i
126 end
127 return lnbyte, filesz, count, bufsz, idx_t
128end -- load_fixed_index
129--------------------------------------------------------------------------------
130
131-- uses print_table to display a whole file
132function print_file(filename, maxlinelen, settings)
133
134 if not filename then return end
135 local file = io.open('/' .. filename or "", "r") --read
136 if not file then _lcd:splashf(100, "Can't open %s", filename) return end
137 maxlinelen = 33
138 local hstr = filename
139 local ftable = {}
140 table.insert(ftable, 1, hstr)
141
142 local tline = #ftable + 1
143 local remln = maxlinelen
144 local posln = 1
145
146 for line in file:lines() do
147 if line then
148 if maxlinelen then
149 if line == "" then
150 ftable[tline] = ftable[tline] or ""
151 tline = tline + 1
152 remln = maxlinelen
153 else
154 line = line:match("%w.+") or ""
155 end
156 local linelen = line:len()
157 while linelen > 0 do
158
159 local fsp = line:find("%s", posln + remln - 5) or 0x0
160 fsp = fsp - (posln + remln)
161 if fsp >= 0 then
162 local fspr = fsp
163 fsp = line:find("%s", posln + remln) or linelen
164 fsp = fsp - (posln + remln)
165 if math.abs(fspr) < fsp then fsp = fspr end
166 end
167 if fsp > 5 or fsp < -5 then fsp = 0 end
168
169 local str = line:sub(posln, posln + remln + fsp)
170 local slen = str:len()
171 ftable[tline] = ftable[tline] or ""
172 ftable[tline] = ftable[tline] .. str
173 linelen = linelen - slen
174 if linelen > 0 then
175 tline = tline + 1
176 posln = posln + slen
177 remln = maxlinelen
178 --loop continues
179 else
180 ftable[tline] = ftable[tline] .. " "
181 remln = maxlinelen - slen
182 posln = 1
183 --loop ends
184 end
185
186 end
187 else
188 ftable[#ftable + 1] = line
189 end
190
191
192 end
193 end
194
195 file:close()
196
197 _lcd:clear()
198 _print.clear()
199
200 if not settings then
201 settings = {}
202 settings.justify = "center"
203 settings.wrap = true
204 settings.msel = true
205 end
206 settings.hasheader = true
207 settings.co_routine = nil
208
209 local sel =
210 print_table(ftable, #ftable, settings)
211
212 _lcd:splashf(rb.HZ * 2, "%d items {%s}", #sel, table.concat(sel, ", "))
213 ftable = nil
214end -- print_file
215--------------------------------------------------------------------------------
216
217-- uses print_table to display a portion of a file
218function print_file_increment(filename, settings)
219
220 if not filename then return end
221 local file = io.open('/' .. filename, "r") --read
222 if not file then _lcd:splashf(100, "Can't open %s", filename) return end
223 local fsz = file:seek("end")
224 local bsz = 1023
225 --if small file do it the easier way and load whole file to table
226 if fsz < 60 * 1024 then
227 file:close()
228 print_file(filename, settings)
229 return
230 end
231
232 local ext = ".idx"
233 local lnbyte, filesz, count, bufsz, idx_t = load_index_file(filename .. ext)
234
235 if not idx_t or fsz ~= filesz then -- build file index
236 build_file_index(filename, ext, bsz)
237 lnbyte, filesz, count, bufsz, idx_t = load_index_file(filename .. ext)
238 end
239
240 -- if invalid or user canceled creation fallback to a fixed index
241 if not idx_t or fsz ~= filesz or count <= 0 then
242 _lcd:splashf(rb.HZ * 5, "Unable to read file index %s", filename .. ext)
243 lnbyte, filesz, count, bufsz, idx_t = load_fixed_index(32, fsz, bsz)
244 end
245
246 if not idx_t or fsz ~= filesz or count <= 0 then
247 _lcd:splashf(rb.HZ * 5, "Unable to load file %s", filename)
248 return
249 end
250
251 local hstr = filename
252 local file_t = setmetatable({},{__mode = "kv"}) --weak keys and values
253 -- this allows them to be garbage collected as space is needed
254 -- rebuilds when needed
255 local ovf = 0
256 local lpos = 1
257 local timer = _timer()
258 file:seek("set", 0)
259
260 function print_co()
261 while true do
262 collectgarbage("step")
263 file_t[1] = hstr --position 1 is ALWAYS header/title
264
265 for i = 1, bufsz + ovf do
266 file_t[lpos + i] = file:read ("*l")
267 end
268 ovf = 0
269 lpos = lpos + bufsz
270
271 local bpos = coroutine.yield()
272
273 if bpos <= lpos then -- roll over or scroll up
274 bpos = (bpos - bufsz) + bpos % bufsz
275 timer:check(true)
276 end
277
278 lpos = bpos - bpos % bufsz
279
280 if lpos < 1 then
281 lpos = 1
282 elseif lpos > count - bufsz then -- partial fill
283 ovf = count - bufsz - lpos
284 end
285 --get position in file of the nearest indexed line
286 file:seek("set", idx_t[bpos / bufsz + 1])
287
288 -- on really large files if it has been more than 10 minutes
289 -- since the user scrolled up the screen wipe out the prior
290 -- items to free memory
291 if lpos % 5000 == 0 and timer:check() > rb.HZ * 600 then
292 for i = 1, lpos - 100 do
293 file_t[i] = nil
294 end
295 end
296
297 end
298 end
299
300 co = coroutine.create(print_co)
301 _lcd:clear()
302 _print.clear()
303
304 if not settings then
305 settings = {}
306 settings.justify = "center"
307 settings.wrap = true
308 end
309 settings.hasheader = true
310 settings.co_routine = co
311 settings.msel = false
312
313 table.insert(file_t, 1, hstr) --position 1 is header/title
314 local sel =
315 print_table(file_t, count, settings)
316 file:close()
317 idx_t = nil
318 file_t = nil
319 return sel
320end --print_file_increment
321--------------------------------------------------------------------------------
322function print_file_hex(filename, bytesperline, settings)
323
324 if not filename then return end
325 local file = io.open('/' .. filename, "r") --read
326 if not file then _lcd:splashf(100, "Can't open %s", filename) return end
327 local hstr = filename
328 local bpl = bytesperline
329 local fsz = file:seek("end")
330--[[
331 local filesz = file:seek("end")
332 local bufsz = 1023
333 local lnbyte = bytesperline
334 local count = (filesz + lnbyte - 1) / lnbyte + 1
335
336 local idx_t = {} -- build index
337 for i = 0, filesz, bufsz do
338 idx_t[#idx_t + 1] = lnbyte * i
339 end]]
340
341 local lnbyte, filesz, count, bufsz, idx_t = load_fixed_index(bpl, fsz, 1023)
342
343 local file_t = setmetatable({},{__mode = "kv"}) --weak keys and values
344 -- this allows them to be garbage collected as space is needed
345 -- rebuilds when needed
346 local ovf = 0
347 local lpos = 1
348 local timer = _timer()
349 file:seek("set", 0)
350
351 function hex_co()
352 while true do
353 collectgarbage("step")
354 file_t[1] = hstr --position 1 is ALWAYS header/title
355
356 for i = 1, bufsz + ovf do
357 local pos = file:seek()
358 local s = file:read (lnbyte)
359 if not s then -- EOF
360 file_t[lpos + i] = ""
361 break;
362 end
363 local s_len = s:len()
364
365 if s_len > 0 then
366 local fmt = "0x%04X: " .. string.rep("%02X ", s_len)
367 local schrs = " " .. s:gsub("(%c)", " . ")
368 file_t[lpos + i] = string.format(fmt, pos, s:byte(1, s_len)) ..
369 schrs
370 else
371 file_t[lpos + i] = string.format("0x%04X: ", pos)
372 end
373 end
374 ovf = 0
375 lpos = lpos + bufsz
376
377 local bpos = coroutine.yield()
378
379 if bpos < lpos then -- roll over or scroll up
380 bpos = (bpos - bufsz) + bpos % bufsz
381 timer:check(true)
382 end
383
384 lpos = bpos - bpos % bufsz
385
386 if lpos < 1 then
387 lpos = 1
388 elseif lpos > count - bufsz then -- partial fill
389 ovf = count - bufsz - lpos
390 end
391 --get position in file of the nearest indexed line
392 file:seek("set", idx_t[bpos / bufsz + 1])
393
394 -- on really large files if it has been more than 10 minutes
395 -- since the user scrolled up the screen wipe out the prior
396 -- items to free memory
397 if lpos % 10000 == 0 and timer:check() > rb.HZ * 600 then
398 for i = 1, lpos - 100 do
399 file_t[i] = nil
400 end
401 end
402
403 end
404 end
405
406 co = coroutine.create(hex_co)
407
408 local function repl(char)
409 local ret = ""
410 if char:sub(1,2) == "0x" then
411 return string.format("%dd:", tonumber(char:sub(3, -2), 16))
412 else
413 return string.format("%03d ", tonumber(char, 16))
414 end
415 end
416
417
418 _lcd:clear()
419 _print.clear()
420
421 local sel, start, vcur = 1
422 table.insert(file_t, 1, hstr) --position 1 is header/title
423
424 if not settings then
425 settings = {}
426 settings.justify = "left"
427 settings.wrap = true
428 settings.msel = false
429 settings.hfgc = _clr.set( 0, 000, 000, 000)
430 settings.hbgc = _clr.set(-1, 255, 255, 255)
431 settings.ifgc = _clr.set(-1, 255, 255, 255)
432 settings.ibgc = _clr.set( 0, 000, 000, 000)
433 settings.iselc = _clr.set( 1, 000, 200, 100)
434 end
435
436 settings.hasheader = true
437 settings.co_routine = co
438 settings.start = start
439 settings.curpos = vcur
440
441 while sel > 0 do
442 settings.start = start
443 settings.curpos = vcur
444
445 sel, start, vcur = print_table(file_t, count, settings)
446
447 if sel > 1 and file_t[sel] then -- flips between hex and decimal
448 local s = file_t[sel]
449 if s:sub(-1) == "\b" then
450 file_t[sel] = nil
451 ovf = -(bufsz - 1)
452 coroutine.resume(co, sel) --rebuild this item
453 else
454 s = s:gsub("(0x%x+:)", repl) .. "\b"
455 file_t[sel] = s:gsub("(%x%x%s)", repl) .. "\b"
456 end
457 end
458 end
459
460 file:close()
461 idx_t = nil
462 file_t = nil
463 return sel
464end -- print_file_hex
465--------------------------------------------------------------------------------
diff --git a/apps/plugins/lua_scripts/lua_scripts.make b/apps/plugins/lua_scripts/lua_scripts.make
new file mode 100644
index 0000000000..2f46f9d74a
--- /dev/null
+++ b/apps/plugins/lua_scripts/lua_scripts.make
@@ -0,0 +1,24 @@
1# __________ __ ___.
2# Open \______ \ ____ ____ | | _\_ |__ _______ ___
3# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
4# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
5# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
6# \/ \/ \/ \/ \/
7# $Id$
8#
9
10LUASCR_SRCDIR := $(APPSDIR)/plugins/lua_scripts
11LUASCR_BUILDDIR := $(BUILDDIR)/apps/plugins/lua_scripts
12LUASCRS := $(wildcard $(LUASCR_SRCDIR)/*.lua)
13
14#DUMMY := $(info [${LUASCRS}])
15
16DUMMY : all
17
18all: $(subst $(LUASCR_SRCDIR)/,$(LUASCR_BUILDDIR)/,$(LUASCRS))
19
20$(LUASCR_BUILDDIR)/%.lua: $(LUASCR_SRCDIR)/%.lua | $(LUASCR_BUILDDIR)
21 $(call PRINTS,CP $(subst $(LUASCR_SRCDIR)/,,$<))cp $< $@
22
23$(LUASCR_BUILDDIR):
24 $(call PRINTS,MKDIR $@)mkdir -p $(LUASCR_BUILDDIR)/
diff --git a/apps/plugins/lua_scripts/print_lua_func.lua b/apps/plugins/lua_scripts/print_lua_func.lua
new file mode 100644
index 0000000000..f2642a187b
--- /dev/null
+++ b/apps/plugins/lua_scripts/print_lua_func.lua
@@ -0,0 +1,304 @@
1--RB LUA show all global variables; BILGUS
2require "actions"
3require "audio"
4require "buttons"
5require "color"
6require "draw"
7require "image"
8require "lcd"
9require "math_ex"
10require "pcm"
11require "playlist"
12require "print"
13--require "settings" --uses a lot of memory
14require "sound"
15collectgarbage("collect")
16
17local sDumpFile = "/rb-lua_functions.txt"
18local filehandle
19
20local function a2m_m2a(addr_member)
21 --turns members into addresses; addresses back into members
22 return addr_member
23end
24
25local function dtTag(sType)
26--convert named type; 'number'.. to short type '[n]...'
27--if '?' supplied print out datatype key; number = [n]...
28 local retType = "?"
29 local typ = {
30 ["nil"] = "nil",
31 ["boolean"] = "b",
32 ["number"] = "n",
33 ["string"] = "s",
34 ["userdata"] = "u",
35 ["function"] = "f",
36 ["thread"] = "thr",
37 ["table"] = "t"
38 }
39 if sType == "?" then retType = "Datatypes: " end
40 for k,v in pairs(typ) do
41 if sType == k then
42 retType = v break
43 elseif (sType == "?") then
44 retType = retType .. " [" ..v.. "] = " .. k
45 end
46 end
47 return " [" ..retType.. "] "
48end
49
50local function tableByName(tName)
51 --find the longest match possible to an actual table
52 --Name comes in as (table) tName.var so we can pass back out the name found PITA
53 --returns the table found (key and value)
54 local ld = {}
55 local sMatch = ""
56 local kMatch = nil
57 local vMatch = nil
58
59----FUNCTIONS for tableByName -----------------------------------------------------
60 local function search4Str(n, k, v)
61 local sKey = tostring(k)
62 if string.find (n, sKey,1,true) then
63 if sKey:len() > sMatch:len() then sMatch = sKey kMatch = k vMatch = v end
64 --find the longest match we can
65 end
66 end
67----END FUNCTIONS for tableByName -------------------------------------------------
68
69 if tName.val ~= nil and tName.val ~= "" then
70 for k, v in pairs(_G) do
71 --_G check both since some tables are only in _G or package.loaded
72 search4Str(tName.val, k, v)
73 end
74 for k, v in pairs(package.loaded) do --package.loaded
75 search4Str(tName.val, k, v)
76 end
77 if not string.find (sMatch, "_G",1,true) then sMatch = "_G." .. sMatch end
78 -- put the root _G in if not exist
79 if kMatch and vMatch then ld[kMatch] = vMatch tName.val = sMatch return ld end
80 end
81 tName.val = "_G"
82 return package.loaded --Not Found return default
83end
84
85local function dump_Tables(tBase, sFunc, tSeen, tRet)
86 --Based on: http://www.lua.org/cgi-bin/demo?globals
87 --Recurse through tBase tables copying all found Tables
88 local sSep=""
89 local ld={}
90 local tNameBuf = {}
91 local sName
92 if sFunc ~= "" then sSep = "." end
93
94 for k, v in pairs(tBase) do
95 k = tostring(k)
96 tNameBuf[1] = sFunc
97 tNameBuf[2] = sSep
98 tNameBuf[3] = k
99
100
101 if k ~= "loaded" and type(v) == "table" and not tSeen[v] then
102 tSeen[v]=sFunc
103 sName = table.concat(tNameBuf)
104 tRet[sName] = a2m_m2a(v) --place all keys into ld[i]=value
105 dump_Tables(v, sName, tSeen, tRet)
106 elseif type(v) == "table" and not tSeen[v] then
107 tSeen[v]=sFunc
108 tRet[table.concat(tNameBuf)] = a2m_m2a(v) -- dump 'loaded' table
109 for k1, v1 in pairs(v) do
110 if not _G[k1] and type(v1) == "table" and not tSeen[v1] then
111 -- dump tables that are loaded but not global
112 tSeen[v1]=sFunc
113 tNameBuf[3] = k1
114 sName = table.concat(tNameBuf)
115 tRet[sName] = a2m_m2a(v1) --place all keys into ld[i]=value
116 dump_Tables(v1, sName, tSeen, tRet)
117 end
118 end
119 end
120 end
121end
122
123local function dump_Functions(tBase)
124 --Based on: http://www.lua.org/cgi-bin/demo?globals
125 --We already recursed through tBase copying all found tables
126 --we look up the table by name and then (ab)use a2m_m2a() to load the address
127 --after finding the table by address in tBase we will
128 --put the table address of tFuncs in its place
129 local tFuncBuf = {}
130 for k,v in pairs(tBase) do
131 local tTable = a2m_m2a(v)
132 local tFuncs = {}
133
134 for key, val in pairs(tTable) do
135 if key ~= "loaded" then
136 tFuncBuf[1] = dtTag(type(val))
137 tFuncBuf[2] = tostring(key)
138 tFuncs[table.concat(tFuncBuf)]= val
139 --put the name and value in our tFuncs table
140 end
141 end
142 tBase[k] = a2m_m2a(tFuncs) -- copy the address back to tBase
143 end
144
145end
146
147local function get_common_branches(t, tRet)
148 --load t 'names(values)' into keys
149 --strip off long paths then iterate value if it exists
150 --local tRet={}
151 local sBranch = ""
152 local tName = {}
153 for k in pairs(t) do
154 tName["val"]=k
155 tableByName(tName)
156 sBranch = tName.val
157 if tRet[sBranch] == nil then
158 tRet[sBranch] = 1 --first instance of this branch
159 else
160 tRet[sBranch] = tRet[sBranch] + 1
161 end
162 end
163end
164
165local function pairsByPairs (t, tkSorted)
166 --tkSorted should be an already sorted (i)table with t[keys] in the values
167 --https://www.lua.org/pil/19.3.html
168 --!!Note: table sort default function does not like numbers as [KEY]!!
169 --see *sortbyKeys*cmp_alphanum*
170
171 local i = 0 -- iterator variable
172 local iter = function () -- iterator function
173 i = i + 1
174 if tkSorted[i] == nil then return nil
175 else return tkSorted[i], t[tkSorted[i]]
176 end
177 end
178 return iter
179end
180
181local function sortbyKeys(t, tkSorted)
182 --loads keys of (t) into values of tkSorted
183 --and then sorts them
184 --tkSorted has integer keys (see ipairs)
185----FUNCTIONS for sortByKeys -------------
186 local cmp_alphanum = function (op1, op2)
187 local type1= type(op1)
188 local type2 = type(op2)
189 if type1 ~= type2 then
190 return type1 < type2
191 else
192 return op1 < op2
193 end
194 end
195----END FUNCTIONS for sortByKeys ---------
196 for n in pairs(t) do table.insert(tkSorted, n) end
197 table.sort(tkSorted, cmp_alphanum)--table.sort(tkSorted)
198end
199
200local function funcprint(tBuf, strName, value)
201 local sType = type(value)
202 local sVal = ""
203 local sHex = ""
204 tBuf[#tBuf + 1] = "\t"
205 tBuf[#tBuf + 1] = strName
206 if nil ~= string.find (";string;number;userdata;boolean;", sType, 1, true) then
207 --If any of the above types print the contents of variable
208 sVal = tostring(value)
209
210 if type(value) == "number" then
211 sHex = " = 0x" .. string.format("%x", value)
212 else
213 sHex = ""
214 sVal = string.gsub(sVal, "\n", "\\n") --replace newline with \n
215 end
216 tBuf[#tBuf + 1] = " : "
217 tBuf[#tBuf + 1] = sVal
218 tBuf[#tBuf + 1] = sHex
219 end
220 tBuf[#tBuf + 1] = "\r\n"
221end
222
223local function errorHandler( err )
224 filehandle:write(" ERROR:" .. err .. "\n")
225end
226
227
228------------MAIN----------------------------------------------------------------
229 local _NIL = nil
230 local tSeen= {}
231 local tcBase = {}
232 local tkSortCbase = {}
233 local tMods= {}
234 local tkSortMods = {}
235 local tWriteBuf = {}
236 local n = 0 -- count of how many items were found
237
238 filehandle = io.open(sDumpFile, "w+") --overwrite
239 tWriteBuf[#tWriteBuf + 1] = "*Loaded Modules* \n"
240
241 xpcall( function()
242 dump_Tables(tableByName({["val"] = "_G"}),"", tSeen, tMods)
243 --you can put a table name here if you just wanted to display
244 --only its items, ex. "os" or "rb" or "io"
245 --However, it has to be accessible directly from _G
246 --so "rb.actions" wouldn't return anything since its technically
247 --enumerated through _G.rb
248 end , errorHandler )
249 tSeen = nil
250
251 xpcall( function()dump_Functions(tMods)end , errorHandler )
252
253 get_common_branches(tMods, tcBase)
254
255 sortbyKeys(tcBase, tkSortCbase)
256 sortbyKeys(tMods, tkSortMods)
257
258 for k, v in pairsByPairs(tcBase, tkSortCbase ) do
259 n = n + 1
260 if n ~= 1 then
261 tWriteBuf[#tWriteBuf + 1] = ", "
262 end
263 tWriteBuf[#tWriteBuf + 1] = tostring(k)
264 if n >= 3 then -- split loaded modules to multiple lines
265 n = 0
266 tWriteBuf[#tWriteBuf + 1] = "\r\n"
267 end
268 if #tWriteBuf > 25 then
269 filehandle:write(table.concat(tWriteBuf))
270 for i=1, #tWriteBuf do tWriteBuf[i] = _NIL end -- reuse table
271 end
272 end
273
274 tcBase= nil tkSortCbase= nil
275 tWriteBuf[#tWriteBuf + 1] = "\r\n"
276 tWriteBuf[#tWriteBuf + 1] = dtTag("?")
277 tWriteBuf[#tWriteBuf + 1] = "\r\n\r\n"
278 tWriteBuf[#tWriteBuf + 1] = "Functions: \r\n"
279
280 n = 0
281 for key, val in pairsByPairs(tMods, tkSortMods) do
282 local tkSorted = {}
283 local tFuncs = a2m_m2a(val)
284 sortbyKeys(tFuncs, tkSorted)
285 tWriteBuf[#tWriteBuf + 1] = "\r\n"
286 tWriteBuf[#tWriteBuf + 1] = tostring(key)
287 tWriteBuf[#tWriteBuf + 1] = "\r\n"
288 for k, v in pairsByPairs(tFuncs, tkSorted) do
289 n = n + 1
290 funcprint(tWriteBuf, k,v)
291 if #tWriteBuf > 25 then
292 filehandle:write(table.concat(tWriteBuf))
293 for i=1, #tWriteBuf do tWriteBuf[i] = _NIL end -- reuse table
294 end
295 end
296 end
297 tWriteBuf[#tWriteBuf + 1] = "\r\n\r\n"
298 tWriteBuf[#tWriteBuf + 1] = n
299 tWriteBuf[#tWriteBuf + 1] = " Items Found \r\n"
300 filehandle:write(table.concat(tWriteBuf))
301 for i=1, #tWriteBuf do tWriteBuf[i] = _NIL end -- empty table
302 filehandle:close()
303 rb.splash(rb.HZ * 5, n .. " Items dumped to : " .. sDumpFile)
304 --rb.splash(500, collectgarbage("count"))
diff --git a/apps/plugins/lua_scripts/printmenu.lua b/apps/plugins/lua_scripts/printmenu.lua
new file mode 100755
index 0000000000..3cb17d8026
--- /dev/null
+++ b/apps/plugins/lua_scripts/printmenu.lua
@@ -0,0 +1,83 @@
1--[[
2/***************************************************************************
3 * __________ __ ___.
4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
8 * \/ \/ \/ \/ \/
9 * $Id$
10 *
11 * Copyright (C) 2017 William Wilgus
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22]]
23if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
24
25require("printtable")
26local _clr = require("color")
27
28local _LCD = rb.lcd_framebuffer()
29--------------------------------------------------------------------------------
30-- displays text in menu_t calls function in same indice of func_t when selected
31function print_menu(menu_t, func_t, selected, settings, copy_screen)
32
33 local i, start, vcur, screen_img
34
35 if selected then vcur = selected + 1 end
36 if vcur and vcur <= 1 then vcur = 2 end
37
38 if not settings then
39 settings = {}
40 settings.justify = "center"
41 settings.wrap = true
42 settings.hfgc = _clr.set( 0, 000, 000, 000)
43 settings.hbgc = _clr.set(-1, 255, 255, 255)
44 settings.ifgc = _clr.set(-1, 000, 255, 060)
45 settings.ibgc = _clr.set( 0, 000, 000, 000)
46 settings.iselc = _clr.set( 1, 000, 200, 100)
47 settings.default = true
48 end
49
50 settings.hasheader = true
51 settings.co_routine = nil
52 settings.msel = false
53 settings.start = start
54 settings.curpos = vcur
55
56 while not i or i > 0 do
57 if copy_screen == true then
58 --make a copy of screen for restoration
59 screen_img = screen_img or rb.new_image()
60 screen_img:copy(_LCD)
61 else
62 screen_img = nil
63 end
64
65 _LCD:clear(settings.ibgc)
66
67 settings.start = start
68 settings.curpos = vcur
69
70 i, start, vcur = print_table(menu_t, #menu_t, settings)
71 --vcur = vcur + 1
72 collectgarbage("collect")
73 if copy_screen == true then _LCD:copy(screen_img) end
74
75 if func_t and func_t[i] then
76 if func_t[i](i, menu_t) == true then break end
77 else
78 break
79 end
80 end
81 if settings.default == true then settings = nil end
82 return screen_img
83end
diff --git a/apps/plugins/lua_scripts/tagnav.lua b/apps/plugins/lua_scripts/tagnav.lua
new file mode 100644
index 0000000000..35e691045d
--- /dev/null
+++ b/apps/plugins/lua_scripts/tagnav.lua
@@ -0,0 +1,344 @@
1-- BILGUS 2018
2
3--local scrpath = rb.current_path()"
4
5--package.path = scrpath .. "/?.lua;" .. package.path --add lua_scripts directory to path
6require("printmenu")
7require("printtable")
8require("dbgettags")
9
10local _print = require("print")
11
12rb.actions = nil
13package.loaded["actions"] = nil
14
15local sERROROPENING = "Error opening"
16local sERRORMENUENTRY = "Error finding menu entry"
17
18local sBLANKLINE = "##sBLANKLINE##"
19local sDEFAULTMENU = "customfilter"
20
21local sFILEOUT = "/.rockbox/tagnavi_custom.config"
22local sFILEHEADER = "#! rockbox/tagbrowser/2.0"
23local sMENUSTART = "%menu_start \"custom\" \"Database\""
24local sMENUTITLE = "title = \"fmt_title\""
25
26local TAG_ARTIST, TAG_ALBARTIST, TAG_ALBUM, TAG_GENRE, TAG_COMPOSER = 1, 2, 3, 4, 5
27local ts_TAGTYPE = {"Artist", "AlbumArtist", "Album", "Genre", "Composer"}
28local ts_DBPATH = {"database_0.tcd", "database_7.tcd", "database_1.tcd", "database_2.tcd", "database_5.tcd"}
29
30local COND_OR, COND_AND, COND_NOR, COND_NAND = 1, 2, 3, 4
31local ts_CONDITIONALS = {"OR", "AND", "!, OR", "!, AND"}
32local ts_CONDSYMBOLS = {"|", "&", "|", "&"}
33
34local ts_YESNO = {"", "Yes", "No"}
35local s_OVERWRITE = "Overwrite"
36local s_EXISTS = "Exists"
37
38
39local function question(tInquiry, start)
40 settings = {}
41 settings.justify = "center"
42 settings.wrap = true
43 settings.msel = false
44 settings.hasheader = true
45 settings.co_routine = nil
46 settings.curpos = start or 1
47 local sel = print_table(tInquiry, #tInquiry, settings)
48 return sel
49end
50
51local function find_linepos(t_lines, search, startline)
52 startline = startline or 1
53
54 for i = startline, #t_lines do
55 if string.match (t_lines[i], search) then
56 return i
57 end
58 end
59
60 return -1
61end
62
63local function replacelines(t_lines, search, replace, startline)
64 startline = startline or 1
65 repcount = 0
66 for i = startline, #t_lines do
67 if string.match (t_lines[i], search) then
68 t_lines[i] = replace
69 repcount = repcount + 1
70 end
71 end
72 return repcount
73end
74
75local function replaceemptylines(t_lines, replace, startline)
76 startline = startline or 1
77 replace = replace or nil
78 repcount = 0
79 for i = startline, #t_lines do
80 if t_lines[i] == "" then
81 t_lines[i] = replace
82 repcount = repcount + 1
83 end
84 end
85 return repcount
86end
87
88local function checkexistingmenu(t_lines, menuname)
89 local pos = find_linepos(t_lines, "^\"" .. menuname .. "\"%s*%->.+")
90 local sel = 0
91 if pos > 0 then
92 ts_YESNO[1] = menuname .. " " .. s_EXISTS .. ", " .. s_OVERWRITE .. "?"
93 sel = question(ts_YESNO, 3)
94 if sel == 3 then
95 pos = nil
96 elseif sel < 2 then
97 pos = 0
98 end
99 else
100 pos = nil
101 end
102 return pos
103end
104
105local function savedata(filename, ts_tags, cond, menuname)
106 menuname = menuname or sDEFAULTMENU
107
108 local lines = {}
109 local curline = 0
110 local function lines_next(str, pos)
111 pos = pos or #lines + 1
112 lines[pos] = str or ""
113 curline = pos
114 end
115
116 local function lines_append(str, pos)
117 pos = pos or curline or #lines
118 lines[pos] = lines[pos] .. str or ""
119 end
120
121 if rb.file_exists(filename) ~= true then
122 lines_next(sFILEHEADER)
123 lines_next("#")
124 lines_next("# MAIN MENU")
125 lines_next(sMENUSTART)
126 else
127 local file = io.open(filename, "r") -- read
128 if not file then
129 rb.splash(rb.HZ, "Error opening" .. " " .. filename)
130 return
131 end
132
133 for line in file:lines() do
134 lines_next(line)
135 end
136 file:close()
137 end
138
139 local menupos = find_linepos(lines, sMENUSTART)
140 if menupos < 1 then
141 rb.splash(rb.HZ, sERRORMENUENTRY)
142 return
143 end
144
145 replaceemptylines(lines, sBLANKLINE, menupos)
146
147 local existmenupos = checkexistingmenu(lines, menuname)
148 if existmenupos and existmenupos < 1 then return end -- user canceled
149
150 local lastcond = ""
151 local n_cond = COND_OR
152 local tags, tagtype
153
154 local function buildtag(e_tagtype)
155 if ts_tags[e_tagtype] then
156 n_cond = (cond[e_tagtype] or COND_OR)
157 if e_tagtype > 1 then
158 lines_append(" " .. ts_CONDSYMBOLS[n_cond])
159 end
160 tags = ts_tags[e_tagtype]
161 tagtype = string.lower(ts_TAGTYPE[e_tagtype])
162
163 if n_cond <= COND_AND then
164 lines_append(" " .. tagtype)
165 lines_append(" @ \"".. table.concat(tags, "|") .. "\"")
166 else
167 for k = 1, #tags do
168 lines_append(" " .. tagtype)
169 lines_append(" !~ \"".. tags[k] .. "\"")
170 if k < #tags then lines_append(" &") end
171 end
172 end
173 end
174 end
175
176 if ts_tags[TAG_ARTIST] or ts_tags[TAG_ALBARTIST] or ts_tags[TAG_ALBUM] or
177 ts_tags[TAG_GENRE] or ts_tags[TAG_COMPOSER] then
178
179 lines_next("\"" .. menuname .. "\" -> " .. sMENUTITLE .. " ?", existmenupos)
180
181 buildtag(TAG_ARTIST)
182 buildtag(TAG_ALBARTIST)
183 buildtag(TAG_ALBUM)
184 buildtag(TAG_GENRE)
185 buildtag(TAG_COMPOSER)
186
187 lines_next("\n")
188 else
189 rb.splash(rb.HZ, "Nothing to save")
190 end
191
192 local file = io.open(filename, "w+") -- overwrite
193 if not file then
194 rb.splash(rb.HZ, "Error writing " .. filename)
195 return
196 end
197
198 for i = 1, #lines do
199 if lines[i] and lines[i] ~= sBLANKLINE then
200 file:write(lines[i], "\n")
201 end
202 end
203
204 file:close()
205end
206
207-- uses print_table to display database tags
208local function print_tags(ftable, settings, t_selected)
209 if not s_cond then s_sep = "|" end
210 ftable = ftable or {}
211
212 if t_selected then
213 for k = 1, #t_selected do
214 ftable[t_selected[k]] = ftable[t_selected[k]] .. "\0"
215 end
216 end
217 rb.lcd_clear_display()
218 _print.clear()
219
220 if not settings then
221 settings = {}
222 settings.justify = "center"
223 settings.wrap = true
224 settings.msel = true
225 end
226
227 settings.hasheader = true
228 settings.co_routine = nil
229
230 local sel = print_table(ftable, #ftable, settings)
231
232 --_lcd:splashf(rb.HZ * 2, "%d items {%s}", #sel, table.concat(sel, ", "))
233 local selected = {}
234 local str = ""
235 for k = 1,#sel do
236 str = ftable[sel[k]] or ""
237 selected[#selected + 1] = string.sub(str, 1, -2) -- REMOVE \0
238 end
239
240 ftable = nil
241
242 if #sel == 0 then
243 return nil, nil
244 end
245
246 return sel, selected
247end -- print_tags
248
249-- uses print_table to display a menu
250function main_menu()
251 local menuname = sDEFAULTMENU
252 local t_tags
253 local ts_tags = {}
254 local cond = {}
255 local sel = {}
256 local mt = {
257 [1] = "TagNav Customizer",
258 [2] = "", --ts_CONDITIONALS[cond[TAG_ARTIST] or COND_OR],
259 [3] = ts_TAGTYPE[TAG_ARTIST],
260 [4] = ts_CONDITIONALS[cond[TAG_ALBARTIST] or COND_OR],
261 [5] = ts_TAGTYPE[TAG_ALBARTIST],
262 [6] = ts_CONDITIONALS[cond[TAG_ALBUM] or COND_OR],
263 [7] = ts_TAGTYPE[TAG_ALBUM],
264 [8] = ts_CONDITIONALS[cond[TAG_GENRE] or COND_OR],
265 [9] = ts_TAGTYPE[TAG_GENRE],
266 [10] = ts_CONDITIONALS[cond[TAG_COMPOSER] or COND_OR],
267 [11] = ts_TAGTYPE[TAG_COMPOSER],
268 [12] = "Save to Tagnav",
269 [13] = "Exit"
270 }
271
272 local function sel_cond(item, item_mt)
273 cond[item] = cond[item] or 1
274 cond[item] = cond[item] + 1
275 if cond[item] > #ts_CONDITIONALS then cond[item] = 1 end
276 mt[item_mt] = ts_CONDITIONALS[cond[item]]
277 end
278
279 local function sel_tag(item, item_mt, t_tags)
280 t_tags = get_tags(rb.ROCKBOX_DIR .. "/" .. ts_DBPATH[item], ts_TAGTYPE[item])
281 sel[item], ts_tags[item] = print_tags(t_tags, nil, sel[item])
282 if ts_tags[item] then
283 mt[item_mt] = ts_TAGTYPE[item] .. " [" .. #sel[item] .. "]"
284 else
285 mt[item_mt] = ts_TAGTYPE[item]
286 end
287 end
288
289 local ft = {
290 [0] = exit_now, --if user cancels do this function
291 [1] = function(TITLE) return true end, -- shouldn't happen title occupies this slot
292 [2] = function(ARTCOND)
293 sel_cond(TAG_ARTIST, ARTCOND)
294 end,
295 [3] = function(ART)
296 sel_tag(TAG_ARTIST, ART, t_tags)
297 end,
298 [4] = function(ALBARTCOND)
299 sel_cond(TAG_ALBARTIST, ALBARTCOND)
300 end,
301 [5] = function(ALBART)
302 sel_tag(TAG_ALBARTIST, ALBART, t_tags)
303 end,
304 [6] = function(ALBCOND)
305 sel_cond(TAG_ALBUM, ALBCOND)
306 end,
307 [7] = function(ALB)
308 sel_tag(TAG_ALBUM, ALB, t_tags)
309 end,
310 [8] = function(GENRECOND)
311 sel_cond(TAG_GENRE, GENRECOND)
312 end,
313 [9] = function(GENRE)
314 sel_tag(TAG_GENRE, GENRE, t_tags)
315 end,
316 [10] = function(COMPCOND)
317 sel_cond(TAG_COMPOSER, COMPCOND)
318 end,
319 [11] = function(COMP)
320 sel_tag(TAG_COMPOSER, COMP, t_tags)
321 end,
322
323 [12] = function(SAVET)
324 menuname = menuname or sDEFAULTMENU
325 menuname = rb.kbd_input(menuname)
326 menuname = string.match(menuname, "%w+")
327 if menuname == "" then menuname = nil end
328 menuname = menuname or sDEFAULTMENU
329 savedata(sFILEOUT, ts_tags, cond, menuname)
330 end,
331 [13] = function(EXIT_) return true end
332 }
333
334 print_menu(mt, ft, 2) --start at item 2
335
336end
337
338function exit_now()
339 rb.lcd_update()
340 os.exit()
341end -- exit_now
342
343main_menu()
344exit_now()