A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 905 lines 29 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2005 by Björn 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 <stdlib.h> 22#include <file.h> 23#include <dir.h> 24#include <string.h> 25#include <kernel.h> 26#include <lcd.h> 27#include <debug.h> 28#include <font.h> 29#include <limits.h> 30#include "bookmark.h" 31#include "tree.h" 32#include "core_alloc.h" 33#include "settings.h" 34#include "filetypes.h" 35#include "talk.h" 36#include "playlist.h" 37#include "lang.h" 38#include "language.h" 39#include "screens.h" 40#include "plugin.h" 41#include "rolo.h" 42#include "splash.h" 43#include "cuesheet.h" 44#include "filetree.h" 45#include "misc.h" 46#include "strnatcmp.h" 47#include "keyboard.h" 48 49#ifdef HAVE_MULTIVOLUME 50#include "mv.h" 51#endif 52 53#if CONFIG_TUNER 54#include "radio.h" 55#endif 56#include "wps.h" 57 58static struct compare_data 59{ 60 int sort_dir; /* qsort key for sorting directories */ 61 int sort_file; /* ...for sorting files */ 62 int(*_compar)(const char*, const char*, size_t); 63} cmp_data; 64 65/* dummmy functions to allow compatibility with strncmp & strncasecmp */ 66static int strnatcmp_n(const char *a, const char *b, size_t n) 67{ 68 (void)n; 69 return strnatcmp(a, b); 70} 71static int strnatcasecmp_n(const char *a, const char *b, size_t n) 72{ 73 (void)n; 74 return strnatcasecmp(a, b); 75} 76 77int ft_build_playlist(struct tree_context* c, int start_index) 78{ 79 int i; 80 int start=start_index; 81 int res; 82 struct playlist_info *playlist = playlist_get_current(); 83 84 tree_lock_cache(c); 85 struct entry *entries = tree_get_entries(c); 86 bool exceeds_pl = false; 87 if (c->filesindir > playlist->max_playlist_size) 88 { 89 exceeds_pl = true; 90 start_index = 0; 91 } 92 struct playlist_insert_context pl_context; 93 94 res = playlist_insert_context_create(playlist, &pl_context, 95 PLAYLIST_REPLACE, false, false); 96 if (res >= 0) 97 { 98 cpu_boost(true); 99 for(i = 0;i < c->filesindir;i++) 100 { 101 int item = i; 102 if (exceeds_pl) 103 item = (i + start) % c->filesindir; 104#if 0 /*only needed if displaying progress */ 105 /* user abort */ 106 if (action_userabort(TIMEOUT_NOBLOCK)) 107 { 108 break; 109 } 110#endif 111 if((entries[item].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) 112 { 113 res = playlist_insert_context_add(&pl_context, entries[item].name); 114 if (res < 0) 115 break; 116 } 117 else if (!exceeds_pl) 118 { 119 /* Adjust the start index when se skip non-MP3 entries */ 120 if(i < start) 121 start_index--; 122 } 123 } 124 cpu_boost(false); 125 } 126 127 playlist_insert_context_release(&pl_context); 128 129 tree_unlock_cache(c); 130 return start_index; 131} 132 133/* Start playback of a playlist, checking for bookmark autoload, modified 134 * playlists, etc., as required. Returns false if playback wasn't started, 135 * or started via bookmark autoload, true otherwise. 136 * 137 * Pointers to both the full pathname and the separated parts needed to 138 * avoid allocating yet another path buffer on the stack (and save some 139 * code; the caller typically needs to create the full pathname anyway)... 140 */ 141bool ft_play_playlist(char* pathname, char* dirname, char* filename) 142{ 143 if (global_settings.party_mode && audio_status()) 144 { 145 splash(HZ, ID2P(LANG_PARTY_MODE)); 146 return false; 147 } 148 149 int res = bookmark_autoload(pathname); 150 if (res == BOOKMARK_CANCEL || res == BOOKMARK_DO_RESUME || !warn_on_pl_erase()) 151 return false; 152 153 splash(0, ID2P(LANG_WAIT)); 154 155 if (playlist_create(dirname, filename) != -1) 156 { 157 if (global_settings.playlist_shuffle) 158 playlist_shuffle(current_tick, -1); 159 160 playlist_start(0, 0, 0); 161 return true; 162 } 163 164 return false; 165} 166 167/* walk a directory and check all entries if a .talk file exists */ 168static void check_file_thumbnails(struct tree_context* c) 169{ 170 int i; 171 struct dirent *entry; 172 struct entry* entries; 173 DIR *dir; 174 175 dir = opendir(c->currdir); 176 if(!dir) 177 return; 178 /* mark all files as non talking, except the .talk ones */ 179 tree_lock_cache(c); 180 entries = tree_get_entries(c); 181 182 for (i=0; i < c->filesindir; i++) 183 { 184 if (entries[i].attr & ATTR_DIRECTORY) 185 continue; /* we're not touching directories */ 186 187 if (strcasecmp(file_thumbnail_ext, 188 &entries[i].name[strlen(entries[i].name) 189 - strlen(file_thumbnail_ext)])) 190 { /* no .talk file */ 191 entries[i].attr &= ~FILE_ATTR_THUMBNAIL; /* clear */ 192 } 193 else 194 { /* .talk file, we later let them speak themselves */ 195 entries[i].attr |= FILE_ATTR_THUMBNAIL; /* set */ 196 } 197 } 198 199 while((entry = readdir(dir)) != 0) /* walk directory */ 200 { 201 int ext_pos; 202 struct dirinfo info = dir_get_info(dir, entry); 203 ext_pos = strlen((char *)entry->d_name) - strlen(file_thumbnail_ext); 204 if (ext_pos <= 0 /* too short to carry ".talk" */ 205 || (info.attribute & ATTR_DIRECTORY) /* no file */ 206 || strcasecmp((char *)&entry->d_name[ext_pos], file_thumbnail_ext)) 207 { /* or doesn't end with ".talk", no candidate */ 208 continue; 209 } 210 211 /* terminate the (disposable) name in dir buffer, 212 this truncates off the ".talk" without needing an extra buffer */ 213 entry->d_name[ext_pos] = '\0'; 214 215 /* search corresponding file in dir cache */ 216 for (i=0; i < c->filesindir; i++) 217 { 218 if (!strcasecmp(entries[i].name, (char *)entry->d_name)) 219 { /* match */ 220 entries[i].attr |= FILE_ATTR_THUMBNAIL; /* set the flag */ 221 break; /* exit search loop, because we found it */ 222 } 223 } 224 } 225 tree_unlock_cache(c); 226 closedir(dir); 227} 228 229/* support function for qsort() */ 230static int compare(const void* p1, const void* p2) 231{ 232 struct entry* e1 = (struct entry*)p1; 233 struct entry* e2 = (struct entry*)p2; 234 int criteria; 235 236 if (cmp_data.sort_dir == SORT_AS_FILE) 237 { /* treat as two files */ 238 criteria = cmp_data.sort_file; 239 } 240 else if (e1->attr & ATTR_DIRECTORY && e2->attr & ATTR_DIRECTORY) 241 { /* two directories */ 242 criteria = cmp_data.sort_dir; 243 244#ifdef HAVE_MULTIVOLUME 245 if (e1->attr & ATTR_VOLUME || e2->attr & ATTR_VOLUME) 246 { /* a volume identifier is involved */ 247 if (e1->attr & ATTR_VOLUME && e2->attr & ATTR_VOLUME) 248 criteria = SORT_ALPHA; /* two volumes: sort alphabetically */ 249 else /* only one is a volume: volume first */ 250 return (e2->attr & ATTR_VOLUME) - (e1->attr & ATTR_VOLUME); 251 } 252#endif 253 254 } 255 else if (!(e1->attr & ATTR_DIRECTORY) && !(e2->attr & ATTR_DIRECTORY)) 256 { /* two files */ 257 criteria = cmp_data.sort_file; 258 } 259 else /* dir and file, dir goes first */ 260 return (e2->attr & ATTR_DIRECTORY) - (e1->attr & ATTR_DIRECTORY); 261 262 switch(criteria) 263 { 264 case SORT_TYPE: 265 case SORT_TYPE_REVERSED: 266 { 267 int t1 = e1->attr & FILE_ATTR_MASK; 268 int t2 = e2->attr & FILE_ATTR_MASK; 269 270 if (!t1) /* unknown type */ 271 t1 = INT_MAX; /* gets a high number, to sort after known */ 272 if (!t2) /* unknown type */ 273 t2 = INT_MAX; /* gets a high number, to sort after known */ 274 275 if (t1 != t2) /* if different */ 276 return (t1 - t2) * (criteria == SORT_TYPE_REVERSED ? -1 : 1); 277 /* else alphabetical sorting */ 278 return cmp_data._compar(e1->name, e2->name, MAX_PATH); 279 } 280 281 case SORT_DATE: 282 case SORT_DATE_REVERSED: 283 { 284 if (e1->time_write != e2->time_write) 285 return (e1->time_write - e2->time_write) 286 * (criteria == SORT_DATE_REVERSED ? -1 : 1); 287 /* else fall through to alphabetical sorting */ 288 } 289 case SORT_ALPHA: 290 case SORT_ALPHA_REVERSED: 291 { 292 return cmp_data._compar(e1->name, e2->name, MAX_PATH) * 293 (criteria == SORT_ALPHA_REVERSED ? -1 : 1); 294 } 295 296 } 297 return 0; /* never reached */ 298} 299 300/* load and sort directory into the tree's cache. returns NULL on failure. */ 301int ft_load(struct tree_context* c, const char* tempdir) 302{ 303 if (c->out_of_tree > 0) /* something else is loaded */ 304 return 0; 305 306 int files_in_dir = 0; 307 int name_buffer_used = 0; 308 struct dirent *entry; 309 bool (*callback_show_item)(char *, int, struct tree_context *) = NULL; 310 DIR *dir; 311 312 if (!c->is_browsing) 313 c->browse = NULL; 314 315 if (tempdir) 316 dir = opendir(tempdir); 317 else 318 { 319 dir = opendir(c->currdir); 320 callback_show_item = c->browse? c->browse->callback_show_item: NULL; 321 } 322 if(!dir) 323 return -1; /* not a directory */ 324 325 c->dirsindir = 0; 326 c->dirfull = false; 327 328 tree_lock_cache(c); 329 while ((entry = readdir(dir))) { 330 int len; 331 struct dirinfo info; 332 struct entry* dptr = tree_get_entry_at(c, files_in_dir); 333 if (!dptr) 334 { 335 c->dirfull = true; 336 break; 337 } 338 339 info = dir_get_info(dir, entry); 340 len = strlen((char *)entry->d_name); 341 342 /* Skip FAT volume ID */ 343 if (info.attribute & ATTR_VOLUME_ID) { 344 continue; 345 } 346 347 dptr->attr = info.attribute; 348 int dir_attr = (dptr->attr & ATTR_DIRECTORY); 349 /* skip directories . and .. */ 350 if (dir_attr && is_dotdir_name(entry->d_name)) 351 continue; 352 353 /* filter out dotfiles and hidden files */ 354 if (*c->dirfilter != SHOW_ALL && 355 ((entry->d_name[0]=='.') || 356 (info.attribute & ATTR_HIDDEN))) { 357 continue; 358 } 359 360 if (*c->dirfilter == SHOW_PLUGINS && (dptr->attr & ATTR_DIRECTORY) && 361 (dptr->attr & 362 (ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID | ATTR_VOLUME)) != 0) { 363 continue; /* skip non plugin folders */ 364 } 365 366 /* check for known file types */ 367 if ( !(dir_attr) ) 368 dptr->attr |= filetype_get_attr((char *)entry->d_name); 369 370 int file_attr = (dptr->attr & FILE_ATTR_MASK); 371 372#define CHK_FT(show,attr) (*c->dirfilter == (show) && file_attr != (attr)) 373 /* filter out non-visible files */ 374 if ((!(dir_attr) && (CHK_FT(SHOW_PLAYLIST, FILE_ATTR_M3U) || 375 (CHK_FT(SHOW_MUSIC, FILE_ATTR_AUDIO) && file_attr != FILE_ATTR_M3U) || 376 (*c->dirfilter == SHOW_SUPPORTED && !filetype_supported(dptr->attr)))) || 377 CHK_FT(SHOW_WPS, FILE_ATTR_WPS) || 378 CHK_FT(SHOW_FONT, FILE_ATTR_FONT) || 379 CHK_FT(SHOW_SBS, FILE_ATTR_SBS) || 380#if CONFIG_TUNER 381 CHK_FT(SHOW_FMS, FILE_ATTR_FMS) || 382 CHK_FT(SHOW_FMR, FILE_ATTR_FMR) || 383#endif 384#ifdef HAVE_REMOTE_LCD 385 CHK_FT(SHOW_RWPS, FILE_ATTR_RWPS) || 386 CHK_FT(SHOW_RSBS, FILE_ATTR_RSBS) || 387#if CONFIG_TUNER 388 CHK_FT(SHOW_RFMS, FILE_ATTR_RFMS) || 389#endif 390#endif 391 CHK_FT(SHOW_M3U, FILE_ATTR_M3U) || 392 CHK_FT(SHOW_CFG, FILE_ATTR_CFG) || 393 CHK_FT(SHOW_LNG, FILE_ATTR_LNG) || 394 CHK_FT(SHOW_MOD, FILE_ATTR_MOD) || 395 /* show first level directories */ 396 ((!(dir_attr) || c->dirlevel > 0) && 397 CHK_FT(SHOW_PLUGINS, FILE_ATTR_ROCK) && 398 file_attr != FILE_ATTR_LUA && 399 file_attr != FILE_ATTR_OPX) || 400 (callback_show_item && !callback_show_item(entry->d_name, dptr->attr, c))) 401 { 402 continue; 403 } 404#undef CHK_FT 405 406 if (len > c->cache.name_buffer_size - name_buffer_used - 1) { 407 /* Tell the world that we ran out of buffer space */ 408 c->dirfull = true; 409 break; 410 } 411 412 ++files_in_dir; 413 414 dptr->name = core_get_data(c->cache.name_buffer_handle)+name_buffer_used; 415 dptr->time_write = info.mtime; 416 strcpy(dptr->name, (char *)entry->d_name); 417 name_buffer_used += len + 1; 418 419 if (dir_attr) /* count the remaining dirs */ 420 c->dirsindir++; 421 } 422 c->filesindir = files_in_dir; 423 c->dirlength = files_in_dir; 424 closedir(dir); 425 426 /* allow directories to be sorted into file list */ 427 cmp_data.sort_dir = (*c->dirfilter == SHOW_PLUGINS) ? SORT_AS_FILE : c->sort_dir; 428 429 /* playlist catalog uses sorting independent from file browser */ 430 cmp_data.sort_file = (*c->dirfilter == SHOW_M3U) ? 431 global_settings.sort_playlists : global_settings.sort_file; 432 433 if (global_settings.sort_case) 434 { 435 if (global_settings.interpret_numbers == SORT_INTERPRET_AS_NUMBER) 436 cmp_data._compar = strnatcmp_n; 437 else 438 cmp_data._compar = strncmp; 439 } 440 else 441 { 442 if (global_settings.interpret_numbers == SORT_INTERPRET_AS_NUMBER) 443 cmp_data._compar = strnatcasecmp_n; 444 else 445 cmp_data._compar = strncasecmp; 446 } 447 448 qsort(tree_get_entries(c), files_in_dir, sizeof(struct entry), compare); 449 450 /* If thumbnail talking is enabled, make an extra run to mark files with 451 associated thumbnails, so we don't do unsuccessful spinups later. */ 452 if (global_settings.talk_file_clip) 453 check_file_thumbnails(c); /* map .talk to ours */ 454 455 tree_unlock_cache(c); 456 return 0; 457} 458static void ft_load_font(char *file) 459{ 460 int current_font_id; 461 enum screen_type screen = SCREEN_MAIN; 462#if NB_SCREENS > 1 463 MENUITEM_STRINGLIST(menu, ID2P(LANG_CUSTOM_FONT), NULL, 464 ID2P(LANG_MAIN_SCREEN), ID2P(LANG_REMOTE_SCREEN)) 465 switch (do_menu(&menu, NULL, NULL, false)) 466 { 467 case 0: /* main lcd */ 468 screen = SCREEN_MAIN; 469 set_file(file, (char *)global_settings.font_file); 470 break; 471 case 1: /* remote */ 472 screen = SCREEN_REMOTE; 473 set_file(file, (char *)global_settings.remote_font_file); 474 break; 475 } 476#else 477 set_file(file, (char *)global_settings.font_file); 478#endif 479 splash(0, ID2P(LANG_WAIT)); 480 current_font_id = screens[screen].getuifont(); 481 if (current_font_id >= 0) 482 font_unload(current_font_id); 483 screens[screen].setuifont( 484 font_load_ex(file,0,global_settings.glyphs_to_cache)); 485 viewportmanager_theme_changed(THEME_UI_VIEWPORT); 486} 487 488static void ft_apply_skin_file(char *buf, char *file) 489{ 490 splash(0, ID2P(LANG_WAIT)); 491 set_file(buf, file); 492 settings_apply_skins(); 493} 494 495static const char *strip_slash(const char *path, const char *def) 496{ 497 if (path) 498 { 499 while (*path == PATH_SEPCH) 500 path++; /* we don't want this treated as an absolute path */ 501 return path; 502 } 503 return def; 504} 505 506int ft_assemble_path(char *buf, size_t bufsz, const char* currdir, const char* filename) 507{ 508 size_t len; 509 const char *cd = strip_slash(currdir, ""); 510 filename = strip_slash(filename, ""); 511 /* remove slashes and NULL strings to make logic below simpler */ 512 513#ifdef HAVE_MULTIVOLUME 514 /* Multi-volume device drives might be enumerated in root so everything 515 should be an absolute qualified path with <drive>/ prepended */ 516 if (*cd != '\0') /* Not in / */ 517 { 518 if (*cd == VOL_START_TOK) 519 { 520 /* use currdir, here we want the slash as it already contains the <drive> */ 521 len = path_append(buf, currdir, filename, bufsz); 522 } /* buf => /currdir/filename */ 523 else 524 { 525 len = path_append(buf, root_realpath(), cd, bufsz); /* /<drive>/currdir */ 526 if(len < bufsz) 527 len += path_append(buf + len, PA_SEP_HARD, filename, bufsz - len); 528 } /* buf => /<drive>/currdir/filename */ 529 } 530 else /* In / */ 531 { 532 if (*filename == VOL_START_TOK) 533 { 534 len = path_append(buf, PATH_SEPSTR, filename, bufsz); 535 } /* buf => /filename */ 536 else 537 { 538 len = path_append(buf, root_realpath(), filename, bufsz); 539 } /* buf => /<drive>/filename */ 540 } 541#else 542 /* Other devices might need a specific drive/dir prepended but its usually '/' */ 543 if (*cd != '\0') /* Not in / */ 544 { 545 len = path_append(buf, root_realpath(), cd, bufsz);/* /currdir */ 546 if(len < bufsz) 547 len += path_append(buf + len, PA_SEP_HARD, filename, bufsz - len); 548 } /* buf => /currdir/filename */ 549 else /* In / */ 550 { 551 len = path_append(buf, root_realpath(), filename, bufsz); 552 } /* buf => /filename */ 553#endif 554 555 if (len > bufsz) 556 splash(HZ, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); 557 return (int)len; 558} 559 560int ft_enter(struct tree_context* c) 561{ 562 int rc = GO_TO_PREVIOUS; 563 char buf[MAX_PATH]; 564 565 struct entry* file = tree_get_entry_at(c, c->selected_item); 566 if (!file) 567 { 568 splashf(HZ, ID2P(LANG_READ_FAILED), str(LANG_UNKNOWN)); 569 return rc; 570 } 571 572 int file_attr = file->attr; 573 ft_assemble_path(buf, sizeof(buf), c->currdir, file->name); 574 if (file_attr & ATTR_DIRECTORY) { 575 memcpy(c->currdir, buf, sizeof(c->currdir)); 576 if ( c->dirlevel < MAX_DIR_LEVELS ) 577 c->selected_item_history[c->dirlevel] = c->selected_item; 578 c->dirlevel++; 579 c->selected_item=0; 580 } 581 else { 582 int seed = current_tick; 583 bool play = false; 584 int start_index=0; 585 586 switch ( file_attr & FILE_ATTR_MASK ) { 587 case FILE_ATTR_M3U: 588 play = ft_play_playlist(buf, c->currdir, file->name); 589 590 if (play) 591 { 592 start_index = 0; 593 } 594 595 break; 596 597 case FILE_ATTR_AUDIO: 598 { 599 int res = bookmark_autoload(c->currdir); 600 if (res == BOOKMARK_CANCEL || res == BOOKMARK_DO_RESUME) 601 break; 602 603 splash(0, ID2P(LANG_WAIT)); 604 605 /* about to create a new current playlist... 606 allow user to cancel the operation */ 607 if (!warn_on_pl_erase()) 608 break; 609 610 if (global_settings.party_mode && audio_status()) 611 { 612 playlist_insert_track(NULL, buf, 613 PLAYLIST_INSERT_LAST, true, true); 614 splash(HZ, ID2P(LANG_QUEUE_LAST)); 615 } 616 else 617 { 618 /* use the assembled path sans filename */ 619 char * fp = strrchr(buf, PATH_SEPCH); 620 if (fp) 621 *fp = '\0'; 622 if (playlist_create(buf, NULL) != -1) 623 { 624 start_index = ft_build_playlist(c, c->selected_item); 625 if (global_settings.playlist_shuffle) 626 { 627 start_index = playlist_shuffle(seed, start_index); 628 /* when shuffling dir.: play all files 629 even if the file selected by user is 630 not the first one */ 631 if (!global_settings.play_selected) 632 start_index = 0; 633 } 634 playlist_start(start_index, 0, 0); 635 play = true; 636 } 637 } 638 break; 639 } 640#if CONFIG_TUNER 641 /* fmr preset file */ 642 case FILE_ATTR_FMR: 643 radio_load_presets(buf); 644 rc = GO_TO_FM; 645 break; 646 case FILE_ATTR_FMS: 647 ft_apply_skin_file(buf, global_settings.fms_file); 648 break; 649#ifdef HAVE_REMOTE_LCD 650 case FILE_ATTR_RFMS: 651 ft_apply_skin_file(buf, global_settings.rfms_file); 652 break; 653#endif 654#endif 655 case FILE_ATTR_SBS: 656 ft_apply_skin_file(buf, global_settings.sbs_file); 657 break; 658#ifdef HAVE_REMOTE_LCD 659 case FILE_ATTR_RSBS: 660 ft_apply_skin_file(buf, global_settings.rsbs_file); 661 break; 662#endif 663 /* wps config file */ 664 case FILE_ATTR_WPS: 665 ft_apply_skin_file(buf, global_settings.wps_file); 666 break; 667#if defined(HAVE_REMOTE_LCD) && (NB_SCREENS > 1) 668 /* remote-wps config file */ 669 case FILE_ATTR_RWPS: 670 ft_apply_skin_file(buf, global_settings.rwps_file); 671 break; 672#endif 673 case FILE_ATTR_CFG: 674 splash(0, ID2P(LANG_WAIT)); 675 if (!settings_load_config(buf,true)) 676 break; 677 splash(HZ, ID2P(LANG_SETTINGS_LOADED)); 678 break; 679 680 case FILE_ATTR_BMARK: 681 splash(0, ID2P(LANG_WAIT)); 682 bookmark_load(buf, false); 683 rc = GO_TO_FILEBROWSER; 684 break; 685 686 case FILE_ATTR_LNG: 687 splash(0, ID2P(LANG_WAIT)); 688 if (lang_core_load(buf)) 689 { 690 splash(HZ, ID2P(LANG_FAILED)); 691 break; 692 } 693 set_file(buf, (char *)global_settings.lang_file); 694 talk_init(); /* use voice of same language */ 695 viewportmanager_theme_changed(THEME_LANGUAGE); 696 settings_apply_skins(); 697 splash(HZ, ID2P(LANG_LANGUAGE_LOADED)); 698 break; 699 700 case FILE_ATTR_FONT: 701 ft_load_font(buf); 702 break; 703 704 case FILE_ATTR_KBD: 705 splash(0, ID2P(LANG_WAIT)); 706 if (!load_kbd(buf)) 707 splash(HZ, ID2P(LANG_KEYBOARD_LOADED)); 708 set_file(buf, (char *)global_settings.kbd_file); 709 break; 710 711#if defined(HAVE_ROLO) 712 /* firmware file */ 713 case FILE_ATTR_MOD: 714 splash(0, ID2P(LANG_WAIT)); 715 audio_hard_stop(); 716 rolo_load(buf); 717 break; 718#endif 719 case FILE_ATTR_CUE: 720 display_cuesheet_content(buf); 721 break; 722 723 /* plugin file */ 724 case FILE_ATTR_ROCK: 725 { 726 char *plugin = buf, *argument = NULL; 727 if (global_settings.party_mode && audio_status()) { 728 splash(HZ, ID2P(LANG_PARTY_MODE)); 729 break; 730 } 731 732#ifdef PLUGINS_RUN_IN_BROWSER /* Stay in the filetree to run a plugin */ 733 switch (plugin_load(plugin, argument)) 734 { 735 case PLUGIN_GOTO_WPS: 736 play = true; 737 break; 738 case PLUGIN_GOTO_PLUGIN: 739 rc = GO_TO_PLUGIN; 740 break; 741 case PLUGIN_USB_CONNECTED: 742 if(*c->dirfilter > NUM_FILTER_MODES) 743 /* leave sub-browsers after usb, doing 744 otherwise might be confusing to the user */ 745 rc = GO_TO_ROOT; 746 else 747 rc = GO_TO_FILEBROWSER; 748 break; 749 /* 750 case PLUGIN_ERROR: 751 case PLUGIN_OK: 752 */ 753 default: 754 break; 755 } 756#else /* Exit the filetree to run a plugin */ 757 plugin_open(plugin, argument); 758 rc = GO_TO_PLUGIN; 759#endif 760 break; 761 } 762 763 default: 764 { 765 const char* plugin; 766 char plugin_path[MAX_PATH]; 767 const char *argument = buf; 768 if (global_settings.party_mode && audio_status()) { 769 splash(HZ, ID2P(LANG_PARTY_MODE)); 770 break; 771 } 772 773 file = tree_get_entry_at(c, c->selected_item); 774 if (!file) 775 { 776 splashf(HZ, ID2P(LANG_READ_FAILED), str(LANG_UNKNOWN)); 777 return rc; 778 } 779 780 plugin = filetype_get_plugin(file->attr, plugin_path, sizeof(plugin_path)); 781 if (plugin) 782 { 783#ifdef PLUGINS_RUN_IN_BROWSER /* Stay in the filetree to run a plugin */ 784 switch (plugin_load(plugin, argument)) 785 { 786 case PLUGIN_USB_CONNECTED: 787 rc = GO_TO_FILEBROWSER; 788 break; 789 case PLUGIN_GOTO_PLUGIN: 790 rc = GO_TO_PLUGIN; 791 break; 792 case PLUGIN_GOTO_WPS: 793 rc = GO_TO_WPS; 794 break; 795 /* 796 case PLUGIN_OK: 797 case PLUGIN_ERROR: 798 */ 799 default: 800 break; 801 } 802#else /* Exit the filetree to run a plugin */ 803 plugin_open(plugin, argument); 804 rc = GO_TO_PLUGIN; 805#endif 806 } 807 break; 808 } 809 } 810 811 if ( play ) { 812 /* the resume_index must always be the index in the 813 shuffled list in case shuffle is enabled */ 814 global_status.resume_index = start_index; 815 global_status.resume_crc32 = 816 playlist_get_filename_crc32(NULL, start_index); 817 global_status.resume_elapsed = 0; 818 global_status.resume_offset = 0; 819 status_save(false); 820 rc = GO_TO_WPS; 821 } 822 else { 823 if (*c->dirfilter > NUM_FILTER_MODES && 824 *c->dirfilter != SHOW_CFG && 825 *c->dirfilter != SHOW_FONT && 826 *c->dirfilter != SHOW_PLUGINS) 827 { 828 rc = GO_TO_ROOT; 829 } 830 } 831 } 832 833 return rc; 834} 835 836int ft_exit(struct tree_context* c) 837{ 838 extern char lastfile[]; /* from tree.c */ 839 char buf[MAX_PATH]; 840 int rc = 0; 841 bool exit_func = false; 842 int i = strlen(c->currdir); 843 844 /* strip trailing slashes */ 845 while (c->currdir[i-1] == PATH_SEPCH) 846 i--; 847 848 if (i>1) { 849 while (c->currdir[i-1]!=PATH_SEPCH) 850 i--; 851 strcpy(buf,&c->currdir[i]); 852 if (i==1) 853 c->currdir[i]='\0'; 854 else 855 c->currdir[i-1]='\0'; 856 857#ifdef HAVE_MULTIVOLUME /* un-redirect the realpath */ 858 if ((unsigned)c->dirlevel<=2) /* only expect redirect two levels max */ 859 { 860 char *currdir = c->currdir; 861 const char *root = root_realpath(); 862 int len = i-1; 863 /* compare to the root path bail if they don't match except single '/' */ 864 for (; len > 0 && *root != '\0' && *root == *currdir; len--) 865 { 866 root++; 867 currdir++; 868 } 869 if (*root == PATH_SEPCH) /* root may have trailing slash */ 870 root++; 871 if (*root == '\0' && 872 (len == 0 || (len == 1 && *currdir == PATH_SEPCH))) 873 { 874 strcpy(c->currdir, PATH_ROOTSTR); 875 c->dirlevel=1; 876 } 877 } 878#endif 879 880 if (*c->dirfilter > NUM_FILTER_MODES && c->dirlevel < 1) 881 exit_func = true; 882 883 c->dirlevel--; 884 if ( c->dirlevel < MAX_DIR_LEVELS ) 885 c->selected_item=c->selected_item_history[c->dirlevel]; 886 else 887 c->selected_item=0; 888 889 /* if undefined position */ 890 if (c->selected_item == -1) 891 strcpy(lastfile, buf); 892 } 893 else 894 { 895 if (*c->dirfilter > NUM_FILTER_MODES && c->dirlevel < 1) 896 exit_func = true; 897 } 898 899 if (exit_func) 900 rc = 3; 901 902 c->out_of_tree = 0; 903 904 return rc; 905}