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 *
9 *
10 * Copyright (C) 2016 by Amaury Pouly
11 * 2018 by Marcin Bukat
12 * 2018 by Roman Stolyarov
13 * 2020 by Solomon Peachy
14 *
15 * Based on Rockbox iriver bootloader by Linus Nielsen Feltzing
16 * and the ipodlinux bootloader by Daniel Palffy and Bernard Leach
17 *
18 * This program is free software; you can redistribute it and/or
19 * modify it under the terms of the GNU General Public License
20 * as published by the Free Software Foundation; either version 2
21 * of the License, or (at your option) any later version.
22 *
23 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
24 * KIND, either express or implied.
25 *
26 ****************************************************************************/
27
28#include "system.h"
29#include "lcd.h"
30#include "backlight.h"
31#include "button-target.h"
32#include "button.h"
33#include "../kernel/kernel-internal.h"
34#include "core_alloc.h"
35#include "filesystem-app.h"
36#include "lcd.h"
37#include "font.h"
38#include "power.h"
39#include <string.h>
40#include <stdlib.h>
41#include <unistd.h>
42#include <sys/types.h>
43#include <sys/stat.h>
44#include <fcntl.h>
45#include <dirent.h>
46#include <sys/wait.h>
47#include <stdarg.h>
48#include "version.h"
49
50/* Basic configuration */
51#if defined(AGPTEK_ROCKER)
52#define ICON_WIDTH 70
53#define ICON_HEIGHT 70
54#define ICON_NAME bm_hibyicon
55#define OF_NAME "HIBY PLAYER"
56#include "bitmaps/hibyicon.h"
57#elif defined(XDUOO_X3II)
58#define ICON_WIDTH 130
59#define ICON_HEIGHT 130
60#define ICON_NAME bm_hibyicon
61#define OF_NAME "HIBY PLAYER"
62#define BUTTON_UP BUTTON_OPTION
63#define BUTTON_DOWN BUTTON_HOME
64#define BUTTON_SELECT BUTTON_PLAY
65#include "bitmaps/hibyicon.h"
66#elif defined(XDUOO_X20)
67#define ICON_WIDTH 130
68#define ICON_HEIGHT 130
69#define ICON_NAME bm_hibyicon
70#define OF_NAME "HIBY PLAYER"
71#define BUTTON_UP BUTTON_OPTION
72#define BUTTON_DOWN BUTTON_HOME
73#define BUTTON_SELECT BUTTON_PLAY
74#include "bitmaps/hibyicon.h"
75#elif defined(FIIO_M3K_LINUX)
76#define ICON_WIDTH 130
77#define ICON_HEIGHT 130
78#define ICON_NAME bm_fiioicon
79#define BUTTON_LEFT BUTTON_PREV
80#define BUTTON_RIGHT BUTTON_NEXT
81#define BUTTON_SELECT BUTTON_PLAY
82#define OF_NAME "FIIO PLAYER"
83#include "bitmaps/fiioicon.h"
84#elif defined(EROS_Q)
85#define ICON_WIDTH 130
86#define ICON_HEIGHT 130
87#define ICON_NAME bm_hibyicon
88#define OF_NAME "HIBY PLAYER"
89#define BUTTON_UP BUTTON_SCROLL_BACK
90#define BUTTON_DOWN BUTTON_SCROLL_FWD
91#define BUTTON_SELECT BUTTON_PLAY
92#include "bitmaps/hibyicon.h"
93#elif defined(SURFANS_F28)
94#define ICON_WIDTH 130
95#define ICON_HEIGHT 130
96#define ICON_NAME bm_hibyicon
97#define OF_NAME "HIBY PLAYER"
98#define BUTTON_UP BUTTON_PREV
99#define BUTTON_DOWN BUTTON_NEXT
100#define BUTTON_SELECT BUTTON_PLAY
101#define BUTTON_LEFT BUTTON_SCROLL_BACK
102#define BUTTON_RIGHT BUTTON_SCROLL_FWD
103#include "bitmaps/hibyicon.h"
104#else
105#error "must define ICON_WIDTH/HEIGHT"
106#endif
107
108#define BASE_DIR PIVOT_ROOT
109//#ifdef FIIO_M3K_LINUX
110//#define BASE_DIR "/mnt"
111//#else
112//#define BASE_DIR "/mnt/sd_0"
113//#endif
114
115/* images */
116#include "bitmaps/rockboxicon.h"
117#include "bitmaps/toolsicon.h"
118
119/* don't issue an error when parsing the file for dependencies */
120#if defined(BMPWIDTH_rockboxicon) && (BMPWIDTH_rockboxicon != ICON_WIDTH || \
121 BMPHEIGHT_rockboxicon != ICON_HEIGHT)
122#error rockboxicon has the wrong resolution
123#endif
124#if defined(BMPWIDTH_hibyicon) && (BMPWIDTH_hibyicon != ICON_WIDTH || \
125 BMPHEIGHT_hibyicon != ICON_HEIGHT)
126#error hibyicon has the wrong resolution
127#endif
128#if defined(BMPWIDTH_fiioicon) && (BMPWIDTH_fiioicon != ICON_WIDTH || \
129 BMPHEIGHT_fiioicon != ICON_HEIGHT)
130#error fiioicon has the wrong resolution
131#endif
132#if defined(BMPWIDTH_toolsicon) && (BMPWIDTH_toolsicon != ICON_WIDTH || \
133 BMPHEIGHT_toolsicon != ICON_HEIGHT)
134#error toolsicon has the wrong resolution
135#endif
136
137/* If we started ADB, don't immediately boot into USB mode if we plug in. */
138static int adb_running = 0;
139
140/* return icon y position (x is always centered) */
141static int get_icon_y(void)
142{
143 int h;
144 lcd_getstringsize("X", NULL, &h);
145 return ((LCD_HEIGHT - ICON_HEIGHT)/2) - h;
146}
147
148/* Important Note: this bootloader is carefully written so that in case of
149 * error, the OF is run. This seems like the safest option since the OF is
150 * always there and might do magic things. */
151
152enum boot_mode
153{
154 BOOT_ROCKBOX,
155 BOOT_TOOLS,
156 BOOT_OF,
157 BOOT_COUNT,
158 BOOT_STOP, /* power down/suspend */
159 BOOT_CANARY,
160};
161
162static void display_text_center(int y, const char *text)
163{
164 int width;
165 lcd_getstringsize(text, &width, NULL);
166 lcd_putsxy(LCD_WIDTH / 2 - width / 2, y, text);
167}
168
169static void display_text_centerf(int y, const char *format, ...)
170{
171 char buf[1024];
172 va_list ap;
173 va_start(ap, format);
174
175 vsnprintf(buf, sizeof(buf), format, ap);
176 display_text_center(y, buf);
177}
178
179/* get timeout before taking action if the user doesn't touch the device */
180static int get_inactivity_tmo(int same_as_last)
181{
182#if defined(HAS_BUTTON_HOLD)
183 if(button_hold())
184 return 5 * HZ; /* Inactivity timeout when on hold */
185 else
186#endif
187 if (same_as_last)
188 return 1 * HZ; /* Timeout when mode is the same as the previous mode */
189 else
190 return 10 * HZ; /* Default timeout */
191}
192
193/* return action on idle timeout */
194static enum boot_mode inactivity_action(enum boot_mode cur_selection)
195{
196#if defined(HAS_BUTTON_HOLD)
197 if(button_hold())
198 return BOOT_STOP; /* power down/suspend */
199 else
200#endif
201 return cur_selection; /* return last choice */
202}
203
204static int mounted = 0;
205
206static void mount_storage(int enable)
207{
208 if (enable && !mounted) {
209 system("/bin/mkdir -p " BASE_DIR);
210 if (system("/bin/mount /dev/mmcblk0 " BASE_DIR))
211 system("/bin/mount /dev/mmcblk0p1 " BASE_DIR);
212 // XXX possibly invoke sys_serv -> "MOUNT:MOUNT:%s %s", blkdev, mntpoint
213 } else if (!enable && mounted) {
214 system("/bin/unmount " BASE_DIR);
215 // XXX possibly invoke sys_serv -> "MOUNT:UNMOUNT:%s %s", mntpoint
216 }
217 mounted = enable;
218}
219
220/* we store the boot mode in a file so we can reload it between 'boots' */
221static enum boot_mode load_boot_mode(enum boot_mode mode)
222{
223 mount_storage(true);
224 int fd = open(BASE_DIR "/.rockbox/rb_bl_mode.txt", O_RDONLY);
225 if(fd >= 0)
226 {
227 read(fd, &mode, sizeof(mode));
228 close(fd);
229 }
230 return mode;
231}
232
233static void save_boot_mode(enum boot_mode mode)
234{
235 mount_storage(true);
236 int fd = open(BASE_DIR "/.rockbox/rb_bl_mode.txt", O_RDWR | O_CREAT | O_TRUNC);
237 if(fd >= 0)
238 {
239 write(fd, &mode, sizeof(mode));
240 close(fd);
241 }
242}
243
244static enum boot_mode get_boot_mode(void)
245{
246 /* load previous mode, or start with rockbox if none */
247 enum boot_mode init_mode = load_boot_mode(BOOT_CANARY);
248 /* wait for user action */
249 enum boot_mode mode = (init_mode == BOOT_CANARY) ? BOOT_ROCKBOX : init_mode;
250 int last_activity = current_tick;
251#if defined(HAS_BUTTON_HOLD)
252 bool hold_status = button_hold();
253#endif
254 while(true)
255 {
256 /* on usb detect, immediately boot with last choice */
257 if(!adb_running && power_input_status() & POWER_INPUT_USB_CHARGER)
258 {
259 /* save last choice */
260 save_boot_mode(mode);
261 return mode;
262 }
263 /* inactivity detection */
264 int timeout = last_activity + get_inactivity_tmo(init_mode == mode);
265 if(TIME_AFTER(current_tick, timeout))
266 {
267 /* save last choice */
268 save_boot_mode(mode);
269 return inactivity_action(mode);
270 }
271 /* redraw */
272 lcd_clear_display();
273 /* display top text */
274#if defined(HAS_BUTTON_HOLD)
275 if(button_hold())
276 {
277 lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
278 display_text_center(0, "ON HOLD!");
279 }
280 else
281#endif
282 {
283 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
284 display_text_center(0, "SELECT PLAYER");
285 }
286 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
287 /* display icon */
288 const struct bitmap *icon = (mode == BOOT_OF) ? &ICON_NAME :
289 (mode == BOOT_ROCKBOX) ? &bm_rockboxicon : &bm_toolsicon;
290 lcd_bmp(icon, (LCD_WIDTH - ICON_WIDTH) / 2, get_icon_y());
291 /* display bottom description */
292 const char *desc = (mode == BOOT_OF) ? OF_NAME :
293 (mode == BOOT_ROCKBOX) ? "ROCKBOX" : "TOOLS";
294
295 int desc_height;
296 lcd_getstringsize(desc, NULL, &desc_height);
297 display_text_center(LCD_HEIGHT - 3*desc_height, desc);
298
299 /* display arrows */
300 int arrow_width, arrow_height;
301 lcd_getstringsize("<", &arrow_width, &arrow_height);
302 int arrow_y = get_icon_y() + ICON_HEIGHT / 2 - arrow_height / 2;
303 lcd_putsxy(arrow_width / 2, arrow_y, "<");
304 lcd_putsxy(LCD_WIDTH - 3 * arrow_width / 2, arrow_y, ">");
305
306 lcd_set_foreground(LCD_RGBPACK(0, 255, 0));
307 display_text_centerf(LCD_HEIGHT - arrow_height * 3 / 2, "timeout in %d sec",
308 (timeout - current_tick + HZ - 1) / HZ);
309
310 lcd_update();
311
312 /* wait for a key */
313 int btn = button_get_w_tmo(HZ / 10);
314
315#if defined(HAS_BUTTON_HOLD)
316 /* record action, changing HOLD counts as action */
317 if(btn & BUTTON_MAIN || hold_status != button_hold())
318 last_activity = current_tick;
319
320 hold_status = button_hold();
321#else
322 if(btn & BUTTON_MAIN)
323 last_activity = current_tick;
324#endif
325 /* ignore release, allow repeat */
326 if(btn & BUTTON_REL)
327 continue;
328 if(btn & BUTTON_REPEAT)
329 btn &= ~BUTTON_REPEAT;
330 /* play -> stop loop and return mode */
331 if(btn == BUTTON_SELECT)
332 break;
333 /* left/right/up/down: change mode */
334 if(btn == BUTTON_LEFT || btn == BUTTON_DOWN) {
335 mode = (mode + BOOT_COUNT - 1) % BOOT_COUNT;
336 init_mode = BOOT_CANARY;
337 }
338 if(btn == BUTTON_RIGHT || btn == BUTTON_UP) {
339 mode = (mode + 1) % BOOT_COUNT;
340 init_mode = BOOT_CANARY;
341 }
342 }
343
344 /* save mode */
345 save_boot_mode(mode);
346 return mode;
347}
348
349void error_screen(const char *msg)
350{
351 lcd_clear_display();
352 lcd_putsf(0, 0, msg);
353 lcd_update();
354}
355
356int choice_screen(const char *title, bool center, int nr_choices, const char *choices[], int nr_extra, const char *extra[])
357{
358 int choice = 0;
359 int max_len = 0;
360 int h;
361 lcd_getstringsize("x", NULL, &h);
362 for(int i = 0; i < nr_choices; i++)
363 {
364 int len = strlen(choices[i]);
365 if(len > max_len)
366 max_len = len;
367 }
368 char *buf = malloc(max_len + 10);
369 int top_y = 2 * h;
370 int nr_lines = (LCD_HEIGHT - top_y) / h;
371 while(true)
372 {
373 /* make sure choice is visible */
374 int offset = choice - nr_lines / 2;
375 if(offset < 0)
376 offset = 0;
377 lcd_clear_display();
378 /* display top text */
379 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
380 display_text_center(0, title);
381 int line = 0;
382 for(int i = 0; i < nr_choices && line < nr_lines; i++)
383 {
384 if(i < offset)
385 continue;
386 if(i == choice)
387 lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
388 else
389 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
390 sprintf(buf, "%s", choices[i]);
391 if(center)
392 display_text_center(top_y + h * line, buf);
393 else
394 lcd_putsxy(0, top_y + h * line, buf);
395 line++;
396 }
397
398 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
399 line++;
400 for (int i = 0 ; i < nr_extra && line < nr_lines ; i++) {
401 sprintf(buf, "%s", extra[i]);
402 display_text_center(top_y + h * line, buf);
403 line++;
404 }
405
406 lcd_update();
407
408 /* wait for a key */
409 int btn = button_get_w_tmo(HZ / 10);
410 /* ignore release, allow repeat */
411 if(btn & BUTTON_REL)
412 continue;
413 if(btn & BUTTON_REPEAT)
414 btn &= ~BUTTON_REPEAT;
415 /* play -> stop loop and return mode */
416 if (btn == BUTTON_SELECT)
417 {
418 free(buf);
419 return btn == BUTTON_SELECT ? choice : -1;
420 }
421 /* left/right/up/down: change mode */
422 if (btn == BUTTON_UP || btn == BUTTON_LEFT)
423 choice = (choice + nr_choices - 1) % nr_choices;
424 if(btn == BUTTON_DOWN || btn == BUTTON_RIGHT)
425 choice = (choice + 1) % nr_choices;
426 }
427}
428
429void run_file(const char *name)
430{
431 char *dirname = BASE_DIR;
432 char *buf = malloc(strlen(dirname) + strlen(name) + 1);
433 sprintf(buf, "%s%s", dirname, name);
434
435 lcd_clear_display();
436 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
437 lcd_putsf(0, 0, "Running %s", name);
438 lcd_update();
439
440 pid_t pid = fork();
441 if(pid == 0)
442 {
443 execlp("/bin/sh", "sh", buf, NULL);
444 _exit(42);
445 }
446 int status;
447 waitpid(pid, &status, 0);
448 if(WIFEXITED(status))
449 {
450 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
451 lcd_putsf(0, 1, "program returned %d", WEXITSTATUS(status));
452 }
453 else
454 {
455 lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
456 lcd_putsf(0, 1, "an error occured: %x", status);
457 }
458 lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
459 lcd_putsf(0, 3, "Press any key or wait");
460 lcd_update();
461 /* wait a small time */
462 sleep(HZ);
463 /* ignore event */
464 while(button_get(false) != 0) {}
465 /* wait for any key or timeout */
466 button_get_w_tmo(4 * HZ);
467}
468
469void run_script_menu(void)
470{
471 const char **entries = NULL;
472 int nr_entries = 0;
473 DIR *dir = opendir(BASE_DIR);
474 struct dirent *ent;
475 while((ent = readdir(dir)))
476 {
477 if(ent->d_type != DT_REG)
478 continue;
479 entries = realloc(entries, (nr_entries + 1) * sizeof(const char *));
480 entries[nr_entries++] = strdup(ent->d_name);
481 }
482 closedir(dir);
483 int idx = choice_screen("RUN SCRIPT", false, nr_entries, entries, 0, NULL);
484 if(idx >= 0)
485 run_file(entries[idx]);
486 for(int i = 0; i < nr_entries; i++)
487 free((char *)entries[i]);
488 free(entries);
489}
490
491static void adb(int start)
492{
493#if defined(FIIO_M3K_LINUX)
494 lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
495 lcd_putsf(0, 1, "ADB not supported!");
496 sleep(2*HZ);
497 (void)start;
498#else
499 pid_t pid = fork();
500 if(pid == 0)
501 {
502 execlp("/etc/init.d/K90adb", "K90adb", start ? "start" : "stop", NULL);
503 _exit(42);
504 }
505 int status;
506 waitpid(pid, &status, 0);
507 adb_running = start;
508#if 0
509 if(WIFEXITED(status))
510 {
511 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
512 lcd_putsf(0, 1, "program returned %d", WEXITSTATUS(status));
513 }
514 else
515 {
516 lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
517 lcd_putsf(0, 1, "an error occured: %x", status);
518 }
519#endif
520#endif
521}
522
523static void tools_screen(void)
524{
525 const char *extra[] = { MODEL_NAME, rbversion };
526 printf("Version: %s\n", rbversion);
527 printf("%s\n", MODEL_NAME);
528 const char *choices[] = {"ADB start", "ADB stop", "Run script", "Remount SD", "Restart", "Shutdown", "Recovery", "Back"};
529 int choice = choice_screen("TOOLS MENU", true, 8, choices, 2, extra);
530 if(choice == 0)
531 {
532 /* run service menu */
533 printf("Starting ADB service...\n");
534 fflush(stdout);
535 adb(1);
536 }
537 else if(choice == 1)
538 {
539 printf("Stopping ADB service...\n");
540 fflush(stdout);
541 adb(0);
542 }
543 else if(choice == 2)
544 {
545 run_script_menu();
546 }
547 else if(choice == 3)
548 {
549 mount_storage(false);
550 mount_storage(true);
551 }
552 else if(choice == 4)
553 {
554 system_reboot();
555 }
556 else if(choice == 5)
557 {
558 power_off();
559 }
560 else if(choice == 6)
561 {
562 int fd = open("/proc/jz/reset/reset", O_WRONLY);
563 if (fd >= 0) {
564 const char *buf = "recovery\n";
565 write(fd, buf, strlen(buf));
566 close(fd);
567 }
568 }
569 else if (choice == 7)
570 {
571 return;
572 }
573}
574
575#if 0
576/* open log file */
577static int open_log(void)
578{
579 /* open regular log file */
580 int fd = open(BASE_DIR "/rockbox.log", O_RDWR | O_CREAT | O_APPEND);
581 /* get its size */
582 struct stat stat;
583 if(fstat(fd, &stat) != 0)
584 return fd; /* on error, don't do anything */
585 /* if file is too large, rename it and start a new log file */
586 if(stat.st_size < 1000000)
587 return fd;
588 close(fd);
589 /* move file */
590 rename(BASE_DIR "/rockbox.log", BASE_DIR "/rockbox.log.1");
591 /* re-open the file, truncate in case the move was unsuccessful */
592 return open(BASE_DIR "/rockbox.log", O_RDWR | O_CREAT | O_APPEND | O_TRUNC);
593}
594#endif
595
596int main(int argc, char **argv)
597{
598 (void) argc;
599 (void) argv;
600#if 0
601 /* redirect stdout and stderr to have error messages logged somewhere on the
602 * user partition */
603 int fd = open_log();
604 if(fd >= 0)
605 {
606 dup2(fd, fileno(stdout));
607 dup2(fd, fileno(stderr));
608 close(fd);
609 }
610 /* print version */
611 printf("Rockbox boot loader\n");
612 printf("Version: %s\n", rbversion);
613 printf("%s\n", MODEL_NAME);
614#endif
615
616 system_init();
617 core_allocator_init();
618 kernel_init();
619 paths_init();
620 lcd_init();
621 font_init();
622 button_init();
623 backlight_init();
624 backlight_set_brightness(DEFAULT_BRIGHTNESS_SETTING);
625// /* try to load the extra font we install on the device */
626// int font_id = font_load("/usr/local/share/rockbox/bootloader.fnt");
627// if(font_id >= 0)
628// lcd_setfont(font_id);
629
630 mount_storage(true);
631
632 /* run all tools menu */
633 while(true)
634 {
635 enum boot_mode mode = get_boot_mode();
636 if (mode == BOOT_OF)
637 {
638#if 0
639 fflush(stdout);
640 fflush(stderr);
641 close(fileno(stdout));
642 close(fileno(stderr));
643#endif
644 mount_storage(false);
645 /* boot OF */
646#if defined(FIIO_M3K_LINUX)
647 execvp("/usr/project/bin/player_daemon", argv);
648#else
649 execvp("/usr/bin/hiby_player", argv);
650#endif
651 error_screen("Cannot boot OF");
652 sleep(5 * HZ);
653 }
654 else if(mode == BOOT_TOOLS)
655 {
656 tools_screen();
657 }
658 else if(mode == BOOT_ROCKBOX)
659 {
660 fflush(stdout);
661 mount_storage(true);
662 system("/bin/cp " BASE_DIR "/.rockbox/" BOOTFILE " /tmp");
663 system("/bin/chmod +x /tmp/" BOOTFILE);
664 execl("/tmp/" BOOTFILE, BOOTFILE, NULL);
665 printf("execvp failed: %s\n", strerror(errno));
666 error_screen("Cannot boot Rockbox!");
667 mode = BOOT_TOOLS;
668 sleep(2 * HZ);
669 }
670 else
671 {
672 printf("suspend\n");
673// nwz_power_suspend();
674 }
675 }
676 /* if we reach this point, everything failed, so return an error so that
677 * sysmgrd knows something is wrong */
678 return 1;
679}