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) 2020 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/* WIP rb_info common info that you wonder about when rockboxing?
23 */
24
25#include "plugin.h"
26#include "lang_enum.h"
27#include "../open_plugin.h"
28#include "logf.h"
29#include "lib/action_helper.h"
30#include "lib/button_helper.h"
31#include "lib/pluginlib_actions.h"
32#include "lib/printcell_helper.h"
33
34#define MENU_ID(x) (((void*)&"RPBUTACNGSX\0" + x))
35enum {
36 M_ROOT = 0,
37 M_PATHS,
38 M_BUFFERS,
39 M_BUTTONS,
40 M_BTNTEST,
41 M_ACTIONS,
42 M_CONTEXTS,
43 M_ACTTEST,
44 M_PLUGINS,
45 M_TESTPUT,
46 M_EXIT,
47 M_LAST_ITEM //ITEM COUNT
48};
49
50#define MENU_ID_PLUGINS_ITEMS 5
51
52/*Action test and Button test*/
53static struct menu_test_t {
54 int count;
55 int context;
56 int last_btn_or_act;
57} m_test;
58
59struct menu_buffer_t { const char *name; size_t size;};
60static const struct menu_buffer_t m_buffer[] =
61{
62#ifndef MAX_LOGF_SIZE
63#define MAX_LOGF_SIZE (0)
64#endif
65#ifndef CACHE_SIZE
66#define CACHE_SIZE (0)
67#endif
68 {"thread stack", DEFAULT_STACK_SIZE},
69 {"plugin buffer", PLUGIN_BUFFER_SIZE},
70 {"frame_buffer", FRAMEBUFFER_SIZE},
71 {"codec_buffer", CODEC_SIZE},
72 {"logf_buffer", MAX_LOGF_SIZE},
73 {"cache", CACHE_SIZE},
74};
75
76/* stringify the macro value */
77#define MACROVAL(x) MACROSTR(x)
78#define MACROSTR(x) #x
79static int main_last_sel = 0;
80static struct gui_synclist lists;
81static void synclist_set(char*, int, int, int);
82
83struct paths { const char *name; const char *path; };
84static const struct paths paths[] = {
85 {"Home", ""HOME_DIR},
86 {"Rockbox", ""ROCKBOX_DIR},
87 {"Plugins", ""PLUGIN_DIR},
88 {"Codecs", ""CODECS_DIR},
89 {"WPS", ""WPS_DIR},
90 {"SBS", ""SBS_DIR},
91 {"Theme", ""THEME_DIR},
92 {"Font", ""FONT_DIR},
93 {"Icon", ""ICON_DIR},
94 {"Backdrop", ""BACKDROP_DIR},
95 {"Eq", ""EQS_DIR},
96 {"Rec Presets", ""RECPRESETS_DIR},
97 {"Recordings", ""REC_BASE_DIR,},
98 {"Fm Presets", ""FMPRESET_PATH},
99 {"MAX_PATH", ""MACROVAL(MAX_PATH)" bytes"},
100};
101#define TESTPUT_HEADER "$*64$col1$col2$*128$col3$col4$col5$col6$*64$col7$col8"
102static int testput_cols = 0;
103struct mainmenu { const char *name; void *menuid; int items;};
104static struct mainmenu mainmenu[M_LAST_ITEM] = {
105#define MENU_ITEM(ID, NAME, COUNT) [ID]{NAME, MENU_ID(ID), (int)COUNT}
106MENU_ITEM(M_ROOT, "Rockbox Info Plugin", M_LAST_ITEM),
107MENU_ITEM(M_PATHS, ID2P(LANG_SHOW_PATH), ARRAYLEN(paths)),
108MENU_ITEM(M_BUFFERS, ID2P(LANG_BUFFER_STAT), ARRAYLEN(m_buffer)),
109MENU_ITEM(M_BUTTONS, "Buttons", -1), /* Set at runtime in plugin_start: */
110MENU_ITEM(M_BTNTEST, "Button test", 2),
111MENU_ITEM(M_ACTIONS, "Actions", LAST_ACTION_PLACEHOLDER),
112MENU_ITEM(M_CONTEXTS, "Contexts", LAST_CONTEXT_PLACEHOLDER ),
113MENU_ITEM(M_ACTTEST, "Action test", 3),
114MENU_ITEM(M_PLUGINS, ID2P(LANG_PLUGINS), MENU_ID_PLUGINS_ITEMS),
115MENU_ITEM(M_TESTPUT, "Printcell test", 36),
116MENU_ITEM(M_EXIT, ID2P(LANG_MENU_QUIT), 0),
117#undef MENU_ITEM
118};
119
120static const struct mainmenu *mainitem(int selected_item)
121{
122 static const struct mainmenu empty = {0};
123 if (selected_item >= 0 && selected_item < (int) ARRAYLEN(mainmenu))
124 return &mainmenu[selected_item];
125 else
126 return ∅
127}
128
129static void cleanup(void *parameter)
130{
131 (void)parameter;
132}
133
134#if 0
135static enum themable_icons menu_icon_cb(int selected_item, void * data)
136{
137 (void)data;
138 (void)selected_item;
139 return Icon_NOICON;
140}
141#endif
142
143static const char *menu_plugin_name_cb(int selected_item, void* data,
144 char* buf, size_t buf_len)
145{
146 (void)data;
147 buf[0] = '\0';
148 switch(selected_item)
149 {
150 case 0:
151 rb->snprintf(buf, buf_len, "%s: [%d bytes] ", "plugin_api", (int)sizeof(struct plugin_api));
152 break;
153 case 1:
154 rb->snprintf(buf, buf_len, "%s: [%d bytes] ", "plugin buffer", PLUGIN_BUFFER_SIZE);
155 break;
156 case 2:
157 rb->snprintf(buf, buf_len, "%s: [%d bytes] ", "frame_buffer", (int)FRAMEBUFFER_SIZE);
158 break;
159 case 3:
160 rb->snprintf(buf, buf_len, "%s: [W: %d H:%d] ", "LCD", LCD_WIDTH, LCD_HEIGHT);
161 break;
162 case 4:
163 rb->snprintf(buf, buf_len, "%s: [%d bits] ", "fb_data", (int)(sizeof(fb_data) * CHAR_BIT));
164 break;
165 case 5:
166 break;
167 }
168 return buf;
169}
170
171static const char *menu_button_test_name_cb(int selected_item, void* data,
172 char* buf, size_t buf_len)
173{
174 (void)data;
175 int curbtn = BUTTON_NONE;
176 buf[0] = '\0';
177 switch(selected_item)
178 {
179 case 0:
180 rb->snprintf(buf, buf_len, "%s: [%s] ", "Button test",
181 m_test.count > 0 ? "true":"false");
182 break;
183 case 1:
184 if (m_test.count > 0)
185 {
186 if (m_test.count <= 2)
187 curbtn = rb->button_get_w_tmo(HZ * 2);
188 else
189 m_test.last_btn_or_act = BUTTON_NONE;
190 if (curbtn == BUTTON_NONE)
191 {
192 m_test.count--;
193 }
194 else
195 m_test.last_btn_or_act = curbtn;
196 }
197 get_button_names(buf, buf_len, m_test.last_btn_or_act);
198
199 break;
200 }
201 return buf;
202}
203
204static const char *menu_action_test_name_cb(int selected_item, void* data,
205 char* buf, size_t buf_len)
206{
207 (void)data;
208 const char *fmtstr;
209 int curact = ACTION_NONE;
210 buf[0] = '\0';
211 switch(selected_item)
212 {
213 case 0:
214 rb->snprintf(buf, buf_len, "%s: [%s] ", "Action test",
215 m_test.count > 0 ? "true":"false");
216 break;
217 case 1:
218 if (m_test.count <= 0)
219 {
220 if (m_test.context <= 0)
221 fmtstr = "%s > ";
222 else if (m_test.context >= LAST_CONTEXT_PLACEHOLDER - 1)
223 fmtstr = "< %s ";
224 else
225 fmtstr = "< %s > ";
226 }
227 else
228 fmtstr = "%s";
229
230 rb->snprintf(buf, buf_len, fmtstr, context_name(m_test.context));
231 break;
232 case 2:
233 if (m_test.count > 0)
234 {
235 if (m_test.count <= 2)
236 curact = rb->get_action(m_test.context, HZ * 2);
237 else
238 m_test.last_btn_or_act = ACTION_NONE;
239 if (curact == ACTION_NONE && rb->button_get(false) == BUTTON_NONE)
240 {
241 m_test.count--;
242 }
243 else
244 {
245 m_test.last_btn_or_act = curact;
246 m_test.count = 2;
247 }
248 }
249 return action_name(m_test.last_btn_or_act);
250
251 break;
252 }
253 return buf;
254}
255
256static const char* list_get_name_cb(int selected_item, void* data,
257 char* buf, size_t buf_len)
258{
259 buf[0] = '\0';
260 if (data == MENU_ID(M_ROOT))
261 return mainitem(selected_item)->name;
262 else if (selected_item == 0 && data != MENU_ID(M_TESTPUT)) /*header text*/
263 return mainitem(main_last_sel)->name;
264 else if (selected_item >= mainitem(main_last_sel)->items - 1)
265 return ID2P(LANG_BACK);
266
267 if (data == MENU_ID(M_PATHS))
268 {
269 selected_item--;
270 if (selected_item >= 0 && selected_item < mainitem(M_PATHS)->items)
271 {
272 const struct paths *cur = &paths[selected_item];
273 rb->snprintf(buf, buf_len, "%s: [%s] ", cur->name, cur->path);
274 return buf;
275 }
276 }
277 else if (data == MENU_ID(M_BUTTONS))
278 {
279 const struct available_button *btn = &available_buttons[selected_item - 1];
280 rb->snprintf(buf, buf_len, "%s: [0x%X] ", btn->name, (unsigned int) btn->value);
281 return buf;
282 }
283 else if (data == MENU_ID(M_BTNTEST))
284 return menu_button_test_name_cb(selected_item - 1, data, buf, buf_len);
285 else if (data == MENU_ID(M_ACTIONS))
286 return action_name(selected_item - 1);
287 else if (data == MENU_ID(M_CONTEXTS))
288 return context_name(selected_item - 1);
289 else if (data == MENU_ID(M_ACTTEST))
290 return menu_action_test_name_cb(selected_item - 1, data, buf, buf_len);
291 else if (data == MENU_ID(M_BUFFERS))
292 {
293 const struct menu_buffer_t *bufm = &m_buffer[selected_item - 1];
294 rb->snprintf(buf, buf_len, "%s: [%ld bytes] ", bufm->name, (long)bufm->size);
295 return buf;
296 }
297 else if (data == MENU_ID(M_PLUGINS))
298 {
299 return menu_plugin_name_cb(selected_item - 1, data, buf, buf_len);
300 }
301 else if (data == MENU_ID(M_TESTPUT))
302 {
303 rb->snprintf(buf, buf_len, "put_line item: [ %d ]$Text %d$Text LONGER TEST text %d $4$5$6$7$8$9", selected_item, 1, 2);
304 return buf;
305 }
306 return buf;
307}
308
309static int list_voice_cb(int list_index, void* data)
310{
311 if (!rb->global_settings->talk_menu)
312 return -1;
313
314 if (data == MENU_ID(M_ROOT))
315 {
316 const char * name = mainitem(list_index)->name;
317 long id = P2ID((const unsigned char *)name);
318 if(id>=0)
319 rb->talk_id(id, true);
320 else
321 rb->talk_spell(name, true);
322 }
323 else if (data == MENU_ID(M_BUFFERS) || data == MENU_ID(M_PLUGINS))
324 {
325 char buf[64];
326 const char* name = list_get_name_cb(list_index, data, buf, sizeof(buf));
327 long id = P2ID((const unsigned char *)name);
328 if(id>=0)
329 rb->talk_id(id, true);
330 else
331 {
332 char* bytstr = rb->strcasestr(name, "bytes");
333 if (bytstr != NULL)
334 *bytstr = '\0';
335 rb->talk_spell(name, true);
336 }
337 }
338 else if (data == MENU_ID(M_TESTPUT))
339 {
340 char buf[64];
341 const char* name = printcell_get_column_text(printcell_get_column_selected(),
342 buf, sizeof(buf));
343 long id = P2ID((const unsigned char *)name);
344 if(id>=0)
345 rb->talk_id(id, true);
346 else
347 rb->talk_spell(name, true);
348 }
349 else
350 {
351 char buf[64];
352 const char* name = list_get_name_cb(list_index, data, buf, sizeof(buf));
353 long id = P2ID((const unsigned char *)name);
354 if(id>=0)
355 rb->talk_id(id, true);
356 else
357 rb->talk_spell(name, true);
358 }
359 return 0;
360}
361
362int menu_action_cb(int *action, int selected_item, bool* exit, struct gui_synclist *lists)
363{
364 if (lists->data == MENU_ID(M_TESTPUT) && (selected_item < (mainitem(M_TESTPUT)->items) - 1)/*back*/)
365 {
366 if (*action == ACTION_STD_OK)
367 {
368 printcell_increment_column(1, true);
369 *action = ACTION_NONE;
370 }
371 else if (*action == ACTION_STD_CANCEL)
372 {
373 if (printcell_increment_column(-1, true) != testput_cols - 1)
374 {
375 *action = ACTION_NONE;
376 }
377 }
378 else if (*action == ACTION_STD_CONTEXT)
379 {
380 char buf[PRINTCELL_MAXLINELEN];
381 char* bufp = buf;
382 int selcol = printcell_get_column_selected();
383 bufp = printcell_get_column_text(selcol, bufp, PRINTCELL_MAXLINELEN);
384 rb->splashf(HZ * 2, "Item: %s", bufp);
385 }
386 }
387 else if (lists->data == MENU_ID(M_ACTTEST))
388 {
389 if (selected_item == 2) /* context */
390 {
391 int ctx = m_test.context;
392 if (*action == ACTION_STD_OK)
393 m_test.context++;
394 else if (*action == ACTION_STD_CANCEL)
395 m_test.context--;
396
397 if (m_test.context < 0)
398 m_test.context = 0;
399 else if (m_test.context >= LAST_CONTEXT_PLACEHOLDER)
400 m_test.context = LAST_CONTEXT_PLACEHOLDER - 1;
401
402 if (ctx != m_test.context)
403 rb->gui_synclist_speak_item(lists);
404
405 goto default_handler;
406 }
407 if (*action == ACTION_STD_OK)
408 {
409 if (selected_item == 1 || selected_item == 3)
410 {
411 m_test.count = 3;
412 rb->gui_synclist_select_item(lists, 3);
413 }
414 }
415 }
416 else if (lists->data == MENU_ID(M_BTNTEST))
417 {
418 if (*action == ACTION_STD_OK)
419 {
420 if (selected_item == 1 || selected_item == 2)
421 {
422 m_test.count = 3;
423 rb->gui_synclist_select_item(lists, 2);
424 }
425 }
426 }
427/* common */
428 if (*action == ACTION_STD_OK)
429 {
430 if (lists->data == MENU_ID(M_ROOT))
431 {
432 rb->memset(&m_test, 0, sizeof(struct menu_test_t));
433 const struct mainmenu *cur = mainitem(selected_item);
434
435 if (cur->menuid == NULL || cur->menuid == MENU_ID(M_EXIT))
436 *exit = true;
437 else
438 {
439 main_last_sel = selected_item;
440
441 if (cur->menuid == MENU_ID(M_TESTPUT))
442 {
443 synclist_set(cur->menuid, 0, cur->items, 1);
444 printcell_enable(true);
445 //lists->callback_draw_item = test_listdraw_fn;
446 }
447 else
448 {
449 printcell_enable(false);
450 synclist_set(cur->menuid, 1, cur->items, 1);
451 }
452 rb->gui_synclist_draw(lists);
453 }
454 }
455 else if (selected_item <= 0) /* title */
456 {
457 rb->gui_synclist_select_item(lists, 1);
458 }
459 else if (selected_item >= (mainitem(main_last_sel)->items) - 1)/*back*/
460 {
461 *action = ACTION_STD_CANCEL;
462 }
463 else if (lists->data == MENU_ID(M_TESTPUT))
464 {
465
466 }
467 else if (lists->data == MENU_ID(M_ACTIONS) ||
468 lists->data == MENU_ID(M_CONTEXTS))
469 {
470 char buf[MAX_PATH];
471 const char *name = list_get_name_cb(selected_item, lists->data, buf, sizeof(buf));
472 /* splash long enough to get fingers off button then wait for new button press */
473 rb->splashf(HZ / 2, "%s %d (0x%X)", name, selected_item -1, selected_item -1);
474 rb->button_get(true);
475 }
476 }
477 if (*action == ACTION_STD_CANCEL)
478 {
479 if (lists->data == MENU_ID(M_TESTPUT))
480 {
481 //lists->callback_draw_item = NULL;
482 printcell_enable(false);
483 }
484 if (lists->data != MENU_ID(M_ROOT))
485 {
486 const struct mainmenu *mainm = &mainmenu[0];
487 synclist_set(mainm->menuid, main_last_sel, mainm->items, 1);
488 rb->gui_synclist_draw(lists);
489 }
490 else
491 *exit = true;
492 }
493default_handler:
494 if (rb->default_event_handler_ex(*action, cleanup, NULL) == SYS_USB_CONNECTED)
495 {
496 *exit = true;
497 return PLUGIN_USB_CONNECTED;
498 }
499 return PLUGIN_OK;
500}
501
502static void synclist_set(char* menu_id, int selected_item, int items, int sel_size)
503{
504 if (items <= 0)
505 return;
506 if (selected_item < 0)
507 selected_item = 0;
508
509 list_voice_cb(0, menu_id);
510 rb->gui_synclist_init(&lists,list_get_name_cb,
511 menu_id, false, sel_size, NULL);
512 if (menu_id == MENU_ID(M_TESTPUT))
513 {
514 testput_cols = printcell_set_columns(&lists, NULL,
515 TESTPUT_HEADER, Icon_Rockbox);
516 }
517 else
518 {
519 rb->gui_synclist_set_title(&lists, NULL,-1);
520 }
521 rb->gui_synclist_set_voice_callback(&lists, list_voice_cb);
522 rb->gui_synclist_set_nb_items(&lists,items);
523 rb->gui_synclist_select_item(&lists, selected_item);
524
525}
526
527enum plugin_status plugin_start(const void* parameter)
528{
529 int ret = PLUGIN_OK;
530 int selected_item = -1;
531 int action;
532 bool redraw = true;
533 bool exit = false;
534 if (parameter)
535 {
536 //
537 }
538 mainmenu[M_BUTTONS].items = available_button_count;
539 /* add header and back item to each submenu */
540 for (int i = 1; i < M_LAST_ITEM; i++)
541 mainmenu[i].items += 2;
542 mainmenu[M_TESTPUT].items -= 1;
543 if (!exit)
544 {
545 const struct mainmenu *mainm = &mainmenu[0];
546 synclist_set(mainm->menuid, main_last_sel, mainm->items, 1);
547 rb->gui_synclist_draw(&lists);
548
549 while (!exit)
550 {
551 action = rb->get_action(CONTEXT_LIST, HZ / 10);
552 if (m_test.count > 0)
553 action = ACTION_REDRAW;
554
555 if (action == ACTION_NONE)
556 {
557 if (redraw)
558 {
559 action = ACTION_REDRAW;
560 redraw = false;
561 }
562 }
563 else
564 redraw = true;
565 ret = menu_action_cb(&action, selected_item, &exit, &lists);
566 if (rb->gui_synclist_do_button(&lists, &action))
567 continue;
568 selected_item = rb->gui_synclist_get_sel_pos(&lists);
569 }
570 }
571 printcell_enable(false);
572 return ret;
573}