A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $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}