Something went wrong. Try again.
A modern, network-enabled music player platform built on Rockbox technology. rockboxd.tsiry-sandratraina.com
rust deno navidrome airplay libadwaita zig mpris snapcast mpd rockbox audio subsonic
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * * Copyright (C) 2019 William Wilgus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/
/* 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 ev = rockev.register("event", cb_function, [timeout / flags]) * cb_function([id] [, data]) ... end * * * 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.suspend(["event"/nil][true/false]) passing nil suspends all events. * stops event from executing, any event before re-enabling will be lost. * Passing false, unregistering or re-registering an event will clear the suspend * * 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 * ******************************************************************************* * * */
#define LUA_LIB
#define _ROCKCONF_H_ /* We don't need strcmp() etc. wrappers */#include "lua.h"#include "lauxlib.h"#include "plugin.h"#include "rocklib_events.h"
#define EVENT_METATABLE "event metatable"
#define EVENT_THREAD LUA_ROCKEVENTSNAME ".thread"#define EV_STACKSZ (DEFAULT_STACK_SIZE * 2)
#define LUA_SUCCESS 0
#define EV_TIMER_TICKS 5 /* timer resolution */#define EV_TIMER_FREQ ((TIMER_FREQ / HZ) * EV_TIMER_TICKS)#define EV_TICKS (HZ / 5)
#define ENABLE_LUA_HOOK (LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT)#define DISABLE_LUA_HOOK (0)
enum { THREAD_ERROR = 0x0,/* event & suspend states */ THREAD_ACTEVENT = 0x1, THREAD_BTNEVENT = 0x2, THREAD_CUSTOMEVENT = 0x4, THREAD_PLAYBKEVENT = 0x8, THREAD_TIMEREVENT = 0x10, //THREAD_AVAILEVENT = 0x20, //THREAD_AVAILEVENT = 0x40, //THREAD_AVAILEVENT = 0x80, THREAD_EVENT_ALL = 0xFF,/* thread states */ //THREAD_AVAILSTATE = 0x1, //THREAD_AVAILSTATE = 0x2, //THREAD_AVAILSTATE = 0x4, //THREAD_AVAILSTATE = 0x8, //THREAD_AVAILSTATE = 0x10, //THREAD_AVAILSTATE = 0x20, THREAD_QUIT = 0x40, //THREAD_AVAILSTATE = 0x80,};
enum { ACTEVENT = 0, BTNEVENT, CUSTOMEVENT, PLAYBKEVENT, TIMEREVENT, EVENT_CT};
struct cb_data { int cb_ref; unsigned long id; void *data; long next_tick; long ticks;};
struct thread_status { uint8_t event; uint8_t suspended; uint8_t thread; uint8_t unused;};
struct event_data { /* lua */ lua_State *L; lua_State *NEWL; /* rockbox */ unsigned int thread_id; long *event_stack; volatile long *get_tick; struct thread_status status; /* callbacks */ struct cb_data *cb[EVENT_CT];};
static void set_event_meta(lua_State *L);static struct event_data ev_data;static struct mutex rev_mtx SHAREDBSS_ATTR;
static inline uint8_t event_flag(unsigned int event){ return (1UL << event) & 0xFF;}
static inline bool has_thread_status(uint8_t ev_flag){ return (ev_data.status.thread & ev_flag) == ev_flag;}
static inline void set_evt(uint8_t ev_flag){ ev_data.status.event |= ev_flag;}
static inline bool remove_evt(uint8_t ev_flag){ /* returns previous flag status and clears it */ bool has_flag = (ev_data.status.event & ev_flag) == ev_flag; ev_data.status.event &= ~(ev_flag); return has_flag;}
static inline bool is_suspend(uint8_t ev_flag){ return (ev_data.status.suspended & ev_flag) == ev_flag;}
static void suspend_evt(uint8_t ev_flag, bool suspend){ if(!suspend && !has_thread_status(THREAD_QUIT)) { ev_data.status.suspended &= ~(ev_flag); ev_flag = 0; } remove_evt(ev_flag); ev_data.status.suspended |= ev_flag;}
/* mutex lock and unlock routines allow us to execute the event thread without * trashing the lua state on error, yield, or sleep in the callback functions */
static inline void rev_lock_mtx(void){ rb->mutex_lock(&rev_mtx);}
static inline void rev_unlock_mtx(void){ rb->mutex_unlock(&rev_mtx);}
static void lua_interrupt_callback(lua_State *L, lua_Debug *ar){ (void) ar;
rb->yield();
rev_lock_mtx(); rev_unlock_mtx(); /* must wait till event thread is done to continue */
/* if callback error, pass error to the main lua state */ if (lua_status(ev_data.NEWL) != LUA_SUCCESS) luaL_error(L, lua_tostring(ev_data.NEWL, -1));}
static void lua_interrupt_set(lua_State *L, int hookmask){ lua_Hook hook; int count; static lua_Hook oldhook = NULL; static int oldmask = 0; static int oldcount = 0;
if (hookmask == ENABLE_LUA_HOOK) { hook = lua_gethook(L); if (hook == lua_interrupt_callback) return; /* our hook is already active */ /* preserve prior hook */ oldhook = hook; oldmask = lua_gethookmask(L); oldcount = lua_gethookcount(L); hook = lua_interrupt_callback; count = 10; } else { hook = oldhook; hookmask = oldmask; count = oldcount; }
lua_sethook(L, hook, hookmask, count);}
static int lua_rev_callback(lua_State *L, struct cb_data *evt){ int lua_status = LUA_ERRRUN;
/* load cb function from lua registry */ lua_rawgeti(L, LUA_REGISTRYINDEX, evt->cb_ref);
lua_pushinteger(L, evt->id); lua_pushlightuserdata(L, evt->data);
lua_status = lua_resume(L, 2); /* call the saved function */ if (lua_status == LUA_SUCCESS) lua_settop(L, 0); /* eat any value(s) returned */ else if (lua_status == LUA_YIELD) /* coroutine.yield() disallowed */ luaL_where(L, 1); /* push error string on stack */
return lua_status;}
/* timer interrupt callback */static void rev_timer_isr(void){ uint8_t ev_flag = 0; long curr_tick = *(ev_data.get_tick); struct cb_data *evt;
for (unsigned int i= 0; i < EVENT_CT; i++) { if (!is_suspend(event_flag(i))) { evt = ev_data.cb[i];
if ((i == ACTEVENT || i == BTNEVENT) && (rb->button_queue_count() || rb->button_status() || evt->id)) ev_flag |= event_flag(i); /* any buttons ready? */ else if(evt->ticks > 0 && TIME_AFTER(curr_tick, evt->next_tick)) ev_flag |= event_flag(i);
} } set_evt(ev_flag); if (ev_data.status.event) /* any events ready? */ lua_interrupt_set(ev_data.L, ENABLE_LUA_HOOK);}
static void event_thread(void){ unsigned long action; uint8_t ev_flag; unsigned int event; struct cb_data *evt;
while(!has_thread_status(THREAD_QUIT) && lua_status(ev_data.L) == LUA_SUCCESS) { rev_lock_mtx();
lua_interrupt_set(ev_data.L, ENABLE_LUA_HOOK);
for (event = 0; event < EVENT_CT; event++) { ev_flag = event_flag(event); if (!remove_evt(ev_flag) || is_suspend(ev_flag)) continue; /* check next event */
evt = ev_data.cb[event];
switch (event) { case ACTEVENT: action = get_plugin_action(TIMEOUT_NOBLOCK, true); if (action == ACTION_UNKNOWN) continue; /* check next event */ else if (action == ACTION_NONE) { /* only send ACTION_NONE once */ if (evt->id == ACTION_NONE || rb->button_status() != 0) continue; /* check next event */ } evt->id = action; break; case BTNEVENT: evt->id = rb->button_get(false); /* only send BUTTON_NONE once */ if (evt->id == BUTTON_NONE) continue; /* check next event */ break; case CUSTOMEVENT: case PLAYBKEVENT: case TIMEREVENT: break;
}
if (lua_rev_callback(ev_data.NEWL, evt) != LUA_SUCCESS) { rev_unlock_mtx(); goto event_error; } evt->next_tick = *(ev_data.get_tick) + evt->ticks; } rev_unlock_mtx(); /* we are safe to release back to main lua state */
do { lua_interrupt_set(ev_data.L, DISABLE_LUA_HOOK); rb->yield(); } while (!has_thread_status(THREAD_QUIT) && (is_suspend(THREAD_EVENT_ALL) || !ev_data.status.event));
} rb->yield(); lua_interrupt_set(ev_data.L, DISABLE_LUA_HOOK);
event_error:
/* thread is exiting -- clean up */ rb->timer_unregister(); rb->thread_exit();
return;}
static inline void create_event_thread_ref(struct event_data *ev_data){ lua_State *L = ev_data->L;
lua_createtable(L, 2, 0);
ev_data->event_stack = (long *) lua_newuserdata(L, EV_STACKSZ);
/* attach EVENT_METATABLE to ud so we get notified on garbage collection */ set_event_meta(L); lua_rawseti(L, -2, 1);
ev_data->NEWL = lua_newthread(L); lua_rawseti(L, -2, 2);
lua_setfield(L, LUA_REGISTRYINDEX, EVENT_THREAD); /* store references */}
static inline void destroy_event_thread_ref(struct event_data *ev_data){ lua_State *L = ev_data->L; ev_data->event_stack = NULL; ev_data->NEWL = NULL; lua_pushnil(L); lua_setfield(L, LUA_REGISTRYINDEX, EVENT_THREAD); /* free references */}
static inline void exit_event_thread(struct event_data *ev_data){ suspend_evt(THREAD_EVENT_ALL, true); ev_data->status.thread = THREAD_QUIT;
rb->thread_wait(ev_data->thread_id); /* wait for thread to exit */ destroy_event_thread_ref(ev_data);
ev_data->status.thread = 0; ev_data->thread_id = UINT_MAX;}
static void init_event_thread(bool init, struct event_data *ev_data){ if (ev_data->event_stack != NULL) /* make sure we don't double free */ { if (!init && ev_data->thread_id != UINT_MAX) exit_event_thread(ev_data);
return; } else if (!init) return;
create_event_thread_ref(ev_data);
ev_data->thread_id = rb->create_thread(&event_thread, ev_data->event_stack, EV_STACKSZ, 0, EVENT_THREAD IF_PRIO(, PRIORITY_SYSTEM) IF_COP(, CPU));
/* Timer is used to poll waiting events */ if (!rb->timer_register(1, NULL, EV_TIMER_FREQ, rev_timer_isr IF_COP(, CPU))) { rb->splash(100, "No timer available!"); }}
static void playback_event_callback(unsigned short id, void *data){ /* playback events are synchronous we need to return ASAP so set a flag */ struct cb_data *evt = ev_data.cb[PLAYBKEVENT]; evt->id = id; evt->data = data; if (!is_suspend(THREAD_PLAYBKEVENT)) { set_evt(THREAD_PLAYBKEVENT); lua_interrupt_set(ev_data.L, ENABLE_LUA_HOOK); }}
static void register_playbk_events(int flag_events){ unsigned short i, pb_evt; static const unsigned short playback_events[7] = { /*flags*/ PLAYBACK_EVENT_START_PLAYBACK, /* 0x1 */ PLAYBACK_EVENT_TRACK_BUFFER, /* 0x2 */ PLAYBACK_EVENT_CUR_TRACK_READY, /* 0x4 */ PLAYBACK_EVENT_TRACK_FINISH, /* 0x8 */ PLAYBACK_EVENT_TRACK_CHANGE, /* 0x10*/ PLAYBACK_EVENT_TRACK_SKIP, /* 0x20*/ PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE /* 0x40*/ };
for(i = 0; i < ARRAYLEN(playback_events); i++) { pb_evt = playback_events[i]; if (!(flag_events & (1 << i))) rb->remove_event(pb_evt, playback_event_callback); else rb->add_event(pb_evt, playback_event_callback); }}
static void destroy_event_userdata(lua_State *L, unsigned int event){ uint8_t ev_flag = event_flag(event); struct cb_data *evt = ev_data.cb[event]; suspend_evt(ev_flag, true); if (evt != NULL) luaL_unref(L, LUA_REGISTRYINDEX, evt->cb_ref);
ev_data.cb[event] = NULL;}
static void create_event_userdata(lua_State *L, unsigned int event, int index){ /* if function is already registered , unregister it */ destroy_event_userdata(L, event);
luaL_checktype(L, index, LUA_TFUNCTION); lua_pushvalue(L, index); /* copy passed lua function on top of stack */ int ref_lua = luaL_ref(L, LUA_REGISTRYINDEX);
ev_data.cb[event] = (struct cb_data *)lua_newuserdata(L, sizeof(struct cb_data));
ev_data.cb[event]->cb_ref = ref_lua; /* store ref for later call/release */
/* attach EVENT_METATABLE to ud so we get notified on garbage collection */ set_event_meta(L); /* cb_data is on top of stack */}
static int rockev_gc(lua_State *L) { bool has_events = false; void *d = (void *) lua_touserdata(L, 1);
if (d != NULL) { for (unsigned int i= 0; i < EVENT_CT; i++) { if (d == ev_data.cb[i] || d == ev_data.event_stack) { if (i == PLAYBKEVENT) register_playbk_events(0); destroy_event_userdata(L, i); } else if (ev_data.cb[i] != NULL) has_events = true; }
if (!has_events) /* nothing to wait for kill thread */ init_event_thread(false, &ev_data); } return 0;}
static void set_event_meta(lua_State *L){ if (luaL_newmetatable(L, EVENT_METATABLE)) { /* set __gc field so we can clean-up our objects */ lua_pushcfunction(L, rockev_gc); lua_setfield(L, -2, "__gc"); } lua_setmetatable(L, -2);}
static void init_event_data(lua_State *L, struct event_data *ev_data){ /* lua */ ev_data->L = L; /*ev_data->NEWL = NULL;*/ /* rockbox */ ev_data->thread_id = UINT_MAX; ev_data->get_tick = rb->current_tick;
ev_data->status.event = 0; ev_data->status.suspended = THREAD_EVENT_ALL; ev_data->status.thread = 0;
/*ev_data->event_stack = NULL;*/ /* callbacks */ for (unsigned int i= 0; i < EVENT_CT; i++) ev_data->cb[i] = NULL;}
/****************************************************************************** * LUA INTERFACE **********************************************************************************************************************************************/static unsigned int get_event_by_name(lua_State *L){ static const char *const ev_map[EVENT_CT] = { [ACTEVENT] = "action", [BTNEVENT] = "button", [CUSTOMEVENT] = "custom", [PLAYBKEVENT] = "playback", [TIMEREVENT] = "timer", };
return luaL_checkoption(L, 1, NULL, ev_map);}
static int rockev_register(lua_State *L){ /* register (event, cb [, args] */ unsigned int event = get_event_by_name(L); uint8_t ev_flag = event_flag(event); int playbk_events;
lua_settop(L, 3); /* we need to lock our optional args before...*/ create_event_userdata(L, event, 2);/* cb_data is on top of stack */ init_event_thread(!has_thread_status(THREAD_QUIT), &ev_data);
long event_ticks = 0; struct cb_data *evt;
switch (event) { case ACTEVENT: /* fall through */ case BTNEVENT: event_ticks = 0; /* button events not triggered by timeout */ break; case CUSTOMEVENT: event_ticks = luaL_optinteger(L, 3, EV_TICKS); ev_flag = 0; /* don't remove suspend */ break; case PLAYBKEVENT: /* see register_playbk_events() for flags */ event_ticks = 0; /* playback events are not triggered by timeout */ playbk_events = luaL_optinteger(L, 3, 0x3F); register_playbk_events(playbk_events); break; case TIMEREVENT: event_ticks = luaL_checkinteger(L, 3); break; }
evt = ev_data.cb[event]; evt->ticks = event_ticks; evt->next_tick = *(ev_data.get_tick) + event_ticks; suspend_evt(ev_flag, false);
return 1; /* returns cb_data */}
static int rockev_suspend(lua_State *L){ unsigned int event; /*Arg 1 is event, pass nil to suspend all */ bool suspend = luaL_optboolean(L, 2, true); uint8_t ev_flag = THREAD_EVENT_ALL;
if (!lua_isnoneornil(L, 1)) { event = get_event_by_name(L); ev_flag = event_flag(event); }
suspend_evt(ev_flag, suspend);
/* don't resume invalid events */ for (unsigned int i = 0; i < EVENT_CT; i++) { if (ev_data.cb[i] == NULL) suspend_evt(event_flag(i), true); }
return 0;}
static int rockev_trigger(lua_State *L){ unsigned int event = get_event_by_name(L); bool enable = luaL_optboolean(L, 2, true);
uint8_t ev_flag = event_flag(event); struct cb_data *evt = ev_data.cb[event];
/* don't trigger invalid events */ if (evt != NULL) { /* allow user to pass an id to some of the callback functions */ evt->id = luaL_optinteger(L, 3, evt->id);
if (event == CUSTOMEVENT) suspend_evt(ev_flag, !enable);
if (enable) set_evt(ev_flag); else remove_evt(ev_flag); } return 0;}
static int rockev_unregister(lua_State *L){ luaL_checkudata(L, 1, EVENT_METATABLE); rockev_gc(L); return 0;}
static const struct luaL_reg evlib[] = { {"register", rockev_register}, {"suspend", rockev_suspend}, {"trigger", rockev_trigger}, {"unregister", rockev_unregister}, {NULL, NULL}};
int luaopen_rockevents(lua_State *L) { rb->mutex_init(&rev_mtx); init_event_data(L, &ev_data); luaL_register(L, LUA_ROCKEVENTSNAME, evlib); return 1;}