A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1437 lines 43 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2002 Daniel Stenberg 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#include <stdio.h> 22#include <stdlib.h> 23#include <stdbool.h> 24#include "string-extra.h" 25#include "panic.h" 26 27#include "applimits.h" 28#include "dir.h" 29#include "file.h" 30#include "lcd.h" 31#include "font.h" 32#include "button.h" 33#include "kernel.h" 34#include "usb.h" 35#include "tree.h" 36#include "audio.h" 37#include "playlist.h" 38#include "menu.h" 39#include "skin_engine/skin_engine.h" 40#include "settings.h" 41#include "debug.h" 42#include "storage.h" 43#include "rolo.h" 44#include "icons.h" 45#include "lang.h" 46#include "screens.h" 47#include "keyboard.h" 48#include "bookmark.h" 49#include "onplay.h" 50#include "core_alloc.h" 51#include "power.h" 52#include "action.h" 53#include "talk.h" 54#include "filetypes.h" 55#include "misc.h" 56#include "pathfuncs.h" 57#include "filetree.h" 58#include "tagtree.h" 59#ifdef HAVE_RECORDING 60#include "recorder/recording.h" 61#endif 62#include "rtc.h" 63#include "dircache.h" 64#ifdef HAVE_TAGCACHE 65#include "tagcache.h" 66#endif 67#include "yesno.h" 68#include "eeprom_settings.h" 69#include "playlist_catalog.h" 70 71/* gui api */ 72#include "list.h" 73#include "splash.h" 74#include "quickscreen.h" 75#include "shortcuts.h" 76#include "appevents.h" 77 78#include "root_menu.h" 79 80static struct gui_synclist tree_lists; 81 82/* I put it here because other files doesn't use it yet, 83 * but should be elsewhere since it will be used mostly everywhere */ 84static struct tree_context tc; 85 86char lastfile[MAX_PATH]; 87static char lastdir[MAX_PATH]; 88#ifdef HAVE_TAGCACHE 89static int lasttable, lastextra; 90#endif 91 92static bool reload_dir = false; 93 94static bool start_wps = false; 95static int curr_context = false;/* id3db or tree*/ 96 97static int dirbrowse(void); 98static int ft_play_dirname(char* name); 99static int ft_play_filename(char *dir, char *file, int attr); 100static void say_filetype(int attr); 101 102struct entry* tree_get_entries(struct tree_context *t) 103{ 104 return core_get_data(t->cache.entries_handle); 105} 106 107struct entry* tree_get_entry_at(struct tree_context *t, int index) 108{ 109 if(index < 0 || index >= t->cache.max_entries) 110 return NULL; /* no entry */ 111 struct entry* entries = tree_get_entries(t); 112 return &entries[index]; 113} 114 115static struct entry *get_valid_entry(const char* funcname, 116 struct tree_context *t, int index) 117{ 118 struct entry *entry = tree_get_entry_at(t, index); 119 if (!entry) 120 panicf("Invalid tree entry %s", funcname); 121 /*DEBUGF("%s tc: %x idx: %d\n", funcname, t, index);*/ 122 return entry; 123} 124 125static bool ext_stripit(bool isdir, int attr, int dirfilter) 126{ 127 if((dirfilter != SHOW_ID3DB) && !isdir) 128 { 129 switch(global_settings.show_filename_ext) 130 { 131 case 0: 132 /* show file extension: off */ 133 return true; 134 break; 135 case 1: 136 /* show file extension: on */ 137 break; 138 case 2: 139 /* show file extension: only unknown types */ 140 return filetype_supported(attr); 141 case 3: 142 default: 143 /* show file extension: only when viewing all */ 144 return (dirfilter != SHOW_ALL); 145 } 146 } 147 return false; 148} 149 150static const char* tree_get_filename(int selected_item, void *data, 151 char *buffer, size_t buffer_len) 152{ 153 struct tree_context * local_tc=(struct tree_context *)data; 154 char *name; 155 int attr=0; 156#ifdef HAVE_TAGCACHE 157 bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB; 158 159 if (id3db) 160 { 161 return tagtree_get_entry_name(&tc, selected_item, buffer, buffer_len); 162 } 163 else 164#endif 165 { 166 struct entry *entry = get_valid_entry(__func__, local_tc, selected_item); 167 name = entry->name; 168 attr = entry->attr; 169 } 170 171 if(ext_stripit((attr & ATTR_DIRECTORY), attr, *(local_tc->dirfilter))) 172 { 173 return(strip_extension(buffer, buffer_len, name)); 174 } 175 return(name); 176} 177 178#ifdef HAVE_LCD_COLOR 179static int tree_get_filecolor(int selected_item, void * data) 180{ 181 if (*tc.dirfilter == SHOW_ID3DB) 182 return -1; 183 struct tree_context * local_tc=(struct tree_context *)data; 184 struct entry *entry = get_valid_entry(__func__, local_tc, selected_item); 185 186 return filetype_get_color(entry->name, entry->attr); 187} 188#endif 189 190static enum themable_icons tree_get_fileicon(int selected_item, void * data) 191{ 192 struct tree_context * local_tc=(struct tree_context *)data; 193#ifdef HAVE_TAGCACHE 194 bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB; 195 if (id3db) { 196 return tagtree_get_icon(&tc); 197 } 198 else 199#endif 200 { 201 struct entry *entry = get_valid_entry(__func__, local_tc, selected_item); 202 203 return filetype_get_icon(entry->attr); 204 } 205} 206 207static int tree_voice_cb(int selected_item, void * data) 208{ 209 struct tree_context * local_tc=(struct tree_context *)data; 210 unsigned char *name; 211 int attr=0; 212 int customaction = ONPLAY_NO_CUSTOMACTION; 213#ifdef HAVE_TAGCACHE 214 bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB; 215 char buf[AVERAGE_FILENAME_LENGTH*2]; 216 217 if (id3db) 218 { 219 attr = tagtree_get_attr(local_tc); 220 name = tagtree_get_entry_name(local_tc, selected_item, buf, sizeof(buf)); 221 customaction = tagtree_get_custom_action(local_tc); 222 223 /* See if name is an encoded ID, if it is, then speak it normally */ 224 int lang_id = P2ID(name); 225 /*debugf("%s Found name %s id %d\n", __func__, P2STR(name), lang_id);*/ 226 if (lang_id >= 0) { 227 if (global_settings.talk_menu) 228 talk_id(lang_id, true); 229 return 0; 230 } 231 232 /* Otherwise, it is either a custom "header" or a database entry, 233 so try to look up a talk clip for it. */ 234 235 // XXX this needs further work, so disable it for now 236 // -- need to distinguish between "headers" and entries 237 // each entry type ("artist", "album", etc) should be delineated 238 // so we can split the clips into subdirs. 239#if 0 240 if (global_settings.talk_file_clip) { 241 if (talk_file(LANG_DIR"/database/", NULL, 242 P2STR(name), file_thumbnail_ext, NULL, true) > 0) 243 return 0; 244 245 // XXX fall back to spelling it out? 246 return 0; 247 } 248#endif 249 } 250 else 251#endif 252 { 253 struct entry *entry = get_valid_entry(__func__, local_tc, selected_item); 254 name = entry->name; 255 attr = entry->attr; 256 } 257 bool is_dir = (attr & ATTR_DIRECTORY); 258 bool did_clip = false; 259 /* First the .talk clip case */ 260 if(is_dir) 261 { 262 if(global_settings.talk_dir_clip) 263 { 264 did_clip = true; 265 if (ft_play_dirname(name) <= 0) 266 /* failed, not existing */ 267 did_clip = false; 268 } 269 } else { /* it's a file */ 270 if (global_settings.talk_file_clip && (attr & FILE_ATTR_THUMBNAIL)) 271 { 272 did_clip = true; 273 if (ft_play_filename(local_tc->currdir, name, attr) <= 0) 274 /* failed, not existing */ 275 did_clip = false; 276 } 277 } 278 bool spell_name = (customaction == ONPLAY_CUSTOMACTION_FIRSTLETTER); 279 if(!did_clip) 280 { 281 /* say the number or spell if required or as a fallback */ 282 switch (is_dir ? global_settings.talk_dir : global_settings.talk_file) 283 { 284 case 1: /* as numbers */ 285 talk_id(is_dir ? VOICE_DIR : VOICE_FILE, false); 286 talk_number(selected_item+1 - (is_dir ? 0 : local_tc->dirsindir), 287 true); 288 break; 289 case 2: /* spelled */ 290 talk_shutup(); 291 if(global_settings.talk_filetype) 292 { 293 if(is_dir) 294 talk_id(VOICE_DIR, true); 295 } 296 spell_name = true; 297 break; 298 } 299 } 300 301 if(global_settings.talk_filetype && !is_dir 302 && *local_tc->dirfilter < NUM_FILTER_MODES) 303 { 304 say_filetype(attr); 305 } 306 307 /* spell name AFTER voicing filetype */ 308 if (spell_name) { 309 bool stripit = ext_stripit(is_dir, attr, *(local_tc->dirfilter)); 310 char *ext = NULL; 311 312 /* Don't spell the extension if it's not displayed */ 313 314 if (stripit) { 315 ext = strrchr(name, '.'); 316 if (ext) 317 *ext = 0; 318 } 319 320 talk_spell(name, true); 321 322 if (stripit && ext) 323 *ext = '.'; 324 } 325 326 return 0; 327} 328 329bool check_rockboxdir(void) 330{ 331 if(!dir_exists(ROCKBOX_DIR)) 332 { /* No need to localise this message. 333 If .rockbox is missing, it wouldn't work anyway */ 334 FOR_NB_SCREENS(i) 335 screens[i].clear_display(); 336 splash(HZ*2, "No .rockbox directory"); 337 FOR_NB_SCREENS(i) 338 screens[i].clear_display(); 339 splash(HZ*2, "Installation incomplete"); 340 return false; 341 } 342 return true; 343} 344 345/* do this really late in the init sequence */ 346void tree_init(void) 347{ 348 check_rockboxdir(); 349 strcpy(tc.currdir, "/"); 350} 351 352struct tree_context* tree_get_context(void) 353{ 354 return &tc; 355} 356 357void tree_lock_cache(struct tree_context *t) 358{ 359 core_pin(t->cache.name_buffer_handle); 360 core_pin(t->cache.entries_handle); 361} 362 363void tree_unlock_cache(struct tree_context *t) 364{ 365 core_unpin(t->cache.name_buffer_handle); 366 core_unpin(t->cache.entries_handle); 367} 368 369/* 370 * Returns the position of a given file in the current directory 371 * returns -1 if not found 372 */ 373static int tree_get_file_position(char * filename) 374{ 375 int i, ret = -1;/* no file match, return undefined */ 376 377 tree_lock_cache(&tc); 378 struct entry *entries = tree_get_entries(&tc); 379 380 /* use lastfile to determine the selected item (default=0) */ 381 for (i=0; i < tc.filesindir; i++) 382 { 383#if ((CONFIG_PLATFORM & PLATFORM_NATIVE) || defined(__APPLE__) || defined(_WIN32) || defined(__CYGWIN__)) 384 if (!strcasecmp(entries[i].name, filename)) 385#else 386 if (!strcmp(entries[i].name, filename)) 387#endif 388 { 389 ret = i; 390 break; 391 } 392 } 393 tree_unlock_cache(&tc); 394 return(ret); 395} 396 397/* 398 * Called when a new dir is loaded (for example when returning from other apps ...) 399 * also completely redraws the tree 400 */ 401static int update_dir(void) 402{ 403 struct gui_synclist * const list = &tree_lists; 404 int show_path_in_browser = global_settings.show_path_in_browser; 405 bool changed = false; 406 407 const char* title = NULL;/* Must clear the title as the list is reused */ 408 int icon = NOICON; 409 410#ifdef HAVE_TAGCACHE 411 bool id3db = *tc.dirfilter == SHOW_ID3DB; 412#else 413 const bool id3db = false; 414#endif 415 416#ifdef HAVE_TAGCACHE 417 /* Checks for changes */ 418 if (id3db) { 419 if (tc.currtable != lasttable || 420 tc.currextra != lastextra || 421 reload_dir) 422 { 423 if (tagtree_load(&tc) < 0) 424 return -1; 425 426 lasttable = tc.currtable; 427 lastextra = tc.currextra; 428 changed = true; 429 } 430 } 431 else 432#endif 433 { 434 tc.sort_dir = global_settings.sort_dir; 435 /* if the tc.currdir has been changed, reload it ...*/ 436 if (reload_dir || strncmp(tc.currdir, lastdir, sizeof(lastdir))) 437 { 438 if (ft_load(&tc, NULL) < 0) 439 return -1; 440 strmemccpy(lastdir, tc.currdir, MAX_PATH); 441 changed = true; 442 } 443 } 444 /* if selected item is undefined */ 445 if (tc.selected_item == -1) 446 { 447 if (!id3db) 448 /* use lastfile to determine the selected item */ 449 tc.selected_item = tree_get_file_position(lastfile); 450 451 /* If the file doesn't exists, select the first one (default) */ 452 if(tc.selected_item < 0) 453 tc.selected_item = 0; 454 changed = true; 455 } 456 if (changed) 457 { 458 if( !id3db && tc.dirfull ) 459 { 460 splash(HZ, ID2P(LANG_SHOWDIR_BUFFER_FULL)); 461 } 462 } 463 464 gui_synclist_init(list, &tree_get_filename, &tc, false, 1, NULL); 465 466#ifdef HAVE_TAGCACHE 467 if (id3db) 468 { 469 if (show_path_in_browser == SHOW_PATH_FULL 470 || show_path_in_browser == SHOW_PATH_CURRENT) 471 { 472 title = tagtree_get_title(&tc); 473 icon = filetype_get_icon(ATTR_DIRECTORY); 474 } 475 } 476 else 477#endif 478 { 479 if (tc.browse && tc.browse->title) 480 { 481 title = tc.browse->title; 482 icon = tc.browse->icon; 483 if (icon == NOICON) 484 icon = filetype_get_icon(ATTR_DIRECTORY); 485 /* display sub directories in the title of plugin browser */ 486 if (tc.dirlevel > 0 && *tc.dirfilter == SHOW_PLUGINS) 487 { 488 char *subdir = strrchr(tc.currdir, '/'); 489 if (subdir != NULL) 490 title = subdir + 1; /* step past the separator */ 491 } 492 } 493 else 494 { 495 if (show_path_in_browser == SHOW_PATH_FULL) 496 { 497 title = tc.currdir; 498 icon = filetype_get_icon(ATTR_DIRECTORY); 499 } 500 else if (show_path_in_browser == SHOW_PATH_CURRENT) 501 { 502 title = strrchr(tc.currdir, '/'); 503 if (title != NULL) 504 { 505 title++; /* step past the separator */ 506 if (*title == '\0') 507 { 508 /* Display "Files" for the root dir */ 509 title = ID2P(LANG_DIR_BROWSER); 510 } 511 icon = filetype_get_icon(ATTR_DIRECTORY); 512 } 513 } 514 } 515 } 516 517 /* set title and icon, if nothing is set, clear the title 518 * with NULL and icon as NOICON as the list is reused */ 519 gui_synclist_set_title(list, P2STR((unsigned char*)title), icon); 520 521 gui_synclist_set_nb_items(list, tc.filesindir); 522 gui_synclist_set_icon_callback(list, 523 global_settings.show_icons?tree_get_fileicon:NULL); 524 gui_synclist_set_voice_callback(list, &tree_voice_cb); 525#ifdef HAVE_LCD_COLOR 526 gui_synclist_set_color_callback(list, &tree_get_filecolor); 527#endif 528 if( tc.selected_item >= tc.filesindir) 529 tc.selected_item=tc.filesindir-1; 530 531 gui_synclist_select_item(list, tc.selected_item); 532 gui_synclist_draw(list); 533 gui_synclist_speak_item(list); 534 return tc.filesindir; 535} 536 537/* load tracks from specified directory to resume play */ 538void resume_directory(const char *dir) 539{ 540 int dirfilter = *tc.dirfilter; 541 int ret; 542#ifdef HAVE_TAGCACHE 543 bool id3db = *tc.dirfilter == SHOW_ID3DB; 544#else 545 const bool id3db = false; 546#endif 547 /* make sure the dirfilter is sane. The only time it should be possible 548 * thats its not is when resume playlist is called from a plugin 549 */ 550 if (!id3db) 551 *tc.dirfilter = global_settings.dirfilter; 552 ret = ft_load(&tc, dir); 553 *tc.dirfilter = dirfilter; 554 if (ret < 0) 555 return; 556 lastdir[0] = 0; 557 558 ft_build_playlist(&tc, 0); 559 560#ifdef HAVE_TAGCACHE 561 if (id3db) 562 tagtree_load(&tc); 563#endif 564} 565 566/* Returns the current working directory and also writes cwd to buf if 567 non-NULL. In case of error, returns NULL. */ 568#ifdef CTRU 569char *__wrap_getcwd(char *buf, getcwd_size_t size) 570#else 571char *getcwd(char *buf, getcwd_size_t size) 572#endif 573{ 574 if (!buf) 575 return tc.currdir; 576 else if (size) 577 { 578 if (strmemccpy(buf, tc.currdir, size) != NULL) 579 return buf; 580 } 581 /* size == 0, or truncation in strmemccpy */ 582 return NULL; 583} 584 585/* Force a reload of the directory next time directory browser is called */ 586void reload_directory(void) 587{ 588 reload_dir = true; 589} 590 591char* get_current_file(char* buffer, size_t buffer_len) 592{ 593#ifdef HAVE_TAGCACHE 594 /* in ID3DB mode it is a bad idea to call this function */ 595 /* (only happens with `follow playlist') */ 596 if( *tc.dirfilter == SHOW_ID3DB ) 597 return NULL; 598#endif 599 600 struct entry *entry = tree_get_entry_at(&tc, tc.selected_item); 601 if (entry && getcwd(buffer, buffer_len)) 602 { 603 if (!tc.dirlength) 604 return buffer; 605 606 size_t usedlen = strlen(buffer); 607 608 if (usedlen + 2 < buffer_len) /* ensure enough room for '/' + '\0' */ 609 { 610 if (buffer[usedlen-1] != '/') 611 { 612 buffer[usedlen] = '/'; 613 /* strmemccpy will zero terminate if we run out of space after */ 614 usedlen++; 615 } 616 buffer_len -= usedlen; 617 if (strmemccpy(buffer + usedlen, entry->name, buffer_len) != NULL) 618 return buffer; 619 } 620 } 621 return NULL; 622} 623 624/* Allow apps to change our dirfilter directly (required for sub browsers) 625 if they're suddenly going to become a file browser for example */ 626void set_dirfilter(int l_dirfilter) 627{ 628 *tc.dirfilter = l_dirfilter; 629} 630 631/* Selects a path + file and update tree context properly */ 632static void set_current_file_ex(const char *path, const char *filename) 633{ 634 int i; 635 636#ifdef HAVE_TAGCACHE 637 /* in ID3DB mode it is a bad idea to call this function */ 638 /* (only happens with `follow playlist') */ 639 if( *tc.dirfilter == SHOW_ID3DB ) 640 return; 641#endif 642 643 if (!filename) /* path and filename supplied combined */ 644 { 645 /* separate directory from filename */ 646 /* gets the directory's name and put it into tc.currdir */ 647 filename = strrchr(path+1,'/'); 648 size_t endpos = filename - path; 649 if (filename && endpos < MAX_PATH - 1) 650 { 651 strmemccpy(tc.currdir, path, endpos + 1); 652 filename++; 653 } 654 else 655 { 656 if (strlen(tc.currdir) <= 1) 657 { 658 strcpy(tc.currdir, "/"); 659 } 660 filename = path+1; 661 } 662 } 663 else /* path and filename came in separate ensure an ending '/' */ 664 { 665 char *end_p = strmemccpy(tc.currdir, path, MAX_PATH); 666 size_t endpos = end_p - tc.currdir; 667 if (endpos < MAX_PATH) 668 { 669 if (tc.currdir[endpos - 2] != '/') 670 { 671 tc.currdir[endpos - 1] = '/'; 672 tc.currdir[endpos] = '\0'; 673 } 674 } 675 } 676 strmemccpy(lastfile, filename, MAX_PATH); 677 678 679 /* If we changed dir we must recalculate the dirlevel 680 and adjust the selected history properly */ 681 if (strncmp(tc.currdir,lastdir,sizeof(lastdir))) 682 { 683 tc.dirlevel = 0; 684 tc.selected_item_history[tc.dirlevel] = -1; 685 686 /* use '/' to calculate dirlevel */ 687 for (i = 1; path[i] != '\0'; i++) 688 { 689 if (path[i] == '/') 690 { 691 tc.dirlevel++; 692 tc.selected_item_history[tc.dirlevel] = -1; 693 } 694 } 695 } 696 if (ft_load(&tc, NULL) >= 0) 697 { 698 tc.selected_item = tree_get_file_position(lastfile); 699 if (!tc.is_browsing && tc.out_of_tree == 0) 700 { 701 /* the browser is closed */ 702 /* don't allow the previous items to overwrite what we just loaded */ 703 tc.out_of_tree = tc.selected_item + 1; 704 } 705 } 706} 707 708/* Selects a file and update tree context properly */ 709void set_current_file(const char *path) 710{ 711 set_current_file_ex(path, NULL); 712} 713 714 715static int exit_to_new_screen(int screen) 716{ 717 gui_synclist_scroll_stop(&tree_lists); 718 return screen; 719} 720 721/* main loop, handles key events */ 722static int dirbrowse(void) 723{ 724 int numentries=0; 725 char buf[MAX_PATH]; 726 int button; 727 int oldbutton; 728 bool reload_root = false; 729 int lastfilter = *tc.dirfilter; 730 bool lastsortcase = global_settings.sort_case; 731 bool exit_func = false; 732 733 char* currdir = tc.currdir; /* just a shortcut */ 734#ifdef HAVE_TAGCACHE 735 bool id3db = *tc.dirfilter == SHOW_ID3DB; 736 737 if (id3db) 738 curr_context=CONTEXT_ID3DB; 739 else 740#endif 741 curr_context=CONTEXT_TREE; 742 if (tc.selected_item < 0) 743 tc.selected_item = 0; 744#ifdef HAVE_TAGCACHE 745 lasttable = -1; 746 lastextra = -1; 747#endif 748 749 start_wps = false; 750 numentries = update_dir(); 751 reload_dir = false; 752 if (numentries == -1) 753 return exit_to_new_screen(GO_TO_PREVIOUS); /* currdir is not a directory */ 754 755 if (*tc.dirfilter > NUM_FILTER_MODES && numentries==0) 756 { 757 splash(HZ*2, *tc.dirfilter == SHOW_M3U ? 758 ID2P(LANG_CATALOG_NO_PLAYLISTS) : ID2P(LANG_NO_FILES)); 759 return exit_to_new_screen(GO_TO_PREVIOUS); /* No files found for rockbox_browse() */ 760 } 761 762 while(tc.browse && tc.is_browsing) { 763 bool restore = false; 764 if (tc.dirlevel < 0) 765 tc.dirlevel = 0; /* shouldnt be needed.. this code needs work! */ 766 767 keyclick_set_callback(gui_synclist_keyclick_callback, &tree_lists); 768 button = get_action(CONTEXT_TREE|ALLOW_SOFTLOCK, 769 list_do_action_timeout(&tree_lists, HZ/2)); 770 oldbutton = button; 771 gui_synclist_do_button(&tree_lists, &button); 772 tc.selected_item = gui_synclist_get_sel_pos(&tree_lists); 773 int customaction = ONPLAY_NO_CUSTOMACTION; 774 bool do_restore_display = true; 775 #ifdef HAVE_TAGCACHE 776 if (id3db && (button == ACTION_STD_OK || button == ACTION_STD_CONTEXT)) 777 { 778 customaction = tagtree_get_custom_action(&tc); 779 if (customaction == ONPLAY_CUSTOMACTION_SHUFFLE_SONGS) 780 { 781 /* The code to insert shuffled is on the context branch of the switch so we always go here */ 782 button = ACTION_STD_CONTEXT; 783 do_restore_display = false; 784 } 785 } 786 #endif 787 switch ( button ) { 788 case ACTION_STD_OK: 789 /* nothing to do if no files to display */ 790 if ( numentries == 0 ) 791 break; 792 if (tc.browse->flags & BROWSE_SELECTONLY) 793 { 794 struct entry *entry = 795 get_valid_entry(__func__, &tc, tc.selected_item); 796 short attr = entry->attr; 797 if(!(attr & ATTR_DIRECTORY)) 798 { 799 tc.browse->flags |= BROWSE_SELECTED; 800 get_current_file(tc.browse->buf, tc.browse->bufsize); 801 return exit_to_new_screen(GO_TO_PREVIOUS); 802 } 803 } 804#ifdef HAVE_TAGCACHE 805 switch (id3db ? tagtree_enter(&tc, true) : ft_enter(&tc)) 806#else 807 switch (ft_enter(&tc)) 808#endif 809 { 810 case GO_TO_FILEBROWSER: reload_dir = true; break; 811 case GO_TO_PLUGIN: 812 return exit_to_new_screen(GO_TO_PLUGIN); 813 case GO_TO_WPS: 814 return exit_to_new_screen(GO_TO_WPS); 815#if CONFIG_TUNER 816 case GO_TO_FM: 817 return exit_to_new_screen(GO_TO_FM); 818#endif 819 case GO_TO_ROOT: exit_func = true; break; 820 default: 821 break; 822 } 823 restore = do_restore_display; 824 break; 825 826 case ACTION_STD_CANCEL: 827 exit_to_new_screen(0); 828 if (*tc.dirfilter > NUM_FILTER_MODES && tc.dirlevel < 1) { 829 exit_func = true; 830 break; 831 } 832 if ((*tc.dirfilter == SHOW_ID3DB && tc.dirlevel == 0) || 833 ((*tc.dirfilter != SHOW_ID3DB && !strcmp(currdir,"/")))) 834 { 835 if (oldbutton == ACTION_TREE_PGLEFT) 836 break; 837 else 838 return exit_to_new_screen(GO_TO_ROOT); 839 } 840 841#ifdef HAVE_TAGCACHE 842 if (id3db) 843 tagtree_exit(&tc, true); 844 else 845#endif 846 if (ft_exit(&tc) == 3) 847 exit_func = true; 848 849 restore = do_restore_display; 850 break; 851 852 case ACTION_TREE_STOP: 853 if (list_stop_handler()) 854 restore = do_restore_display; 855 break; 856 857 case ACTION_STD_MENU: 858 return exit_to_new_screen(GO_TO_ROOT); 859 break; 860 861#ifdef HAVE_RECORDING 862 case ACTION_STD_REC: 863 return exit_to_new_screen(GO_TO_RECSCREEN); 864#endif 865 866 case ACTION_TREE_WPS: 867 return exit_to_new_screen(GO_TO_PREVIOUS_MUSIC); 868 break; 869#ifdef HAVE_QUICKSCREEN 870 case ACTION_STD_QUICKSCREEN: 871 { 872 bool enter_shortcuts_menu = global_settings.shortcuts_replaces_qs; 873 if (enter_shortcuts_menu && *tc.dirfilter >= NUM_FILTER_MODES) 874 break; 875 else if (!enter_shortcuts_menu) 876 { 877 int ret = quick_screen_quick(button); 878 if (ret == QUICKSCREEN_IN_USB) 879 reload_dir = true; 880 else if (ret == QUICKSCREEN_GOTO_SHORTCUTS_MENU) 881 enter_shortcuts_menu = true; 882 } 883 884 if (enter_shortcuts_menu && *tc.dirfilter < NUM_FILTER_MODES) 885 { 886 int last_screen = global_status.last_screen; 887 global_status.last_screen = GO_TO_SHORTCUTMENU; 888 int shortcut_ret = do_shortcut_menu(NULL); 889 if (shortcut_ret == GO_TO_PREVIOUS) 890 global_status.last_screen = last_screen; 891 else 892 return exit_to_new_screen(shortcut_ret); 893 } 894 else if (enter_shortcuts_menu) /* currently disabled */ 895 { 896 /* QuickScreen defers skin updates, popping its activity, when 897 switching to Shortcuts Menu, so make up for that here: */ 898 FOR_NB_SCREENS(i) 899 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); 900 } 901 902 restore = do_restore_display; 903 break; 904 } 905#endif 906 907#ifdef HAVE_HOTKEY 908 case ACTION_TREE_HOTKEY: 909 if (!global_settings.hotkey_tree) 910 break; 911 /* fall through */ 912#endif 913 case ACTION_STD_CONTEXT: 914 { 915 bool hotkey = button == ACTION_TREE_HOTKEY; 916 int onplay_result; 917 int attr = 0; 918 919 if (tc.browse->flags & BROWSE_NO_CONTEXT_MENU) 920 break; 921 922 if(!numentries) 923 onplay_result = onplay(NULL, 0, curr_context, hotkey, customaction); 924 else { 925#ifdef HAVE_TAGCACHE 926 if (id3db) 927 { 928 if (tagtree_get_attr(&tc) == FILE_ATTR_AUDIO) 929 { 930 attr = FILE_ATTR_AUDIO; 931 932 /* Look up the filename only once it is needed, so we 933 don't have to wait for the disk to wake up here. */ 934 buf[0] = '\0'; 935 } 936 else 937 { 938 attr = ATTR_DIRECTORY; 939 int title_len = 0; 940 941 /* In case of "special entries", add table title as 942 prefix, e.g. "The Beatles [All Tracks]", instead 943 of just "[All Tracks]", to improve the suggested 944 playlist filename. 945 */ 946 if (tc.selected_item < tc.special_entry_count) 947 { 948 title_len = snprintf(buf, sizeof(buf), "%s ", 949 tagtree_get_title(&tc)); 950 if (title_len < 0) 951 title_len = 0; 952 } 953 954 if (title_len < (int) sizeof(buf)) 955 tagtree_get_entry_name(&tc, tc.selected_item, 956 buf + title_len, 957 sizeof(buf) - title_len); 958 959 fix_path_part(buf, 0, sizeof(buf) - 1); 960 } 961 } 962 else 963#endif 964 { 965 struct entry *entry = 966 get_valid_entry(__func__, &tc, tc.selected_item); 967 968 attr = entry->attr; 969 970 ft_assemble_path(buf, sizeof(buf), currdir, entry->name); 971 972 } 973 onplay_result = onplay(buf, attr, curr_context, hotkey, customaction); 974 } 975 switch (onplay_result) 976 { 977 case ONPLAY_MAINMENU: 978 return exit_to_new_screen(GO_TO_ROOT); 979 break; 980 981 case ONPLAY_OK: 982 restore = do_restore_display; 983 break; 984 985 case ONPLAY_RELOAD_DIR: 986 reload_dir = true; 987 break; 988 989 case ONPLAY_START_PLAY: 990 return exit_to_new_screen(GO_TO_WPS); 991 break; 992 993 case ONPLAY_PLUGIN: 994 return exit_to_new_screen(GO_TO_PLUGIN); 995 break; 996 } 997 break; 998 } 999 1000#ifdef HAVE_HOTSWAP 1001 case SYS_FS_CHANGED: 1002#ifdef HAVE_TAGCACHE 1003 if (!id3db) 1004#endif 1005 reload_dir = true; 1006 /* The 'dir no longer valid' situation will be caught later 1007 * by checking the showdir() result. */ 1008 break; 1009#endif 1010 1011 default: 1012 if (tc.browse->disable_gui == true) { 1013 #ifdef HAVE_TAGCACHE 1014 switch (id3db ? tagtree_enter(&tc, true) : ft_enter(&tc)) 1015 #else 1016 switch (ft_enter(&tc)) 1017 #endif 1018 { 1019 case GO_TO_FILEBROWSER: reload_dir = true; break; 1020 case GO_TO_PLUGIN: 1021 return GO_TO_PLUGIN; 1022 case GO_TO_WPS: 1023 return GO_TO_WPS; 1024 #if CONFIG_TUNER 1025 case GO_TO_FM: 1026 return GO_TO_FM; 1027 #endif 1028 case GO_TO_ROOT: exit_func = true; break; 1029 default: 1030 break; 1031 } 1032 restore = do_restore_display; 1033 return GO_TO_ROOT; 1034 } 1035 if (default_event_handler(button) == SYS_USB_CONNECTED) 1036 { 1037 if(*tc.dirfilter > NUM_FILTER_MODES) 1038 /* leave sub-browsers after usb, doing otherwise 1039 might be confusing to the user */ 1040 exit_func = true; 1041 else 1042 reload_dir = true; 1043 } 1044 break; 1045 } 1046 if (start_wps) 1047 return exit_to_new_screen(GO_TO_WPS); 1048 if (button && !IS_SYSEVENT(button)) 1049 { 1050 storage_spin(); 1051 } 1052 1053 1054 check_rescan: 1055 /* do we need to rescan dir? */ 1056 if (reload_dir || reload_root || 1057 lastfilter != *tc.dirfilter || 1058 lastsortcase != global_settings.sort_case) 1059 { 1060 if (reload_root) { 1061 strcpy(currdir, "/"); 1062 tc.dirlevel = 0; 1063#ifdef HAVE_TAGCACHE 1064 tc.currtable = 0; 1065 tc.currextra = 0; 1066 lasttable = -1; 1067 lastextra = -1; 1068#endif 1069 reload_root = false; 1070 } 1071 1072 if (!reload_dir) 1073 { 1074 gui_synclist_select_item(&tree_lists, 0); 1075 gui_synclist_draw(&tree_lists); 1076 tc.selected_item = 0; 1077 lastdir[0] = 0; 1078 } 1079 1080 lastfilter = *tc.dirfilter; 1081 lastsortcase = global_settings.sort_case; 1082 restore = do_restore_display; 1083 } 1084 1085 if (exit_func) 1086 return exit_to_new_screen(GO_TO_PREVIOUS); 1087 1088 if (restore || reload_dir) { 1089 FOR_NB_SCREENS(i) 1090 screens[i].scroll_stop(); 1091 /* restore display */ 1092 numentries = update_dir(); 1093 reload_dir = false; 1094 if (currdir[1] && (numentries < 0)) 1095 { /* not in root and reload failed */ 1096 reload_root = true; /* try root */ 1097 goto check_rescan; 1098 } 1099 } 1100 } 1101 return exit_to_new_screen(GO_TO_ROOT); 1102} 1103 1104int create_playlist(void) 1105{ 1106 bool ret; 1107 trigger_cpu_boost(); 1108 ret = catalog_add_to_a_playlist(PATH_ROOTSTR, ATTR_DIRECTORY, true, NULL, NULL); 1109 cancel_cpu_boost(); 1110 1111 return (ret) ? 1 : 0; 1112} 1113 1114#define NUM_TC_BACKUP 3 1115static struct tree_context backups[NUM_TC_BACKUP]; 1116/* do not make backup if it is not recursive call */ 1117static int backup_count = -1; 1118int rockbox_browse(struct browse_context *browse) 1119{ 1120 tc.is_browsing = (browse != NULL); 1121 int ret_val = 0; 1122 int dirfilter = SHOW_ALL; 1123 if (tc.is_browsing) 1124 dirfilter = browse->dirfilter; 1125 else 1126 { 1127 DEBUGF("%s browse is [NULL] \n", __func__); 1128 browse = tc.browse; 1129 } 1130 if (backup_count >= NUM_TC_BACKUP) 1131 return GO_TO_PREVIOUS; 1132 if (backup_count >= 0) 1133 backups[backup_count] = tc; 1134 backup_count++; 1135 int *prev_dirfilter = tc.dirfilter; 1136 tc.dirfilter = &dirfilter; 1137 tc.sort_dir = global_settings.sort_dir; 1138 1139 reload_dir = true; 1140 1141 if (tc.out_of_tree > 0) 1142 { 1143 /* an item has already been loaded out_of_tree holds the selected index 1144 * what happens with the item is dependent on the browse context */ 1145 tc.selected_item = tc.out_of_tree - 1; 1146 tc.out_of_tree = 0; 1147 ret_val = ft_enter(&tc); 1148 } 1149 else 1150 { 1151 if (*tc.dirfilter >= NUM_FILTER_MODES) 1152 { 1153 int last_context; 1154 /* don't reset if its the same browse already loaded */ 1155 if (tc.browse != browse || 1156 !(tc.currdir[1] && strstr(tc.currdir, browse->root) != NULL)) 1157 { 1158 tc.browse = browse; 1159 tc.selected_item = 0; 1160 tc.dirlevel = 0; 1161 1162 strmemccpy(tc.currdir, browse->root, sizeof(tc.currdir)); 1163 } 1164 1165 start_wps = false; 1166 last_context = curr_context; 1167 1168 if (browse->selected) 1169 { 1170 set_current_file_ex(browse->root, browse->selected); 1171 /* set_current_file changes dirlevel, change it back */ 1172 tc.dirlevel = 0; 1173 } 1174 1175 ret_val = dirbrowse(); 1176 curr_context = last_context; 1177 } 1178 else 1179 { 1180 if (dirfilter != SHOW_ID3DB && (browse->flags & BROWSE_DIRFILTER) == 0) 1181 tc.dirfilter = &global_settings.dirfilter; 1182 tc.browse = browse; 1183 set_current_file(browse->root); 1184 if (browse->flags&BROWSE_RUNFILE) 1185 ret_val = ft_enter(&tc); 1186 else 1187 ret_val = dirbrowse(); 1188 } 1189 } 1190 1191 tc.is_browsing = false; 1192 tc.dirfilter = prev_dirfilter; /* Bugfix restore dirfilter*/ 1193 1194 backup_count--; 1195 if (backup_count >= 0) 1196 tc = backups[backup_count]; 1197 1198 return ret_val; 1199} 1200 1201int rockbox_browse_at(const char* path) 1202{ 1203 struct browse_context browse = { 1204 .dirfilter = SHOW_SUPPORTED, 1205 .icon = Icon_NOICON, 1206 .root = path, 1207 .disable_gui = true, 1208 }; 1209 strcpy(tc.currdir, path); 1210 1211 return rockbox_browse(&browse); 1212} 1213 1214static int move_callback(int handle, void* current, void* new) 1215{ 1216 struct tree_cache* cache = &tc.cache; 1217 ptrdiff_t diff = new - current; 1218 /* FIX_PTR makes sure to not accidentally update static allocations */ 1219#define FIX_PTR(x) \ 1220 { if ((void*)x >= current && (void*)x < (current+cache->name_buffer_size)) x+= diff; } 1221 1222 if (handle == cache->name_buffer_handle) 1223 { /* update entry structs, *even if they are struct tagentry */ 1224 struct entry *this = core_get_data(cache->entries_handle); 1225 struct entry *last = this + cache->max_entries; 1226 for(; this < last; this++) 1227 FIX_PTR(this->name); 1228 } 1229 /* nothing to do if entries moved */ 1230 return BUFLIB_CB_OK; 1231} 1232 1233static struct buflib_callbacks ops = { 1234 .move_callback = move_callback, 1235 .shrink_callback = NULL, 1236}; 1237 1238void tree_mem_init(void) 1239{ 1240 /* initialize tree context struct */ 1241 struct tree_cache* cache = &tc.cache; 1242 memset(&tc, 0, sizeof(tc)); 1243 tc.dirfilter = &global_settings.dirfilter; 1244 tc.sort_dir = global_settings.sort_dir; 1245 1246 cache->name_buffer_size = AVERAGE_FILENAME_LENGTH * 1247 global_settings.max_files_in_dir; 1248 cache->name_buffer_handle = core_alloc_ex(cache->name_buffer_size, &ops); 1249 1250 cache->max_entries = global_settings.max_files_in_dir; 1251 cache->entries_handle = 1252 core_alloc_ex(cache->max_entries*(sizeof(struct entry)), &ops); 1253} 1254 1255bool bookmark_play(char *resume_file, int index, unsigned long elapsed, 1256 unsigned long offset, int seed, char *filename) 1257{ 1258 int i; 1259 char* suffix = strrchr(resume_file, '.'); 1260 bool started = false; 1261 1262 if (suffix != NULL && !strncasecmp(suffix, ".m3u", sizeof(".m3u") - 1)) /* gets m3u8 too */ 1263 { 1264 /* Playlist playback */ 1265 char* slash; 1266 /* check that the file exists */ 1267 if (!file_exists(resume_file)) 1268 return false; 1269 1270 slash = strrchr(resume_file,'/'); 1271 if (slash) 1272 { 1273 char* cp; 1274 *slash=0; 1275 1276 cp=resume_file; 1277 if (!cp[0]) 1278 cp="/"; 1279 1280 if (playlist_create(cp, slash+1) != -1) 1281 { 1282 if (global_settings.playlist_shuffle) 1283 playlist_shuffle(seed, -1); 1284 started = true; 1285 } 1286 *slash='/'; 1287 } 1288 } 1289 else 1290 { 1291 /* Directory playback */ 1292 lastdir[0]='\0'; 1293 if (playlist_create(resume_file, NULL) != -1) 1294 { 1295 char filename_buf[MAX_PATH + 1]; 1296 const char* peek_filename; 1297 resume_directory(resume_file); 1298 if (global_settings.playlist_shuffle) 1299 playlist_shuffle(seed, -1); 1300 1301 /* Check if the file is at the same spot in the directory, 1302 else search for it */ 1303 int amt = playlist_amount(); 1304 for ( i=0; i < amt; i++ ) 1305 { 1306 int modidx = (i + index) % amt; 1307 peek_filename = playlist_peek(modidx, filename_buf, 1308 sizeof(filename_buf)); 1309 1310 if (peek_filename == NULL) 1311 { 1312 if (index == 0) /* searched every entry didn't find a match */ 1313 return false; 1314 /* playlist has shrunk, search from the top */ 1315 i = 0; 1316 amt = index; 1317 index = 0; 1318 } 1319 else if (!strcmp(strrchr(peek_filename, '/') + 1, filename)) 1320 { 1321 started = true; 1322 index = modidx; 1323 break; 1324 } 1325 } 1326 } 1327 } 1328 1329 if (started) 1330 { 1331 playlist_start(index, elapsed, offset); 1332 start_wps = true; 1333 } 1334 return started; 1335} 1336 1337static void say_filetype(int attr) 1338{ 1339 talk_id(tree_get_filetype_voiceclip(attr), true); 1340} 1341 1342static int ft_play_dirname(char* name) 1343{ 1344#ifdef HAVE_MULTIVOLUME 1345 int vol = path_get_volume_id(name); 1346 if (talk_volume_id(vol)) 1347 return 1; 1348#endif 1349 1350 return talk_file(tc.currdir, name, dir_thumbnail_name, NULL, 1351 global_settings.talk_filetype ? 1352 TALK_IDARRAY(VOICE_DIR) : NULL, 1353 false); 1354} 1355 1356static int ft_play_filename(char *dir, char *file, int attr) 1357{ 1358 if (strlen(file) >= strlen(file_thumbnail_ext) 1359 && strcasecmp(&file[strlen(file) - strlen(file_thumbnail_ext)], 1360 file_thumbnail_ext)) 1361 /* file has no .talk extension */ 1362 return talk_file(dir, NULL, file, file_thumbnail_ext, 1363 TALK_IDARRAY(tree_get_filetype_voiceclip(attr)), false); 1364 1365 /* it already is a .talk file, play this directly, but prefix it. */ 1366 return talk_file(dir, NULL, file, NULL, 1367 TALK_IDARRAY(LANG_VOICE_DIR_HOVER), false); 1368} 1369 1370/* These two functions are called by the USB and shutdown handlers */ 1371void tree_flush(void) 1372{ 1373 tc.is_browsing = false;/* clear browse to prevent reentry to a possibly missing file */ 1374#ifdef HAVE_TAGCACHE 1375 tagcache_shutdown(); 1376#endif 1377 1378#ifdef HAVE_TC_RAMCACHE 1379 tagcache_unload_ramcache(); 1380#endif 1381 1382#ifdef HAVE_DIRCACHE 1383 int old_val = global_status.dircache_size; 1384#ifdef HAVE_EEPROM_SETTINGS 1385 bool savecache = false; 1386#endif 1387 1388 if (global_settings.dircache) 1389 { 1390 dircache_suspend(); 1391 1392 struct dircache_info info; 1393 dircache_get_info(&info); 1394 1395 global_status.dircache_size = info.last_size; 1396 #ifdef HAVE_EEPROM_SETTINGS 1397 savecache = firmware_settings.initialized; 1398 #endif 1399 } 1400 else 1401 { 1402 global_status.dircache_size = 0; 1403 } 1404 1405 if (old_val != global_status.dircache_size) 1406 status_save(true); 1407 1408 #ifdef HAVE_EEPROM_SETTINGS 1409 if (savecache) 1410 dircache_save(); 1411 #endif 1412#endif /* HAVE_DIRCACHE */ 1413} 1414 1415void tree_restore(void) 1416{ 1417#ifdef HAVE_EEPROM_SETTINGS 1418 firmware_settings.disk_clean = false; 1419#endif 1420 1421#ifdef HAVE_TC_RAMCACHE 1422 tagcache_remove_statefile(); 1423#endif 1424 1425#ifdef HAVE_DIRCACHE 1426 if (global_settings.dircache && dircache_resume() > 0) 1427 { 1428 /* Print "Scanning disk..." to the display. */ 1429 splash(0, str(LANG_SCANNING_DISK)); 1430 dircache_wait(); 1431 } 1432#endif 1433 1434#ifdef HAVE_TAGCACHE 1435 tagcache_start_scan(); 1436#endif 1437}