summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Wilgus <me.theuser@yahoo.com>2019-06-27 11:28:34 -0500
committerWilliam Wilgus <me.theuser@yahoo.com>2019-07-19 20:48:34 -0500
commit3e2b50ed3b58daa5e3be5ea336d276b1655565f1 (patch)
treea56ba215a934ab60c0be0ffcb73f0c193c5c7e38
parentb0de98ad3b1391ec0dfe4f8eced0a6833490cd8f (diff)
downloadrockbox-3e2b50ed3b58daa5e3be5ea336d276b1655565f1.tar.gz
rockbox-3e2b50ed3b58daa5e3be5ea336d276b1655565f1.zip
lua events from rockbox
This library allows events to be subscribed / recieved within a lua script most events in rb are synchronous so flags are set and later checked by a secondary thread to make them (semi?) asynchronous. There are a few caveats to be aware of: FIRST, The main lua state is halted till the lua callback(s) are finished Yielding will not return control to your script from within a callback Also, subsequent callbacks may be delayed by the code in your lua callback SECOND, You must store the value returned from the event_register function you might get away with it for a bit but gc will destroy your callback eventually if you do not store the event THIRD, You only get one cb per event type ["action", "button", "custom", "playback", "timer"] (Re-registration of an event overwrites the previous one) Usage: possible events =["action", "button", "custom", "playback", "timer"] local evX = rockev.register("event", cb_function, [timeout / flags]) cb_function([id] [, data]) ... end rockev.suspend(["event"/nil][true/false]) passing nil affects all events stops event from executing, any but the last event before re-enabling will be lost, passing false, unregistering or re-registering an event will clear the suspend rockev.trigger("event", [true/false], [id]) sets an event to triggered, NOTE!, CUSTOM_EVENT must be unset manually id is only passed to callback by custom and playback events rockev.unregister(evX) Use unregister(evX) to remove an event Unregistering is not necessary before script end, it will be cleaned up on script exit Change-Id: Iea12a5cc0c0295b955dcc1cdf2eec835ca7e354d
-rw-r--r--apps/plugins/lua/SOURCES1
-rwxr-xr-xapps/plugins/lua/rbdefines_helper.pl4
-rw-r--r--apps/plugins/lua/rockaux.c19
-rw-r--r--apps/plugins/lua/rocklib.c21
-rw-r--r--apps/plugins/lua/rocklib.h2
-rw-r--r--apps/plugins/lua/rocklib_events.c632
-rw-r--r--apps/plugins/lua/rocklib_events.h5
-rw-r--r--apps/plugins/lua/rocklua.c3
8 files changed, 674 insertions, 13 deletions
diff --git a/apps/plugins/lua/SOURCES b/apps/plugins/lua/SOURCES
index 8877a4164d..93fa5e9c83 100644
--- a/apps/plugins/lua/SOURCES
+++ b/apps/plugins/lua/SOURCES
@@ -37,3 +37,4 @@ strtol.c
37strstr.c 37strstr.c
38rocklua.c 38rocklua.c
39luadir.c 39luadir.c
40rocklib_events.c
diff --git a/apps/plugins/lua/rbdefines_helper.pl b/apps/plugins/lua/rbdefines_helper.pl
index f15cc71d36..ba15346a8a 100755
--- a/apps/plugins/lua/rbdefines_helper.pl
+++ b/apps/plugins/lua/rbdefines_helper.pl
@@ -31,8 +31,12 @@ my @rockbox_defines = (
31 '^SCREEN_REMOTE$', 31 '^SCREEN_REMOTE$',
32 '^FONT_SYSFIXED$', 32 '^FONT_SYSFIXED$',
33 '^FONT_UI$', 33 '^FONT_UI$',
34 '^PLAYBACK_EVENT_.*',
34 '^PLAYLIST_(INSERT|PREPEND|REPLACE)', 35 '^PLAYLIST_(INSERT|PREPEND|REPLACE)',
35 '^TOUCHSCREEN_(POINT|BUTTON)$', 36 '^TOUCHSCREEN_(POINT|BUTTON)$',
37 '^SYS_CHARGER_(DIS|)CONNECTED$',
38 '^SYS_(TIMEOUT|POWEROFF)$',
39 '^SYS_USB_(DIS|)CONNECTED$',
36 '^HOME_DIR$', 40 '^HOME_DIR$',
37 '^PLUGIN_DIR$', 41 '^PLUGIN_DIR$',
38 '^PLUGIN(_APPS_|_GAMES_|_)DATA_DIR$', 42 '^PLUGIN(_APPS_|_GAMES_|_)DATA_DIR$',
diff --git a/apps/plugins/lua/rockaux.c b/apps/plugins/lua/rockaux.c
index 562d1654a7..734b6a8324 100644
--- a/apps/plugins/lua/rockaux.c
+++ b/apps/plugins/lua/rockaux.c
@@ -24,6 +24,7 @@
24#include "plugin.h" 24#include "plugin.h"
25#define _ROCKCONF_H_ /* Protect against unwanted include */ 25#define _ROCKCONF_H_ /* Protect against unwanted include */
26#include "lua.h" 26#include "lua.h"
27#include "lib/pluginlib_actions.h"
27 28
28extern long strtol(const char *nptr, char **endptr, int base); 29extern long strtol(const char *nptr, char **endptr, int base);
29 30
@@ -164,3 +165,21 @@ int filetol(int fd, long *num)
164 return retn; 165 return retn;
165} 166}
166 167
168int get_plugin_action(int timeout, bool with_remote)
169{
170 static const struct button_mapping *m1[] = { pla_main_ctx };
171 int btn;
172
173#ifndef HAVE_REMOTE_LCD
174 (void) with_remote;
175#else
176 static const struct button_mapping *m2[] = { pla_main_ctx, pla_remote_ctx };
177
178 if (with_remote)
179 btn = pluginlib_getaction(timeout, m2, 2);
180 else
181#endif
182 btn = pluginlib_getaction(timeout, m1, 1);
183
184 return btn;
185}
diff --git a/apps/plugins/lua/rocklib.c b/apps/plugins/lua/rocklib.c
index 1d20989009..9518fe955b 100644
--- a/apps/plugins/lua/rocklib.c
+++ b/apps/plugins/lua/rocklib.c
@@ -30,7 +30,6 @@
30#include "lauxlib.h" 30#include "lauxlib.h"
31#include "rocklib.h" 31#include "rocklib.h"
32#include "lib/helper.h" 32#include "lib/helper.h"
33#include "lib/pluginlib_actions.h"
34 33
35/* 34/*
36 * http://www.lua.org/manual/5.1/manual.html#lua_CFunction 35 * http://www.lua.org/manual/5.1/manual.html#lua_CFunction
@@ -88,19 +87,9 @@ RB_WRAP(current_path)
88 87
89RB_WRAP(get_plugin_action) 88RB_WRAP(get_plugin_action)
90{ 89{
91 static const struct button_mapping *m1[] = { pla_main_ctx };
92 int timeout = luaL_checkint(L, 1); 90 int timeout = luaL_checkint(L, 1);
93 int btn;
94
95#ifdef HAVE_REMOTE_LCD
96 static const struct button_mapping *m2[] = { pla_main_ctx, pla_remote_ctx };
97 bool with_remote = luaL_optint(L, 2, 0); 91 bool with_remote = luaL_optint(L, 2, 0);
98 if (with_remote) 92 int btn = get_plugin_action(timeout, with_remote);
99 btn = pluginlib_getaction(timeout, m2, 2);
100 else
101#endif
102 btn = pluginlib_getaction(timeout, m1, 1);
103
104 lua_pushinteger(L, btn); 93 lua_pushinteger(L, btn);
105 return 1; 94 return 1;
106} 95}
@@ -829,6 +818,14 @@ LUALIB_API int luaopen_rock(lua_State *L)
829 RB_CONSTANT(PLAYLIST_PREPEND), 818 RB_CONSTANT(PLAYLIST_PREPEND),
830 RB_CONSTANT(PLAYLIST_REPLACE), 819 RB_CONSTANT(PLAYLIST_REPLACE),
831 820
821/* queue sys events */
822 RB_CONSTANT(SYS_USB_CONNECTED),
823 RB_CONSTANT(SYS_USB_DISCONNECTED),
824 RB_CONSTANT(SYS_TIMEOUT),
825 RB_CONSTANT(SYS_POWEROFF),
826 RB_CONSTANT(SYS_CHARGER_CONNECTED),
827 RB_CONSTANT(SYS_CHARGER_DISCONNECTED),
828
832#ifdef HAVE_TOUCHSCREEN 829#ifdef HAVE_TOUCHSCREEN
833 RB_CONSTANT(TOUCHSCREEN_POINT), 830 RB_CONSTANT(TOUCHSCREEN_POINT),
834 RB_CONSTANT(TOUCHSCREEN_BUTTON), 831 RB_CONSTANT(TOUCHSCREEN_BUTTON),
diff --git a/apps/plugins/lua/rocklib.h b/apps/plugins/lua/rocklib.h
index 5d3abe30ec..02b5ff6c88 100644
--- a/apps/plugins/lua/rocklib.h
+++ b/apps/plugins/lua/rocklib.h
@@ -46,8 +46,10 @@ struct lua_str_reg {
46}; 46};
47 47
48LUALIB_API int (luaopen_rock) (lua_State *L) __attribute__((aligned(0x8))); 48LUALIB_API int (luaopen_rock) (lua_State *L) __attribute__((aligned(0x8)));
49/* in rockaux.c */
49int get_current_path(lua_State *L, int level); 50int get_current_path(lua_State *L, int level);
50int filetol(int fd, long *num); 51int filetol(int fd, long *num);
52int get_plugin_action(int timeout, bool with_remote);
51 53
52#endif /* _ROCKLIB_H_ */ 54#endif /* _ROCKLIB_H_ */
53 55
diff --git a/apps/plugins/lua/rocklib_events.c b/apps/plugins/lua/rocklib_events.c
new file mode 100644
index 0000000000..9e363edbdd
--- /dev/null
+++ b/apps/plugins/lua/rocklib_events.c
@@ -0,0 +1,632 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2019 William Wilgus
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22/* lua events from rockbox *****************************************************
23 * This library allows events to be subscribed / recieved within a lua script
24 * most events in rb are synchronous so flags are set and later checked by a
25 * secondary thread to make them (semi?) asynchronous.
26 *
27 * There are a few caveats to be aware of:
28 * FIRST, The main lua state is halted till the lua callback(s) are finished
29 * Yielding will not return control to your script from within a callback
30 * Also, subsequent callbacks may be delayed by the code in your lua callback
31 * SECOND, You must store the value returned from the event_register function
32 * you might get away with it for a bit but gc will destroy your callback
33 * eventually if you do not store the event
34 * THIRD, You only get one cb per event type
35 * ["action", "button", "custom", "playback", "timer"]
36 * (Re-registration of an event overwrites the previous one)
37 *
38 * Usage:
39 * possible events =["action", "button", "custom", "playback", "timer"]
40 *
41 * local ev = rockev.register("event", cb_function, [timeout / flags])
42 * cb_function([id] [, data]) ... end
43 *
44 *
45 * rockev.trigger("event", [true/false], [id])
46 * sets an event to triggered,
47 * NOTE!, CUSTOM_EVENT must be unset manually
48 * id is only passed to callback by custom and playback events
49 *
50 * rockev.suspend(["event"/nil][true/false]) passing nil affects all events
51 * stops event from executing, any but the last event before
52 * re-enabling will be lost. Passing false, unregistering or re-registering
53 * an event will clear the suspend
54 *
55 * rockev.unregister(evX)
56 * Use unregister(evX) to remove an event
57 * Unregistering is not necessary before script end, it will be
58 * cleaned up on script exit
59 *
60 *******************************************************************************
61 * *
62 */
63
64#define LUA_LIB
65
66#define _ROCKCONF_H_ /* We don't need strcmp() etc. wrappers */
67#include "lua.h"
68#include "lauxlib.h"
69#include "plugin.h"
70#include "rocklib_events.h"
71
72#define EVENT_METATABLE "event metatable"
73
74#define EVENT_THREAD LUA_ROCKEVENTSNAME ".thread"
75
76#define LUA_SUCCESS 0
77#define EV_TIMER_FREQ (TIMER_FREQ / HZ)
78#define EV_TICKS (HZ / 5)
79#define EV_INPUT (HZ / 4)
80//#define DEBUG_EV
81
82enum e_thread_state_flags{
83 THREAD_QUIT = 0x0,
84 THREAD_YIELD = 0x1,
85 THREAD_TIMEREVENT = 0x2,
86 THREAD_PLAYBKEVENT = 0x4,
87 THREAD_ACTEVENT = 0x8,
88 THREAD_BUTEVENT = 0x10,
89 THREAD_CUSTOMEVENT = 0x20,
90 //THREAD_AVAILEVENT = 0x40,
91 //THREAD_AVAILEVENT = 0x80,
92/* thread state holds 3 status items using masks and bitshifts */
93 THREAD_STATEMASK = 0x00FF,
94 THREAD_SUSPENDMASK = 0xFF00,
95 THREAD_INPUTMASK = 0xFF0000,
96};
97
98enum {
99 ACTEVENT = 0,
100 BUTEVENT,
101 CUSTOMEVENT,
102 PLAYBKEVENT,
103 TIMEREVENT,
104 EVENT_CT
105};
106
107static const unsigned char thread_ev_states[EVENT_CT] =
108{
109 [ACTEVENT] = THREAD_ACTEVENT,
110 [BUTEVENT] = THREAD_BUTEVENT,
111 [CUSTOMEVENT] = THREAD_CUSTOMEVENT,
112 [PLAYBKEVENT] = THREAD_PLAYBKEVENT,
113 [TIMEREVENT] = THREAD_TIMEREVENT,
114};
115
116static const char *const ev_map[EVENT_CT] =
117{
118 [ACTEVENT] = "action",
119 [BUTEVENT] = "button",
120 [CUSTOMEVENT] = "custom",
121 [PLAYBKEVENT] = "playback",
122 [TIMEREVENT] = "timer",
123};
124
125struct cb_data {
126 int cb_ref;
127 unsigned long id;
128 void *data;
129};
130
131struct event_data {
132 /* lua */
133 lua_State *L;
134 lua_State *NEWL;
135 /* rockbox */
136 unsigned int thread_id;
137 int thread_state;
138 long *event_stack;
139 long timer_ticks;
140 short next_input;
141 short next_event;
142 /* callbacks */
143 struct cb_data *cb[EVENT_CT];
144};
145
146static struct event_data ev_data;
147static struct mutex rev_mtx SHAREDBSS_ATTR;
148
149#ifdef DEBUG_EV
150static int dbg_hook_calls = 0;
151#endif
152
153static inline bool has_event(int ev_flag)
154{
155 return ((THREAD_STATEMASK & (ev_data.thread_state & ev_flag)) == ev_flag);
156}
157
158static inline bool is_suspend(int ev_flag)
159{
160 ev_flag <<= 8;
161 return ((THREAD_SUSPENDMASK & (ev_data.thread_state & ev_flag)) == ev_flag);
162}
163
164static void init_event_data(lua_State *L, struct event_data *ev_data)
165{
166 /* lua */
167 ev_data->L = L;
168 //ev_data->NEWL = NULL;
169 /* rockbox */
170 ev_data->thread_id = UINT_MAX;
171 ev_data->thread_state = THREAD_YIELD;
172 //ev_data->event_stack = NULL;
173 //ev_data->timer_ticks = 0;
174 ev_data->next_input = EV_TICKS;
175 ev_data->next_event = EV_INPUT;
176 /* callbacks */
177 for (int i= 0; i < EVENT_CT; i++)
178 ev_data->cb[i] = NULL;
179}
180
181/* lock and unlock routines allow us to execute the event thread without
182 * trashing the lua state on error, yield, or sleep in the callback functions */
183
184static inline void rev_lock_mtx(void)
185{
186 rb->mutex_lock(&rev_mtx);
187}
188
189static inline void rev_unlock_mtx(void)
190{
191 rb->mutex_unlock(&rev_mtx);
192}
193
194static void lua_interrupt_callback( lua_State *L, lua_Debug *ar)
195{
196 (void) L;
197 (void) ar;
198#ifdef DEBUG_EV
199 dbg_hook_calls++;
200#endif
201
202 rb->yield();
203
204 rev_lock_mtx();
205 rev_unlock_mtx(); /* must wait till event thread is done to continue */
206
207#ifdef DEBUG_EV
208 rb->splashf(0, "spin %d, hooked %d", dbg_hook_calls, (lua_gethookmask(L) != 0));
209 unsigned char delay = -1;
210 /* we can't sleep or yield without affecting count so lets spin in a loop */
211 while(delay > 0) {delay--;}
212 if (lua_gethookmask(L) == 0)
213 dbg_hook_calls = 0;
214#endif
215
216 /* if callback error, pass error to the main lua state */
217 if (lua_status(ev_data.NEWL) != LUA_SUCCESS)
218 luaL_error (L, lua_tostring (ev_data.NEWL, -1));
219}
220
221static void lua_interrupt_set(lua_State *L, bool is_enabled)
222{
223 const int hookmask = LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT;
224
225 if (is_enabled)
226 lua_sethook(L, lua_interrupt_callback, hookmask, 1 );
227 else
228 lua_sethook(L, NULL, 0, 0 );
229}
230
231static int lua_rev_callback(lua_State *L, struct cb_data *cbd)
232{
233 int lua_status = LUA_ERRRUN;
234
235 if (L != NULL)
236 {
237 /* load cb function from lua registry */
238 lua_rawgeti(L, LUA_REGISTRYINDEX, cbd->cb_ref);
239
240 lua_pushinteger(L, cbd->id);
241 lua_pushlightuserdata (L, cbd->data);
242
243 lua_status = lua_resume(L, 2);
244 if (lua_status == LUA_YIELD) /* coroutine.yield() disallowed */
245 luaL_where(L, 0); /* push error string on stack */
246 }
247 return lua_status;
248}
249
250static void event_thread(void)
251{
252 unsigned long action;
253 int event;
254 int ev_flag;
255
256 while(ev_data.thread_state != THREAD_QUIT && lua_status(ev_data.L) == LUA_SUCCESS)
257 {
258 rev_lock_mtx();
259 lua_interrupt_set(ev_data.L, true);
260
261 for (event = 0; event < EVENT_CT; event++)
262 {
263 ev_flag = thread_ev_states[event];
264 if (!has_event(ev_flag) || is_suspend(ev_flag))
265 continue; /* check next event */
266
267 ev_data.thread_state &= ~(ev_flag); /* event handled */
268
269 switch (event)
270 {
271 case ACTEVENT:
272 action = get_plugin_action(TIMEOUT_NOBLOCK, true);
273 if (action == ACTION_UNKNOWN)
274 continue; /* check next event */
275 else if (action == ACTION_NONE)
276 {
277 /* only send ACTION_NONE once */
278 if (ev_data.cb[ACTEVENT]->id == ACTION_NONE ||
279 rb->button_status() != 0)
280 continue; /* check next event */
281 }
282 ev_data.cb[ACTEVENT]->id = action;
283 break;
284 case BUTEVENT:
285 ev_data.cb[BUTEVENT]->id = rb->button_get(false);
286 if (ev_data.cb[BUTEVENT]->id == 0)
287 continue; /* check next event */
288 break;
289 case CUSTOMEVENT:
290 ev_data.thread_state |= thread_ev_states[CUSTOMEVENT]; // don't reset */
291 break;
292 case PLAYBKEVENT:
293 break;
294 case TIMEREVENT:
295 ev_data.cb[TIMEREVENT]->id = *rb->current_tick + ev_data.timer_ticks;
296 break;
297
298 }
299
300 if (lua_rev_callback(ev_data.NEWL, ev_data.cb[event]) != LUA_SUCCESS)
301 {
302 rev_unlock_mtx();
303 goto event_error;
304 }
305 }
306 rev_unlock_mtx(); /* we are safe to release back to main lua state */
307
308 do
309 {
310#ifdef DEBUG_EV
311 dbg_hook_calls--;
312#endif
313 lua_interrupt_set(ev_data.L, false);
314 ev_data.next_event = EV_TICKS;
315 rb->yield();
316 } while(ev_data.thread_state == THREAD_YIELD || is_suspend(THREAD_SUSPENDMASK));
317
318 }
319
320event_error:
321
322 /* thread is exiting -- clean up */
323 rb->timer_unregister();
324 rb->yield();
325 rb->thread_exit();
326
327 return;
328}
329
330/* timer interrupt callback */
331static void rev_timer_isr(void)
332{
333 ev_data.next_event--;
334 ev_data.next_input--;
335
336 if (ev_data.next_input <=0)
337 {
338 ev_data.thread_state |= ((ev_data.thread_state & THREAD_INPUTMASK) >> 16);
339 ev_data.next_input = EV_INPUT;
340 }
341
342 if (ev_data.cb[TIMEREVENT] != NULL && !is_suspend(TIMEREVENT))
343 {
344 if (TIME_AFTER(*rb->current_tick, ev_data.cb[TIMEREVENT]->id))
345 {
346 ev_data.thread_state |= thread_ev_states[TIMEREVENT];
347 ev_data.next_event = 0;
348 }
349 }
350
351 if (ev_data.next_event <= 0)
352 lua_interrupt_set(ev_data.L, true);
353}
354
355static void create_event_thread_ref(struct event_data *ev_data)
356{
357 lua_State *L = ev_data->L;
358
359 lua_createtable(L, 2, 0);
360
361 ev_data->event_stack = (long *) lua_newuserdata (L, DEFAULT_STACK_SIZE);
362
363 /* attach EVENT_METATABLE to ud so we get notified on garbage collection */
364 luaL_getmetatable (L, EVENT_METATABLE);
365 lua_setmetatable (L, -2);
366 lua_rawseti(L, -2, 1);
367
368 ev_data->NEWL = lua_newthread(L);
369 lua_rawseti(L, -2, 2);
370
371 lua_setfield (L, LUA_REGISTRYINDEX, EVENT_THREAD); /* store references */
372}
373
374static void destroy_event_thread_ref(struct event_data *ev_data)
375{
376 lua_State *L = ev_data->L;
377 ev_data->event_stack = NULL;
378 ev_data->NEWL = NULL;
379 lua_pushnil(L);
380 lua_setfield (L, LUA_REGISTRYINDEX, EVENT_THREAD); /* free references */
381}
382
383static void exit_event_thread(struct event_data *ev_data)
384{
385 ev_data->thread_state = THREAD_QUIT;
386 rb->thread_wait(ev_data->thread_id); /* wait for thread to exit */
387
388 ev_data->thread_state = THREAD_YIELD;
389 ev_data->thread_id = UINT_MAX;
390}
391
392static void init_event_thread(bool init, struct event_data *ev_data)
393{
394 if (ev_data->event_stack != NULL) /* make sure we don't double free */
395 {
396 if (!init && ev_data->thread_id != UINT_MAX)
397 {
398 exit_event_thread(ev_data);
399 destroy_event_thread_ref(ev_data);
400 lua_interrupt_set(ev_data->L, false);
401 }
402 return;
403 }
404 else if (!init)
405 return;
406
407 create_event_thread_ref(ev_data);
408 if (ev_data->NEWL == NULL || ev_data->event_stack == NULL)
409 return;
410
411 ev_data->thread_id = rb->create_thread(&event_thread,
412 ev_data->event_stack,
413 DEFAULT_STACK_SIZE,
414 0,
415 EVENT_THREAD
416 IF_PRIO(, PRIORITY_SYSTEM)
417 IF_COP(, COP));
418
419 /* Timer is used to poll waiting events */
420 rb->timer_register(0, NULL, EV_TIMER_FREQ, rev_timer_isr IF_COP(, CPU));
421}
422
423static void playback_event_callback(unsigned short id, void *data)
424{
425 /* playback events are synchronous we need to return ASAP so set a flag */
426 ev_data.thread_state |= thread_ev_states[PLAYBKEVENT];
427 ev_data.cb[PLAYBKEVENT]->id = id;
428 ev_data.cb[PLAYBKEVENT]->data = data;
429 lua_interrupt_set(ev_data.L, true);
430}
431
432static void register_playbk_events(int flag_events,
433 void (*handler)(unsigned short id, void *data))
434{
435 long unsigned int i = 0;
436 const unsigned short playback_events[7] =
437 { /*flags*/
438 PLAYBACK_EVENT_START_PLAYBACK, /* 0x1 */
439 PLAYBACK_EVENT_TRACK_BUFFER, /* 0x2 */
440 PLAYBACK_EVENT_CUR_TRACK_READY, /* 0x4 */
441 PLAYBACK_EVENT_TRACK_FINISH, /* 0x8 */
442 PLAYBACK_EVENT_TRACK_CHANGE, /* 0x10*/
443 PLAYBACK_EVENT_TRACK_SKIP, /* 0x20*/
444 PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE /* 0x40*/
445 };
446
447 for(; i < ARRAYLEN(playback_events); i++, flag_events >>= 1)
448 {
449 if (flag_events == 0) /* remove events */
450 rb->remove_event(playback_events[i], handler);
451 else /* add events */
452 if ((flag_events & 0x1) == 0x1)
453 rb->add_event(playback_events[i], handler);
454 }
455}
456
457static void destroy_event_userdata(lua_State *L, int event)
458{
459 if (ev_data.cb[event] != NULL)
460 {
461 int ev_flag = thread_ev_states[event];
462 ev_data.thread_state &= ~(ev_flag | (ev_flag << 8) | (ev_flag << 16));
463
464 luaL_unref (L, LUA_REGISTRYINDEX, ev_data.cb[event]->cb_ref);
465 ev_data.cb[event] = NULL;
466 }
467}
468
469static void create_event_userdata(lua_State *L, int event, int index)
470{
471 /* if function is already registered , unregister it */
472 destroy_event_userdata(L, event);
473
474 if (!lua_isfunction (L, index))
475 {
476 init_event_thread(false, &ev_data);
477 luaL_typerror (L, index, "function");
478 return;
479 }
480
481 lua_pushvalue (L, index); /* copy passed lua function on top of stack */
482 int ref_lua = luaL_ref(L, LUA_REGISTRYINDEX);
483
484 ev_data.cb[event] = (struct cb_data *)lua_newuserdata(L, sizeof(struct cb_data));
485
486 ev_data.cb[event]->cb_ref = ref_lua; /* store ref for later call/release */
487
488 /* attach EVENT_METATABLE to ud so we get notified on garbage collection */
489 luaL_getmetatable (L, EVENT_METATABLE);
490 lua_setmetatable (L, -2);
491 /* cb_data is on top of stack */
492}
493
494static int rockev_gc(lua_State *L) {
495 bool has_events = false;
496 void *d = (void *) lua_touserdata (L, 1);
497
498 if (d == NULL)
499 return 0;
500 else if (d == ev_data.event_stack) /* thread stack is gc'd kill thread */
501 init_event_thread(false, &ev_data);
502 else if (d == ev_data.cb[PLAYBKEVENT])
503 register_playbk_events(0, &playback_event_callback);
504
505 for( int i= 0; i < EVENT_CT; i++)
506 {
507 if (d == ev_data.cb[i])
508 destroy_event_userdata(L, i);
509 else if (ev_data.cb[i] != NULL)
510 has_events = true;
511 }
512
513 if (!has_events) /* nothing to wait for kill thread */
514 init_event_thread(false, &ev_data);
515
516 return 0;
517}
518
519/******************************************************************************
520 * LUA INTERFACE **************************************************************
521*******************************************************************************
522*/
523
524static int rockev_register(lua_State *L)
525{
526 int event = luaL_checkoption(L, 1, NULL, ev_map);
527 int ev_flag = thread_ev_states[event];
528 int playbk_events;
529
530 lua_settop (L, 3); /* we need to lock our optional args before...*/
531 create_event_userdata(L, event, 2);/* cb_data is on top of stack */
532
533 switch (event)
534 {
535 case ACTEVENT:
536 /* fall through */
537 case BUTEVENT:
538 ev_data.thread_state |= (ev_flag | (ev_flag << 16));
539 break;
540 case CUSTOMEVENT:
541 break;
542 case PLAYBKEVENT:
543 /* see register_playbk_events() for flags */
544 playbk_events = luaL_optinteger(L, 3, 0x3F);
545 register_playbk_events(playbk_events, &playback_event_callback);
546 break;
547 case TIMEREVENT:
548 ev_data.timer_ticks = luaL_checkinteger(L, 3);
549 ev_data.cb[TIMEREVENT]->id = *rb->current_tick + ev_data.timer_ticks;
550 break;
551 }
552
553 init_event_thread(true, &ev_data);
554
555 return 1; /* returns cb_data */
556}
557
558static int rockev_suspend(lua_State *L)
559{
560 int event; /*Arg 1 is event pass nil to suspend all */
561 bool suspend = luaL_optboolean(L, 2, true);
562 int ev_flag = THREAD_SUSPENDMASK;
563
564 if (!lua_isnoneornil(L, 1))
565 {
566 event = luaL_checkoption(L, 1, NULL, ev_map);
567 ev_flag = thread_ev_states[event] << 8;
568 }
569
570 if (suspend)
571 ev_data.thread_state |= ev_flag;
572 else
573 ev_data.thread_state &= ~(ev_flag);
574
575 return 0;
576}
577
578static int rockev_trigger(lua_State *L)
579{
580 int event = luaL_checkoption(L, 1, NULL, ev_map);
581 bool enable = luaL_optboolean(L, 2, true);
582
583 int ev_flag;
584
585 /* protect from invalid events */
586 if (ev_data.cb[event] != NULL)
587 {
588 ev_flag = thread_ev_states[event];
589 /* allow user to pass an id to some of the callback functions */
590 ev_data.cb[event]->id = luaL_optinteger(L, 3, ev_data.cb[event]->id);
591
592 if (enable)
593 ev_data.thread_state |= ev_flag;
594 else
595 ev_data.thread_state &= ~(ev_flag);
596 }
597 return 0;
598}
599
600static int rockev_unregister(lua_State *L)
601{
602 luaL_checkudata (L, 1, EVENT_METATABLE);
603 rockev_gc(L);
604 lua_pushnil(L);
605 return 1;
606}
607/*
608** Creates events metatable.
609*/
610static int event_create_meta (lua_State *L) {
611 luaL_newmetatable (L, EVENT_METATABLE);
612 /* set __gc field so we can clean-up our objects */
613 lua_pushcfunction (L, rockev_gc);
614 lua_setfield (L, -2, "__gc");
615 return 1;
616}
617
618static const struct luaL_reg evlib[] = {
619 {"register", rockev_register},
620 {"suspend", rockev_suspend},
621 {"trigger", rockev_trigger},
622 {"unregister", rockev_unregister},
623 {NULL, NULL}
624};
625
626int luaopen_rockevents (lua_State *L) {
627 rb->mutex_init(&rev_mtx);
628 init_event_data(L, &ev_data);
629 event_create_meta (L);
630 luaL_register (L, LUA_ROCKEVENTSNAME, evlib);
631 return 1;
632}
diff --git a/apps/plugins/lua/rocklib_events.h b/apps/plugins/lua/rocklib_events.h
new file mode 100644
index 0000000000..c96a25e577
--- /dev/null
+++ b/apps/plugins/lua/rocklib_events.h
@@ -0,0 +1,5 @@
1#define LUA_ROCKEVENTSNAME "rockev"
2int luaopen_rockevents (lua_State *L);
3
4/* in rockaux.c */
5int get_plugin_action(int timeout, bool with_remote);
diff --git a/apps/plugins/lua/rocklua.c b/apps/plugins/lua/rocklua.c
index eec6ee54f9..0d0b1f63f7 100644
--- a/apps/plugins/lua/rocklua.c
+++ b/apps/plugins/lua/rocklua.c
@@ -26,7 +26,7 @@
26#include "rocklib.h" 26#include "rocklib.h"
27#include "rocklib_img.h" 27#include "rocklib_img.h"
28#include "luadir.h" 28#include "luadir.h"
29 29#include "rocklib_events.h"
30 30
31 31
32static const luaL_Reg lualibs[] = { 32static const luaL_Reg lualibs[] = {
@@ -40,6 +40,7 @@ static const luaL_Reg lualibs[] = {
40 {LUA_OSLIBNAME, luaopen_os}, 40 {LUA_OSLIBNAME, luaopen_os},
41 {LUA_ROCKLIBNAME, luaopen_rock}, 41 {LUA_ROCKLIBNAME, luaopen_rock},
42 {LUA_ROCKLIBNAME, luaopen_rock_img}, 42 {LUA_ROCKLIBNAME, luaopen_rock_img},
43 {LUA_ROCKEVENTSNAME, luaopen_rockevents},
43 {LUA_DIRLIBNAME, luaopen_luadir}, 44 {LUA_DIRLIBNAME, luaopen_luadir},
44 {NULL, NULL} 45 {NULL, NULL}
45}; 46};