A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 686 lines 20 kB view raw
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}