A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 538 lines 16 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2016 by Amaury Pouly 11 * 12 * Based on Rockbox iriver bootloader by Linus Nielsen Feltzing 13 * and the ipodlinux bootloader by Daniel Palffy and Bernard Leach 14 * 15 * This program is free software; you can redistribute it and/or 16 * modify it under the terms of the GNU General Public License 17 * as published by the Free Software Foundation; either version 2 18 * of the License, or (at your option) any later version. 19 * 20 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 * KIND, either express or implied. 22 * 23 ****************************************************************************/ 24 25#include "system.h" 26#include "lcd.h" 27#include "backlight.h" 28#include "button-target.h" 29#include "button.h" 30#include "../kernel/kernel-internal.h" 31#include "core_alloc.h" 32#include "filesystem-app.h" 33#include "nvp-nwz.h" 34#include "power-nwz.h" 35#include "lcd.h" 36#include "font.h" 37#include "power.h" 38#include <string.h> 39#include <stdlib.h> 40#include <unistd.h> 41#include <sys/types.h> 42#include <sys/stat.h> 43#include <fcntl.h> 44#include <dirent.h> 45#include <sys/wait.h> 46#include <stdarg.h> 47#include "version.h" 48 49/* all images must have the following size */ 50#define ICON_WIDTH 130 51#define ICON_HEIGHT 130 52 53/* images */ 54#include "bitmaps/rockboxicon.h" 55#include "bitmaps/toolsicon.h" 56 57/* don't issue an error when parsing the file for dependencies */ 58#if defined(BMPWIDTH_rockboxicon) && (BMPWIDTH_rockboxicon != ICON_WIDTH || \ 59 BMPHEIGHT_rockboxicon != ICON_HEIGHT) 60#error rockboxicon has the wrong resolution 61#endif 62#if defined(BMPWIDTH_toolsicon) && (BMPWIDTH_toolsicon != ICON_WIDTH || \ 63 BMPHEIGHT_toolsicon != ICON_HEIGHT) 64#error toolsicon has the wrong resolution 65#endif 66 67/* the A860 does not have left/right/up/down but it has rew/ff so pretend we 68 * always have rew/ff */ 69#ifndef BUTTON_REW 70#define BUTTON_REW BUTTON_LEFT 71#endif 72#ifndef BUTTON_FF 73#define BUTTON_FF BUTTON_RIGHT 74#endif 75 76/* buffer for Sony image, filled from NVP */ 77unsigned short sonyicon[ICON_WIDTH * ICON_HEIGHT]; 78const struct bitmap bm_sonyicon = 79{ 80 .width = ICON_WIDTH, 81 .height = ICON_HEIGHT, 82 .format = FORMAT_NATIVE, 83 .data = (unsigned char*)sonyicon 84}; 85 86/* return icon y position (x is always centered) */ 87int get_icon_y(void) 88{ 89 /* adjust so that this contains the Sony logo and produces a nice logo 90 * when used with rockbox */ 91 if(LCD_HEIGHT == 320) 92 return 70; 93 else if(LCD_HEIGHT == 400) 94 return 100; 95 else 96 return LCD_HEIGHT / 2 - ICON_HEIGHT + 30; /* guess, probably won't work */ 97} 98 99/* Sony logo extraction */ 100bool extract_sony_logo(void) 101{ 102 /* load the entire image from the nvp */ 103 int bti_size = nwz_nvp_read(NWZ_NVP_BTI, NULL); 104 if(bti_size < 0) 105 return false; 106 unsigned short *bti = malloc(bti_size); 107 if(nwz_nvp_read(NWZ_NVP_BTI, bti) != bti_size) 108 return false; 109 /* compute the offset in the image of the logo itself */ 110 int x_off = (LCD_WIDTH - ICON_WIDTH) / 2; /* logo is centered horizontally */ 111 int y_off = get_icon_y(); 112 /* extract part of the image */ 113 for(int y = 0; y < ICON_HEIGHT; y++) 114 { 115 memcpy(sonyicon + ICON_WIDTH * y, 116 bti + LCD_WIDTH * (y + y_off) + x_off, ICON_WIDTH * sizeof(unsigned short)); 117 } 118 free(bti); 119 return true; 120} 121 122/* Important Note: this bootloader is carefully written so that in case of 123 * error, the OF is run. This seems like the safest option since the OF is 124 * always there and might do magic things. */ 125 126enum boot_mode 127{ 128 BOOT_ROCKBOX, 129 BOOT_TOOLS, 130 BOOT_OF, 131 BOOT_COUNT, 132 BOOT_USB, /* special */ 133 BOOT_STOP, /* power down/suspend */ 134}; 135 136static void display_text_center(int y, const char *text) 137{ 138 int width; 139 lcd_getstringsize(text, &width, NULL); 140 lcd_putsxy(LCD_WIDTH / 2 - width / 2, y, text); 141} 142 143static void display_text_centerf(int y, const char *format, ...) 144{ 145 char buf[1024]; 146 va_list ap; 147 va_start(ap, format); 148 149 vsnprintf(buf, sizeof(buf), format, ap); 150 display_text_center(y, buf); 151} 152 153/* get timeout before taking action if the user doesn't touch the device */ 154int get_inactivity_tmo(void) 155{ 156 if(button_hold()) 157 return 5 * HZ; /* Inactivity timeout when on hold */ 158 else 159 return 10 * HZ; /* Inactivity timeout when not on hold */ 160} 161 162/* return action on idle timeout */ 163enum boot_mode inactivity_action(enum boot_mode cur_selection) 164{ 165 if(button_hold()) 166 return BOOT_STOP; /* power down/suspend */ 167 else 168 return cur_selection; /* return last choice */ 169} 170 171/* we store the boot mode in a file in /tmp so we can reload it between 'boots' 172 * (since the mostly suspends instead of powering down) */ 173enum boot_mode load_boot_mode(enum boot_mode mode) 174{ 175 int fd = open("/tmp/rb_bl_mode.txt", O_RDONLY); 176 if(fd >= 0) 177 { 178 read(fd, &mode, sizeof(mode)); 179 close(fd); 180 } 181 return mode; 182} 183 184void save_boot_mode(enum boot_mode mode) 185{ 186 int fd = open("/tmp/rb_bl_mode.txt", O_RDWR | O_CREAT | O_TRUNC); 187 if(fd >= 0) 188 { 189 write(fd, &mode, sizeof(mode)); 190 close(fd); 191 } 192} 193 194enum boot_mode get_boot_mode(void) 195{ 196 /* load previous mode, or start with rockbox if none */ 197 enum boot_mode init_mode = load_boot_mode(BOOT_ROCKBOX); 198 /* wait for user action */ 199 enum boot_mode mode = init_mode; 200 int last_activity = current_tick; 201 bool hold_status = button_hold(); 202 while(true) 203 { 204 /* on usb detect, return to usb 205 * FIXME this is a hack, we need proper usb detection */ 206 if(power_input_status() & POWER_INPUT_USB_CHARGER) 207 { 208 /* save last choice */ 209 save_boot_mode(mode); 210 return BOOT_USB; 211 } 212 /* inactivity detection */ 213 int timeout = last_activity + get_inactivity_tmo(); 214 if(TIME_AFTER(current_tick, timeout)) 215 { 216 /* save last choice */ 217 save_boot_mode(mode); 218 return inactivity_action(mode); 219 } 220 /* redraw */ 221 lcd_clear_display(); 222 /* display top text */ 223 if(button_hold()) 224 { 225 lcd_set_foreground(LCD_RGBPACK(255, 0, 0)); 226 display_text_center(0, "ON HOLD!"); 227 } 228 else 229 { 230 lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); 231 display_text_center(0, "SELECT PLAYER"); 232 } 233 lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); 234 /* display icon */ 235 const struct bitmap *icon = (mode == BOOT_OF) ? &bm_sonyicon : 236 (mode == BOOT_ROCKBOX) ? &bm_rockboxicon : &bm_toolsicon; 237 lcd_bmp(icon, (LCD_WIDTH - ICON_WIDTH) / 2, get_icon_y()); 238 /* display bottom description */ 239 const char *desc = (mode == BOOT_OF) ? "SONY" : 240 (mode == BOOT_ROCKBOX) ? "ROCKBOX" : "TOOLS"; 241 display_text_center(get_icon_y() + ICON_HEIGHT + 30, desc); 242 /* display arrows */ 243 int arrow_width, arrow_height; 244 lcd_getstringsize("<", &arrow_width, &arrow_height); 245 int arrow_y = get_icon_y() + ICON_HEIGHT / 2 - arrow_height / 2; 246 lcd_putsxy(arrow_width / 2, arrow_y, "<"); 247 lcd_putsxy(LCD_WIDTH - 3 * arrow_width / 2, arrow_y, ">"); 248 249 lcd_set_foreground(LCD_RGBPACK(0, 255, 0)); 250 display_text_centerf(LCD_HEIGHT - arrow_height * 3 / 2, "timeout in %d sec", 251 (timeout - current_tick + HZ - 1) / HZ); 252 253 lcd_update(); 254 255 /* wait for a key */ 256 int btn = button_get_w_tmo(HZ / 10); 257 /* record action, changing HOLD counts as action */ 258 if(btn & BUTTON_MAIN || hold_status != button_hold()) 259 last_activity = current_tick; 260 hold_status = button_hold(); 261 /* ignore release, allow repeat */ 262 if(btn & BUTTON_REL) 263 continue; 264 if(btn & BUTTON_REPEAT) 265 btn &= ~BUTTON_REPEAT; 266 /* play -> stop loop and return mode */ 267 if(btn == BUTTON_PLAY) 268 break; 269 /* left/right/up/down: change mode */ 270 if(btn == BUTTON_LEFT || btn == BUTTON_DOWN || btn == BUTTON_REW) 271 mode = (mode + BOOT_COUNT - 1) % BOOT_COUNT; 272 if(btn == BUTTON_RIGHT || btn == BUTTON_UP || btn == BUTTON_FF) 273 mode = (mode + 1) % BOOT_COUNT; 274 } 275 276 /* save mode */ 277 save_boot_mode(mode); 278 return mode; 279} 280 281void error_screen(const char *msg) 282{ 283 lcd_clear_display(); 284 lcd_putsf(0, 0, msg); 285 lcd_update(); 286} 287 288void create_sony_logo(void) 289{ 290 for(int y = 0; y < ICON_HEIGHT; y++) 291 for(int x = 0; x < ICON_WIDTH; x++) 292 sonyicon[y * ICON_WIDTH + x] = 0xf81f; 293} 294 295int choice_screen(const char *title, bool center, int nr_choices, const char *choices[]) 296{ 297 int choice = 0; 298 int max_len = 0; 299 int h; 300 lcd_getstringsize("x", NULL, &h); 301 for(int i = 0; i < nr_choices; i++) 302 { 303 int len = strlen(choices[i]); 304 if(len > max_len) 305 max_len = len; 306 } 307 char *buf = malloc(max_len + 10); 308 int top_y = 2 * h; 309 int nr_lines = (LCD_HEIGHT - top_y) / h; 310 while(true) 311 { 312 /* make sure choice is visible */ 313 int offset = choice - nr_lines / 2; 314 if(offset < 0) 315 offset = 0; 316 lcd_clear_display(); 317 /* display top text */ 318 lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); 319 display_text_center(0, title); 320 int line = 0; 321 for(int i = 0; i < nr_choices && line < nr_lines; i++) 322 { 323 if(i < offset) 324 continue; 325 if(i == choice) 326 lcd_set_foreground(LCD_RGBPACK(255, 0, 0)); 327 else 328 lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); 329 sprintf(buf, "%s", choices[i]); 330 if(center) 331 display_text_center(top_y + h * line, buf); 332 else 333 lcd_putsxy(0, top_y + h * line, buf); 334 line++; 335 } 336 337 lcd_update(); 338 339 /* wait for a key */ 340 int btn = button_get_w_tmo(HZ / 10); 341 /* ignore release, allow repeat */ 342 if(btn & BUTTON_REL) 343 continue; 344 if(btn & BUTTON_REPEAT) 345 btn &= ~BUTTON_REPEAT; 346 /* play -> stop loop and return mode */ 347 if(btn == BUTTON_PLAY || btn == BUTTON_BACK) 348 { 349 free(buf); 350 return btn == BUTTON_PLAY ? choice : -1; 351 } 352 /* left/right/up/down: change mode */ 353 if(btn == BUTTON_LEFT || btn == BUTTON_UP || btn == BUTTON_REW) 354 choice = (choice + nr_choices - 1) % nr_choices; 355 if(btn == BUTTON_RIGHT || btn == BUTTON_DOWN || btn == BUTTON_FF) 356 choice = (choice + 1) % nr_choices; 357 } 358} 359 360void run_file(const char *name) 361{ 362 char *dirname = "/contents/"; 363 char *buf = malloc(strlen(dirname) + strlen(name) + 1); 364 sprintf(buf, "%s%s", dirname, name); 365 366 lcd_clear_display(); 367 lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); 368 lcd_putsf(0, 0, "Running %s", name); 369 lcd_update(); 370 371 pid_t pid = fork(); 372 if(pid == 0) 373 { 374 execlp("sh", "sh", buf, NULL); 375 _exit(42); 376 } 377 int status; 378 waitpid(pid, &status, 0); 379 if(WIFEXITED(status)) 380 { 381 lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); 382 lcd_putsf(0, 1, "program returned %d", WEXITSTATUS(status)); 383 } 384 else 385 { 386 lcd_set_foreground(LCD_RGBPACK(255, 0, 0)); 387 lcd_putsf(0, 1, "an error occured: %x", status); 388 } 389 lcd_set_foreground(LCD_RGBPACK(255, 0, 0)); 390 lcd_putsf(0, 3, "Press any key or wait"); 391 lcd_update(); 392 /* wait a small time */ 393 sleep(HZ); 394 /* ignore event */ 395 while(button_get(false) != 0) {} 396 /* wait for any key or timeout */ 397 button_get_w_tmo(4 * HZ); 398} 399 400void run_script_menu(void) 401{ 402 const char **entries = NULL; 403 int nr_entries = 0; 404 DIR *dir = opendir("/contents/"); 405 struct dirent *ent; 406 while((ent = readdir(dir))) 407 { 408 if(ent->d_type != DT_REG) 409 continue; 410 entries = realloc(entries, (nr_entries + 1) * sizeof(const char *)); 411 entries[nr_entries++] = strdup(ent->d_name); 412 } 413 closedir(dir); 414 int idx = choice_screen("RUN SCRIPT", false, nr_entries, entries); 415 if(idx >= 0) 416 run_file(entries[idx]); 417 for(int i = 0; i < nr_entries; i++) 418 free((char *)entries[i]); 419 free(entries); 420} 421 422void tools_screen(void) 423{ 424 const char *choices[] = {"Service menu", "Run script", "Restart", "Shutdown"}; 425 int choice = choice_screen("TOOLS MENU", true, 4, choices); 426 if(choice == 0) 427 { 428 /* run service menu */ 429 fflush(stdout); 430 execl("/usr/local/bin/mptapp", "mptapp", NULL); 431 error_screen("Cannot boot service menu"); 432 sleep(5 * HZ); 433 } 434 else if(choice == 1) 435 run_script_menu(); 436 else if(choice == 2) 437 nwz_power_restart(); 438 else if(choice == 3) 439 nwz_power_shutdown(); 440} 441 442/* open log file */ 443int open_log(void) 444{ 445 /* open regular log file */ 446 int fd = open("/contents/rockbox.log", O_RDWR | O_CREAT | O_APPEND); 447 /* get its size */ 448 struct stat stat; 449 if(fstat(fd, &stat) != 0) 450 return fd; /* on error, don't do anything */ 451 /* if file is too large, rename it and start a new log file */ 452 if(stat.st_size < 1000000) 453 return fd; 454 close(fd); 455 /* move file */ 456 rename("/contents/rockbox.log", "/contents/rockbox.log.1"); 457 /* re-open the file, truncate in case the move was unsuccessful */ 458 return open("/contents/rockbox.log", O_RDWR | O_CREAT | O_APPEND | O_TRUNC); 459} 460 461int main(int argc, char **argv) 462{ 463 (void) argc; 464 (void) argv; 465 /* redirect stdout and stderr to have error messages logged somewhere on the 466 * user partition */ 467 int fd = open_log(); 468 if(fd >= 0) 469 { 470 dup2(fd, fileno(stdout)); 471 dup2(fd, fileno(stderr)); 472 close(fd); 473 } 474 /* print version */ 475 printf("Rockbox boot loader\n"); 476 printf("Version: %s\n", rbversion); 477 printf("%s\n", MODEL_NAME); 478 479 system_init(); 480 core_allocator_init(); 481 kernel_init(); 482 paths_init(); 483 lcd_init(); 484 font_init(); 485 button_init(); 486 backlight_init(); 487 backlight_set_brightness(DEFAULT_BRIGHTNESS_SETTING); 488 /* try to load the extra font we install on the device */ 489 int font_id = font_load("/usr/local/share/rockbox/bootloader.fnt"); 490 if(font_id >= 0) 491 lcd_setfont(font_id); 492 /* extract logo */ 493 if(!extract_sony_logo()) 494 create_sony_logo(); 495 /* run all tools menu */ 496 while(true) 497 { 498 enum boot_mode mode = get_boot_mode(); 499 if(mode == BOOT_USB || mode == BOOT_OF) 500 { 501 fflush(stdout); 502 fflush(stderr); 503 close(fileno(stdout)); 504 close(fileno(stderr)); 505 /* for now the only way we have to trigger USB mode it to run the OF */ 506 /* boot OF */ 507 execvp("/usr/local/bin/SpiderApp.of", argv); 508 error_screen("Cannot boot OF"); 509 sleep(5 * HZ); 510 } 511 else if(mode == BOOT_TOOLS) 512 { 513 tools_screen(); 514 } 515 else if(mode == BOOT_ROCKBOX) 516 { 517 /* Rockbox expects /.rockbox to contain themes, rocks, etc, but we 518 * cannot easily create this symlink because the root filesystem is 519 * mounted read-only. Although we could remount it read-write temporarily, 520 * this is neededlessly complicated and we defer this job to the dualboot 521 * install script */ 522 fflush(stdout); 523 execl("/contents/.rockbox/rockbox.sony", "rockbox.sony", NULL); 524 printf("execvp failed: %s\n", strerror(errno)); 525 /* fallback to OF in case of failure */ 526 error_screen("Cannot boot Rockbox"); 527 sleep(5 * HZ); 528 } 529 else 530 { 531 printf("suspend\n"); 532 nwz_power_suspend(); 533 } 534 } 535 /* if we reach this point, everything failed, so return an error so that 536 * sysmgrd knows something is wrong */ 537 return 1; 538}