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