A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
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 suspends all events.
51 * stops event from executing, any event before re-enabling will be lost.
52 * Passing false, unregistering or re-registering an event will clear the suspend
53 *
54 * rockev.unregister(evX)
55 * Use unregister(evX) to remove an event
56 * Unregistering is not necessary before script end, it will be
57 * cleaned up on script exit
58 *
59 *******************************************************************************
60 * *
61 */
62
63#define LUA_LIB
64
65#define _ROCKCONF_H_ /* We don't need strcmp() etc. wrappers */
66#include "lua.h"
67#include "lauxlib.h"
68#include "plugin.h"
69#include "rocklib_events.h"
70
71#define EVENT_METATABLE "event metatable"
72
73#define EVENT_THREAD LUA_ROCKEVENTSNAME ".thread"
74#define EV_STACKSZ (DEFAULT_STACK_SIZE * 2)
75
76#define LUA_SUCCESS 0
77
78#define EV_TIMER_TICKS 5 /* timer resolution */
79#define EV_TIMER_FREQ ((TIMER_FREQ / HZ) * EV_TIMER_TICKS)
80#define EV_TICKS (HZ / 5)
81
82#define ENABLE_LUA_HOOK (LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT)
83#define DISABLE_LUA_HOOK (0)
84
85enum {
86 THREAD_ERROR = 0x0,
87/* event & suspend states */
88 THREAD_ACTEVENT = 0x1,
89 THREAD_BTNEVENT = 0x2,
90 THREAD_CUSTOMEVENT = 0x4,
91 THREAD_PLAYBKEVENT = 0x8,
92 THREAD_TIMEREVENT = 0x10,
93 //THREAD_AVAILEVENT = 0x20,
94 //THREAD_AVAILEVENT = 0x40,
95 //THREAD_AVAILEVENT = 0x80,
96 THREAD_EVENT_ALL = 0xFF,
97/* thread states */
98 //THREAD_AVAILSTATE = 0x1,
99 //THREAD_AVAILSTATE = 0x2,
100 //THREAD_AVAILSTATE = 0x4,
101 //THREAD_AVAILSTATE = 0x8,
102 //THREAD_AVAILSTATE = 0x10,
103 //THREAD_AVAILSTATE = 0x20,
104 THREAD_QUIT = 0x40,
105 //THREAD_AVAILSTATE = 0x80,
106};
107
108enum {
109 ACTEVENT = 0,
110 BTNEVENT,
111 CUSTOMEVENT,
112 PLAYBKEVENT,
113 TIMEREVENT,
114 EVENT_CT
115};
116
117struct cb_data {
118 int cb_ref;
119 unsigned long id;
120 void *data;
121 long next_tick;
122 long ticks;
123};
124
125struct thread_status {
126 uint8_t event;
127 uint8_t suspended;
128 uint8_t thread;
129 uint8_t unused;
130};
131
132struct event_data {
133 /* lua */
134 lua_State *L;
135 lua_State *NEWL;
136 /* rockbox */
137 unsigned int thread_id;
138 long *event_stack;
139 volatile long *get_tick;
140 struct thread_status status;
141 /* callbacks */
142 struct cb_data *cb[EVENT_CT];
143};
144
145static void set_event_meta(lua_State *L);
146static struct event_data ev_data;
147static struct mutex rev_mtx SHAREDBSS_ATTR;
148
149static inline uint8_t event_flag(unsigned int event)
150{
151 return (1UL << event) & 0xFF;
152}
153
154static inline bool has_thread_status(uint8_t ev_flag)
155{
156 return (ev_data.status.thread & ev_flag) == ev_flag;
157}
158
159static inline void set_evt(uint8_t ev_flag)
160{
161 ev_data.status.event |= ev_flag;
162}
163
164static inline bool remove_evt(uint8_t ev_flag)
165{
166 /* returns previous flag status and clears it */
167 bool has_flag = (ev_data.status.event & ev_flag) == ev_flag;
168 ev_data.status.event &= ~(ev_flag);
169 return has_flag;
170}
171
172static inline bool is_suspend(uint8_t ev_flag)
173{
174 return (ev_data.status.suspended & ev_flag) == ev_flag;
175}
176
177static void suspend_evt(uint8_t ev_flag, bool suspend)
178{
179 if(!suspend && !has_thread_status(THREAD_QUIT))
180 {
181 ev_data.status.suspended &= ~(ev_flag);
182 ev_flag = 0;
183 }
184 remove_evt(ev_flag);
185 ev_data.status.suspended |= ev_flag;
186}
187
188/* mutex lock and unlock routines allow us to execute the event thread without
189 * trashing the lua state on error, yield, or sleep in the callback functions */
190
191static inline void rev_lock_mtx(void)
192{
193 rb->mutex_lock(&rev_mtx);
194}
195
196static inline void rev_unlock_mtx(void)
197{
198 rb->mutex_unlock(&rev_mtx);
199}
200
201static void lua_interrupt_callback(lua_State *L, lua_Debug *ar)
202{
203 (void) ar;
204
205 rb->yield();
206
207 rev_lock_mtx();
208 rev_unlock_mtx(); /* must wait till event thread is done to continue */
209
210 /* if callback error, pass error to the main lua state */
211 if (lua_status(ev_data.NEWL) != LUA_SUCCESS)
212 luaL_error(L, lua_tostring(ev_data.NEWL, -1));
213}
214
215static void lua_interrupt_set(lua_State *L, int hookmask)
216{
217 lua_Hook hook;
218 int count;
219 static lua_Hook oldhook = NULL;
220 static int oldmask = 0;
221 static int oldcount = 0;
222
223 if (hookmask == ENABLE_LUA_HOOK)
224 {
225 hook = lua_gethook(L);
226 if (hook == lua_interrupt_callback)
227 return; /* our hook is already active */
228 /* preserve prior hook */
229 oldhook = hook;
230 oldmask = lua_gethookmask(L);
231 oldcount = lua_gethookcount(L);
232 hook = lua_interrupt_callback;
233 count = 10;
234 }
235 else
236 {
237 hook = oldhook;
238 hookmask = oldmask;
239 count = oldcount;
240 }
241
242 lua_sethook(L, hook, hookmask, count);
243}
244
245static int lua_rev_callback(lua_State *L, struct cb_data *evt)
246{
247 int lua_status = LUA_ERRRUN;
248
249 /* load cb function from lua registry */
250 lua_rawgeti(L, LUA_REGISTRYINDEX, evt->cb_ref);
251
252 lua_pushinteger(L, evt->id);
253 lua_pushlightuserdata(L, evt->data);
254
255 lua_status = lua_resume(L, 2); /* call the saved function */
256 if (lua_status == LUA_SUCCESS)
257 lua_settop(L, 0); /* eat any value(s) returned */
258 else if (lua_status == LUA_YIELD) /* coroutine.yield() disallowed */
259 luaL_where(L, 1); /* push error string on stack */
260
261 return lua_status;
262}
263
264/* timer interrupt callback */
265static void rev_timer_isr(void)
266{
267 uint8_t ev_flag = 0;
268 long curr_tick = *(ev_data.get_tick);
269 struct cb_data *evt;
270
271 for (unsigned int i= 0; i < EVENT_CT; i++)
272 {
273 if (!is_suspend(event_flag(i)))
274 {
275 evt = ev_data.cb[i];
276
277 if ((i == ACTEVENT || i == BTNEVENT) &&
278 (rb->button_queue_count() || rb->button_status() || evt->id))
279 ev_flag |= event_flag(i); /* any buttons ready? */
280 else if(evt->ticks > 0 && TIME_AFTER(curr_tick, evt->next_tick))
281 ev_flag |= event_flag(i);
282
283 }
284 }
285 set_evt(ev_flag);
286 if (ev_data.status.event) /* any events ready? */
287 lua_interrupt_set(ev_data.L, ENABLE_LUA_HOOK);
288}
289
290static void event_thread(void)
291{
292 unsigned long action;
293 uint8_t ev_flag;
294 unsigned int event;
295 struct cb_data *evt;
296
297 while(!has_thread_status(THREAD_QUIT) && lua_status(ev_data.L) == LUA_SUCCESS)
298 {
299 rev_lock_mtx();
300
301 lua_interrupt_set(ev_data.L, ENABLE_LUA_HOOK);
302
303 for (event = 0; event < EVENT_CT; event++)
304 {
305 ev_flag = event_flag(event);
306 if (!remove_evt(ev_flag) || is_suspend(ev_flag))
307 continue; /* check next event */
308
309 evt = ev_data.cb[event];
310
311 switch (event)
312 {
313 case ACTEVENT:
314 action = get_plugin_action(TIMEOUT_NOBLOCK, true);
315 if (action == ACTION_UNKNOWN)
316 continue; /* check next event */
317 else if (action == ACTION_NONE)
318 {
319 /* only send ACTION_NONE once */
320 if (evt->id == ACTION_NONE || rb->button_status() != 0)
321 continue; /* check next event */
322 }
323 evt->id = action;
324 break;
325 case BTNEVENT:
326 evt->id = rb->button_get(false);
327 /* only send BUTTON_NONE once */
328 if (evt->id == BUTTON_NONE)
329 continue; /* check next event */
330 break;
331 case CUSTOMEVENT:
332 case PLAYBKEVENT:
333 case TIMEREVENT:
334 break;
335
336 }
337
338 if (lua_rev_callback(ev_data.NEWL, evt) != LUA_SUCCESS)
339 {
340 rev_unlock_mtx();
341 goto event_error;
342 }
343 evt->next_tick = *(ev_data.get_tick) + evt->ticks;
344 }
345 rev_unlock_mtx(); /* we are safe to release back to main lua state */
346
347 do
348 {
349 lua_interrupt_set(ev_data.L, DISABLE_LUA_HOOK);
350 rb->yield();
351 } while (!has_thread_status(THREAD_QUIT) && (is_suspend(THREAD_EVENT_ALL)
352 || !ev_data.status.event));
353
354 }
355 rb->yield();
356 lua_interrupt_set(ev_data.L, DISABLE_LUA_HOOK);
357
358event_error:
359
360 /* thread is exiting -- clean up */
361 rb->timer_unregister();
362 rb->thread_exit();
363
364 return;
365}
366
367static inline void create_event_thread_ref(struct event_data *ev_data)
368{
369 lua_State *L = ev_data->L;
370
371 lua_createtable(L, 2, 0);
372
373 ev_data->event_stack = (long *) lua_newuserdata(L, EV_STACKSZ);
374
375 /* attach EVENT_METATABLE to ud so we get notified on garbage collection */
376 set_event_meta(L);
377 lua_rawseti(L, -2, 1);
378
379 ev_data->NEWL = lua_newthread(L);
380 lua_rawseti(L, -2, 2);
381
382 lua_setfield(L, LUA_REGISTRYINDEX, EVENT_THREAD); /* store references */
383}
384
385static inline void destroy_event_thread_ref(struct event_data *ev_data)
386{
387 lua_State *L = ev_data->L;
388 ev_data->event_stack = NULL;
389 ev_data->NEWL = NULL;
390 lua_pushnil(L);
391 lua_setfield(L, LUA_REGISTRYINDEX, EVENT_THREAD); /* free references */
392}
393
394static inline void exit_event_thread(struct event_data *ev_data)
395{
396 suspend_evt(THREAD_EVENT_ALL, true);
397 ev_data->status.thread = THREAD_QUIT;
398
399 rb->thread_wait(ev_data->thread_id); /* wait for thread to exit */
400 destroy_event_thread_ref(ev_data);
401
402 ev_data->status.thread = 0;
403 ev_data->thread_id = UINT_MAX;
404}
405
406static void init_event_thread(bool init, struct event_data *ev_data)
407{
408 if (ev_data->event_stack != NULL) /* make sure we don't double free */
409 {
410 if (!init && ev_data->thread_id != UINT_MAX)
411 exit_event_thread(ev_data);
412
413 return;
414 }
415 else if (!init)
416 return;
417
418 create_event_thread_ref(ev_data);
419
420 ev_data->thread_id = rb->create_thread(&event_thread,
421 ev_data->event_stack,
422 EV_STACKSZ,
423 0,
424 EVENT_THREAD
425 IF_PRIO(, PRIORITY_SYSTEM)
426 IF_COP(, CPU));
427
428 /* Timer is used to poll waiting events */
429 if (!rb->timer_register(1, NULL, EV_TIMER_FREQ, rev_timer_isr IF_COP(, CPU)))
430 {
431 rb->splash(100, "No timer available!");
432 }
433}
434
435static void playback_event_callback(unsigned short id, void *data)
436{
437 /* playback events are synchronous we need to return ASAP so set a flag */
438 struct cb_data *evt = ev_data.cb[PLAYBKEVENT];
439 evt->id = id;
440 evt->data = data;
441 if (!is_suspend(THREAD_PLAYBKEVENT))
442 {
443 set_evt(THREAD_PLAYBKEVENT);
444 lua_interrupt_set(ev_data.L, ENABLE_LUA_HOOK);
445 }
446}
447
448static void register_playbk_events(int flag_events)
449{
450 unsigned short i, pb_evt;
451 static const unsigned short playback_events[7] =
452 { /*flags*/
453 PLAYBACK_EVENT_START_PLAYBACK, /* 0x1 */
454 PLAYBACK_EVENT_TRACK_BUFFER, /* 0x2 */
455 PLAYBACK_EVENT_CUR_TRACK_READY, /* 0x4 */
456 PLAYBACK_EVENT_TRACK_FINISH, /* 0x8 */
457 PLAYBACK_EVENT_TRACK_CHANGE, /* 0x10*/
458 PLAYBACK_EVENT_TRACK_SKIP, /* 0x20*/
459 PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE /* 0x40*/
460 };
461
462 for(i = 0; i < ARRAYLEN(playback_events); i++)
463 {
464 pb_evt = playback_events[i];
465 if (!(flag_events & (1 << i)))
466 rb->remove_event(pb_evt, playback_event_callback);
467 else
468 rb->add_event(pb_evt, playback_event_callback);
469 }
470}
471
472static void destroy_event_userdata(lua_State *L, unsigned int event)
473{
474 uint8_t ev_flag = event_flag(event);
475 struct cb_data *evt = ev_data.cb[event];
476 suspend_evt(ev_flag, true);
477 if (evt != NULL)
478 luaL_unref(L, LUA_REGISTRYINDEX, evt->cb_ref);
479
480 ev_data.cb[event] = NULL;
481}
482
483static void create_event_userdata(lua_State *L, unsigned int event, int index)
484{
485 /* if function is already registered , unregister it */
486 destroy_event_userdata(L, event);
487
488 luaL_checktype(L, index, LUA_TFUNCTION);
489 lua_pushvalue(L, index); /* copy passed lua function on top of stack */
490 int ref_lua = luaL_ref(L, LUA_REGISTRYINDEX);
491
492 ev_data.cb[event] = (struct cb_data *)lua_newuserdata(L, sizeof(struct cb_data));
493
494 ev_data.cb[event]->cb_ref = ref_lua; /* store ref for later call/release */
495
496 /* attach EVENT_METATABLE to ud so we get notified on garbage collection */
497 set_event_meta(L);
498 /* cb_data is on top of stack */
499}
500
501static int rockev_gc(lua_State *L) {
502 bool has_events = false;
503 void *d = (void *) lua_touserdata(L, 1);
504
505 if (d != NULL)
506 {
507 for (unsigned int i= 0; i < EVENT_CT; i++)
508 {
509 if (d == ev_data.cb[i] || d == ev_data.event_stack)
510 {
511 if (i == PLAYBKEVENT)
512 register_playbk_events(0);
513 destroy_event_userdata(L, i);
514 }
515 else if (ev_data.cb[i] != NULL)
516 has_events = true;
517 }
518
519 if (!has_events) /* nothing to wait for kill thread */
520 init_event_thread(false, &ev_data);
521 }
522 return 0;
523}
524
525static void set_event_meta(lua_State *L)
526{
527 if (luaL_newmetatable(L, EVENT_METATABLE))
528 {
529 /* set __gc field so we can clean-up our objects */
530 lua_pushcfunction(L, rockev_gc);
531 lua_setfield(L, -2, "__gc");
532 }
533 lua_setmetatable(L, -2);
534}
535
536static void init_event_data(lua_State *L, struct event_data *ev_data)
537{
538 /* lua */
539 ev_data->L = L;
540 /*ev_data->NEWL = NULL;*/
541 /* rockbox */
542 ev_data->thread_id = UINT_MAX;
543 ev_data->get_tick = rb->current_tick;
544
545 ev_data->status.event = 0;
546 ev_data->status.suspended = THREAD_EVENT_ALL;
547 ev_data->status.thread = 0;
548
549 /*ev_data->event_stack = NULL;*/
550 /* callbacks */
551 for (unsigned int i= 0; i < EVENT_CT; i++)
552 ev_data->cb[i] = NULL;
553}
554
555/******************************************************************************
556 * LUA INTERFACE **************************************************************
557*******************************************************************************
558*/
559static unsigned int get_event_by_name(lua_State *L)
560{
561 static const char *const ev_map[EVENT_CT] =
562 {
563 [ACTEVENT] = "action",
564 [BTNEVENT] = "button",
565 [CUSTOMEVENT] = "custom",
566 [PLAYBKEVENT] = "playback",
567 [TIMEREVENT] = "timer",
568 };
569
570 return luaL_checkoption(L, 1, NULL, ev_map);
571}
572
573
574static int rockev_register(lua_State *L)
575{
576 /* register (event, cb [, args] */
577 unsigned int event = get_event_by_name(L);
578 uint8_t ev_flag = event_flag(event);
579 int playbk_events;
580
581 lua_settop(L, 3); /* we need to lock our optional args before...*/
582 create_event_userdata(L, event, 2);/* cb_data is on top of stack */
583 init_event_thread(!has_thread_status(THREAD_QUIT), &ev_data);
584
585 long event_ticks = 0;
586 struct cb_data *evt;
587
588 switch (event)
589 {
590 case ACTEVENT:
591 /* fall through */
592 case BTNEVENT:
593 event_ticks = 0; /* button events not triggered by timeout */
594 break;
595 case CUSTOMEVENT:
596 event_ticks = luaL_optinteger(L, 3, EV_TICKS);
597 ev_flag = 0; /* don't remove suspend */
598 break;
599 case PLAYBKEVENT: /* see register_playbk_events() for flags */
600 event_ticks = 0; /* playback events are not triggered by timeout */
601 playbk_events = luaL_optinteger(L, 3, 0x3F);
602 register_playbk_events(playbk_events);
603 break;
604 case TIMEREVENT:
605 event_ticks = luaL_checkinteger(L, 3);
606 break;
607 }
608
609 evt = ev_data.cb[event];
610 evt->ticks = event_ticks;
611 evt->next_tick = *(ev_data.get_tick) + event_ticks;
612 suspend_evt(ev_flag, false);
613
614 return 1; /* returns cb_data */
615}
616
617static int rockev_suspend(lua_State *L)
618{
619 unsigned int event; /*Arg 1 is event, pass nil to suspend all */
620 bool suspend = luaL_optboolean(L, 2, true);
621 uint8_t ev_flag = THREAD_EVENT_ALL;
622
623 if (!lua_isnoneornil(L, 1))
624 {
625 event = get_event_by_name(L);
626 ev_flag = event_flag(event);
627 }
628
629 suspend_evt(ev_flag, suspend);
630
631 /* don't resume invalid events */
632 for (unsigned int i = 0; i < EVENT_CT; i++)
633 {
634 if (ev_data.cb[i] == NULL)
635 suspend_evt(event_flag(i), true);
636 }
637
638 return 0;
639}
640
641static int rockev_trigger(lua_State *L)
642{
643 unsigned int event = get_event_by_name(L);
644 bool enable = luaL_optboolean(L, 2, true);
645
646 uint8_t ev_flag = event_flag(event);
647 struct cb_data *evt = ev_data.cb[event];
648
649 /* don't trigger invalid events */
650 if (evt != NULL)
651 {
652 /* allow user to pass an id to some of the callback functions */
653 evt->id = luaL_optinteger(L, 3, evt->id);
654
655 if (event == CUSTOMEVENT)
656 suspend_evt(ev_flag, !enable);
657
658 if (enable)
659 set_evt(ev_flag);
660 else
661 remove_evt(ev_flag);
662 }
663 return 0;
664}
665
666static int rockev_unregister(lua_State *L)
667{
668 luaL_checkudata(L, 1, EVENT_METATABLE);
669 rockev_gc(L);
670 return 0;
671}
672
673static const struct luaL_reg evlib[] = {
674 {"register", rockev_register},
675 {"suspend", rockev_suspend},
676 {"trigger", rockev_trigger},
677 {"unregister", rockev_unregister},
678 {NULL, NULL}
679};
680
681int luaopen_rockevents(lua_State *L) {
682 rb->mutex_init(&rev_mtx);
683 init_event_data(L, &ev_data);
684 luaL_register(L, LUA_ROCKEVENTSNAME, evlib);
685 return 1;
686}