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) 2007 Jonathan Gordon
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 "config.h"
26#include "appevents.h"
27#include "menu.h"
28#include "root_menu.h"
29#include "lang.h"
30#include "settings.h"
31#include "screens.h"
32#include "kernel.h"
33#include "debug.h"
34#include "misc.h"
35#include "open_plugin.h"
36#include "rolo.h"
37#include "powermgmt.h"
38#include "power.h"
39#include "talk.h"
40#include "audio.h"
41#include "shortcuts.h"
42
43#ifdef HAVE_HOTSWAP
44#include "storage.h"
45#include "mv.h"
46#endif
47/* gui api */
48#include "list.h"
49#include "splash.h"
50#include "action.h"
51#include "yesno.h"
52#include "viewport.h"
53
54#include "tree.h"
55#if CONFIG_TUNER
56#include "radio.h"
57#endif
58#ifdef HAVE_RECORDING
59#include "recording.h"
60#endif
61#include "wps.h"
62#include "bookmark.h"
63#include "playlist.h"
64#include "playlist_viewer.h"
65#include "playlist_catalog.h"
66#include "menus/exported_menus.h"
67#ifdef HAVE_RTC_ALARM
68#include "rtc.h"
69#endif
70#ifdef HAVE_TAGCACHE
71#include "tagcache.h"
72#endif
73#include "language.h"
74#include "plugin.h"
75#include "disk.h"
76
77struct root_items {
78 int (*function)(void* param);
79 void* param;
80 const struct menu_item_ex *context_menu;
81};
82static int next_screen = GO_TO_ROOT; /* holding info about the upcoming screen
83 * which is the current screen for the
84 * rest of the code after load_screen
85 * is called */
86static int last_screen = GO_TO_ROOT; /* unfortunatly needed so we can resume
87 or goto current track based on previous
88 screen */
89
90static int previous_music = GO_TO_WPS; /* Toggles behavior of the return-to
91 * playback-button depending
92 * on FM radio */
93
94#if (CONFIG_TUNER)
95static void rootmenu_start_playback_callback(unsigned short id, void *param)
96{
97 (void) id; (void) param;
98 /* Cancel FM radio selection as previous music. For cases where we start
99 playback without going to the WPS, such as playlist insert or
100 playlist catalog. */
101 previous_music = GO_TO_WPS;
102}
103#endif
104
105static char current_track_path[MAX_PATH];
106static void rootmenu_track_changed_callback(unsigned short id, void* param)
107{
108 (void)id;
109 struct mp3entry *id3 = ((struct track_event *)param)->id3;
110 strmemccpy(current_track_path, id3->path, MAX_PATH);
111}
112static int browser(void* param)
113{
114 int ret_val;
115#ifdef HAVE_TAGCACHE
116 struct tree_context* tc = tree_get_context();
117#endif
118 int filter = SHOW_SUPPORTED;
119 char folder[MAX_PATH] = "/";
120 /* stuff needed to remember position in file browser */
121 static char last_folder[MAX_PATH] = "/";
122 /* and stuff for the database browser */
123#ifdef HAVE_TAGCACHE
124 static int last_db_dirlevel = 0, last_db_selection = 0, last_ft_dirlevel = 0;
125#endif
126
127 switch ((intptr_t)param)
128 {
129 case GO_TO_FILEBROWSER:
130 filter = global_settings.dirfilter;
131 if (global_settings.browse_current &&
132 last_screen == GO_TO_WPS &&
133 current_track_path[0])
134 {
135 strcpy(folder, current_track_path);
136 }
137 else if (!strcmp(last_folder, "/"))
138 {
139 strcpy(folder, global_settings.start_directory);
140 }
141 else
142 {
143#ifdef HAVE_HOTSWAP
144 bool in_hotswap = false;
145 /* handle entering an ejected drive */
146 int i;
147 for (i = 0; i < NUM_VOLUMES; i++)
148 {
149 char vol_string[VOL_MAX_LEN + 1];
150 if (!volume_removable(i))
151 continue;
152 get_volume_name(i, vol_string);
153 /* test whether we would browse the external card */
154 if (!volume_present(i) &&
155 (strstr(last_folder, vol_string)
156#ifdef HAVE_HOTSWAP_STORAGE_AS_MAIN
157 || (i == 0)
158#endif
159 ))
160 { /* leave folder as "/" to avoid crash when trying
161 * to access an ejected drive */
162 strcpy(folder, "/");
163 in_hotswap = true;
164 break;
165 }
166 }
167 if (!in_hotswap)
168#endif /*HAVE_HOTSWAP*/
169 strcpy(folder, last_folder);
170 }
171 push_current_activity(ACTIVITY_FILEBROWSER);
172 break;
173#ifdef HAVE_TAGCACHE
174 case GO_TO_DBBROWSER:
175 if (!tagcache_is_usable())
176 {
177 bool reinit_attempted = false;
178
179 /* Now display progress until it's ready or the user exits */
180 while(!tagcache_is_usable())
181 {
182 struct tagcache_stat *stat = tagcache_get_stat();
183
184 /* Allow user to exit */
185 if (action_userabort(HZ/2))
186 break;
187
188 /* Maybe just needs to reboot due to delayed commit */
189 if (stat->commit_delayed)
190 {
191 splash(HZ*2, ID2P(LANG_PLEASE_REBOOT));
192 break;
193 }
194
195 /* Check if ready status is known */
196 if (!stat->readyvalid)
197 {
198 splash(0, ID2P(LANG_TAGCACHE_BUSY));
199 continue;
200 }
201
202 /* Re-init if required */
203 if (!reinit_attempted && !stat->ready &&
204 stat->processed_entries == 0 && stat->commit_step == 0)
205 {
206 /* Prompt the user */
207 reinit_attempted = true;
208 static const char *lines[]={
209 ID2P(LANG_TAGCACHE_BUSY), ID2P(LANG_TAGCACHE_FORCE_UPDATE)};
210 static const struct text_message message={lines, 2};
211 if(gui_syncyesno_run(&message, NULL, NULL) == YESNO_NO)
212 break;
213 FOR_NB_SCREENS(i)
214 screens[i].clear_display();
215
216 /* Start initialisation */
217 tagcache_rebuild();
218 }
219
220 /* Display building progress */
221 static long talked_tick = 0;
222 if(global_settings.talk_menu &&
223 (talked_tick == 0
224 || TIME_AFTER(current_tick, talked_tick+7*HZ)))
225 {
226 talked_tick = current_tick;
227 if (stat->commit_step > 0)
228 {
229 talk_id(LANG_TAGCACHE_INIT, false);
230 talk_number(stat->commit_step, true);
231 talk_id(VOICE_OF, true);
232 talk_number(tagcache_get_max_commit_step(), true);
233 } else if(stat->processed_entries)
234 {
235 talk_number(stat->processed_entries, false);
236 talk_id(LANG_BUILDING_DATABASE, true);
237 }
238 }
239 if (stat->commit_step > 0)
240 {
241 /* (prevent redundant voicing by splash_progress */
242 bool tmp = global_settings.talk_menu;
243 global_settings.talk_menu = false;
244
245 if (lang_is_rtl())
246 {
247 splash_progress(stat->commit_step,
248 tagcache_get_max_commit_step(),
249 "[%d/%d] %s", stat->commit_step,
250 tagcache_get_max_commit_step(),
251 str(LANG_TAGCACHE_INIT));
252 }
253 else
254 {
255 splash_progress(stat->commit_step,
256 tagcache_get_max_commit_step(),
257 "%s [%d/%d]", str(LANG_TAGCACHE_INIT),
258 stat->commit_step,
259 tagcache_get_max_commit_step());
260 }
261 global_settings.talk_menu = tmp;
262 }
263 else
264 {
265 splashf(0, str(LANG_BUILDING_DATABASE),
266 stat->processed_entries); /* (voiced above) */
267 }
268 }
269 }
270 if (!tagcache_is_usable())
271 return GO_TO_PREVIOUS;
272 filter = SHOW_ID3DB;
273 last_ft_dirlevel = tc->dirlevel;
274 tc->dirlevel = last_db_dirlevel;
275 tc->selected_item = last_db_selection;
276 push_current_activity(ACTIVITY_DATABASEBROWSER);
277 break;
278#endif /*HAVE_TAGCACHE*/
279 }
280
281 struct browse_context browse = {
282 .dirfilter = filter,
283 .icon = Icon_NOICON,
284 .root = folder,
285 };
286
287 ret_val = rockbox_browse(&browse);
288
289 if (ret_val == GO_TO_WPS
290 || ret_val == GO_TO_PREVIOUS_MUSIC
291 || ret_val == GO_TO_PLUGIN)
292 pop_current_activity_without_refresh();
293 else
294 pop_current_activity();
295
296 switch ((intptr_t)param)
297 {
298 case GO_TO_FILEBROWSER:
299 if (!get_current_file(last_folder, MAX_PATH) ||
300 (!strchr(&last_folder[1], '/') &&
301 global_settings.start_directory[1] != '\0'))
302 {
303 last_folder[0] = '/';
304 last_folder[1] = '\0';
305 }
306 break;
307#ifdef HAVE_TAGCACHE
308 case GO_TO_DBBROWSER:
309 last_db_dirlevel = tc->dirlevel;
310 last_db_selection = tc->selected_item;
311 tc->dirlevel = last_ft_dirlevel;
312 break;
313#endif
314 }
315 return ret_val;
316}
317
318#ifdef HAVE_RECORDING
319static int recscrn(void* param)
320{
321 (void)param;
322 recording_screen(false);
323 return GO_TO_ROOT;
324}
325#endif
326static int wpsscrn(void* param)
327{
328 int ret_val = GO_TO_PREVIOUS;
329 int audstatus = audio_status();
330 (void)param;
331 push_current_activity(ACTIVITY_WPS);
332
333#ifdef HAVE_PITCHCONTROL
334 if (!audstatus)
335 {
336 sound_set_pitch(global_status.resume_pitch);
337 dsp_set_timestretch(global_status.resume_speed);
338 }
339#endif
340
341 if (audstatus)
342 {
343 talk_shutup();
344 ret_val = gui_wps_show();
345 }
346 else if (global_status.resume_index != -1)
347 {
348 DEBUGF("Resume index %d crc32 %lX offset %lX\n",
349 global_status.resume_index,
350 (unsigned long)global_status.resume_crc32,
351 (unsigned long)global_status.resume_offset);
352 if (playlist_resume() != -1)
353 {
354 playlist_resume_track(global_status.resume_index,
355 global_status.resume_crc32,
356 global_status.resume_elapsed,
357 global_status.resume_offset);
358 ret_val = gui_wps_show();
359 }
360 }
361 else if (!file_exists(PLAYLIST_CONTROL_FILE))
362 splash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME));
363 else if (yesno_pop(ID2P(LANG_REPLAY_FINISHED_PLAYLIST)) &&
364 playlist_resume() != -1)
365 {
366 playlist_start(0, 0, 0);
367 ret_val = gui_wps_show();
368 }
369
370 if (ret_val == GO_TO_PLAYLIST_VIEWER
371 || ret_val == GO_TO_PLUGIN
372 || ret_val == GO_TO_WPS
373 || ret_val == GO_TO_PREVIOUS_MUSIC
374 || ret_val == GO_TO_PREVIOUS_BROWSER
375 || (ret_val == GO_TO_PREVIOUS
376 && (last_screen == GO_TO_MAINMENU /* Settings */
377 || last_screen == GO_TO_BROWSEPLUGINS
378 || last_screen == GO_TO_SYSTEM_SCREEN
379 || last_screen == GO_TO_PLAYLISTS_SCREEN)))
380 {
381 pop_current_activity_without_refresh();
382 }
383 else
384 pop_current_activity();
385
386 return ret_val;
387}
388#if CONFIG_TUNER
389static int radio(void* param)
390{
391 (void)param;
392 radio_screen();
393 return GO_TO_ROOT;
394}
395#endif
396
397static int miscscrn(void * param)
398{
399 const struct menu_item_ex *menu = (const struct menu_item_ex*)param;
400 int result = do_menu(menu, NULL, NULL, false);
401 switch (result)
402 {
403 case GO_TO_PLUGIN:
404 case GO_TO_PLAYLIST_VIEWER:
405 case GO_TO_WPS:
406 case GO_TO_PREVIOUS_MUSIC:
407 return result;
408 default:
409 return GO_TO_ROOT;
410 }
411}
412
413
414static int playlist_view_catalog(void * param)
415{
416 (void)param;
417 push_current_activity(ACTIVITY_PLAYLISTBROWSER);
418 bool item_was_selected = catalog_view_playlists();
419
420 if (item_was_selected)
421 {
422 pop_current_activity_without_refresh();
423 return GO_TO_WPS;
424 }
425 pop_current_activity();
426 return GO_TO_ROOT;
427}
428
429static int playlist_view(void * param)
430{
431 (void)param;
432 int val;
433
434 val = playlist_viewer();
435 switch (val)
436 {
437 case PLAYLIST_VIEWER_MAINMENU:
438 case PLAYLIST_VIEWER_USB:
439 return GO_TO_ROOT;
440 case PLAYLIST_VIEWER_OK:
441 return GO_TO_PREVIOUS;
442 }
443 return GO_TO_PREVIOUS;
444}
445
446static int load_bmarks(void* param)
447{
448 (void)param;
449 if(bookmark_mrb_load())
450 return GO_TO_WPS;
451 return GO_TO_PREVIOUS;
452}
453
454/* These are all static const'd from apps/menus/ *.c
455 so little hack so we can use them */
456extern struct menu_item_ex
457 file_menu,
458#ifdef HAVE_TAGCACHE
459 tagcache_menu,
460#endif
461 main_menu_,
462 manage_settings,
463 plugin_menu,
464 playlist_options,
465 info_menu,
466 system_menu;
467static const struct root_items items[] = {
468 [GO_TO_FILEBROWSER] = { browser, (void*)GO_TO_FILEBROWSER, &file_menu},
469#ifdef HAVE_TAGCACHE
470 [GO_TO_DBBROWSER] = { browser, (void*)GO_TO_DBBROWSER, &tagcache_menu },
471#endif
472 [GO_TO_WPS] = { wpsscrn, NULL, &playback_settings },
473 [GO_TO_MAINMENU] = { miscscrn, (struct menu_item_ex*)&main_menu_,
474 &manage_settings },
475
476#ifdef HAVE_RECORDING
477 [GO_TO_RECSCREEN] = { recscrn, NULL, &recording_settings_menu },
478#endif
479
480#if CONFIG_TUNER
481 [GO_TO_FM] = { radio, NULL, &radio_settings_menu },
482#endif
483
484 [GO_TO_RECENTBMARKS] = { load_bmarks, NULL, &bookmark_settings_menu },
485 [GO_TO_BROWSEPLUGINS] = { miscscrn, &plugin_menu, NULL },
486 [GO_TO_PLAYLISTS_SCREEN] = { playlist_view_catalog, NULL,
487 &playlist_options },
488 [GO_TO_PLAYLIST_VIEWER] = { playlist_view, NULL, &playlist_options },
489 [GO_TO_SYSTEM_SCREEN] = { miscscrn, &info_menu, &system_menu },
490 [GO_TO_SHORTCUTMENU] = { do_shortcut_menu, NULL, NULL },
491
492};
493//static const int nb_items = sizeof(items)/sizeof(*items);
494
495static int item_callback(int action,
496 const struct menu_item_ex *this_item,
497 struct gui_synclist *this_list);
498
499MENUITEM_RETURNVALUE(shortcut_menu, ID2P(LANG_SHORTCUTS), GO_TO_SHORTCUTMENU,
500 NULL, Icon_Bookmark);
501
502MENUITEM_RETURNVALUE(file_browser, ID2P(LANG_DIR_BROWSER), GO_TO_FILEBROWSER,
503 NULL, Icon_file_view_menu);
504#ifdef HAVE_TAGCACHE
505MENUITEM_RETURNVALUE(db_browser, ID2P(LANG_TAGCACHE), GO_TO_DBBROWSER,
506 NULL, Icon_Audio);
507#endif
508MENUITEM_RETURNVALUE(rocks_browser, ID2P(LANG_PLUGINS), GO_TO_BROWSEPLUGINS,
509 NULL, Icon_Plugin);
510
511static char *get_wps_item_name(int selected_item, void * data,
512 char *buffer, size_t buffer_len)
513{
514 (void)selected_item; (void)data; (void)buffer; (void)buffer_len;
515 if (audio_status())
516 return ID2P(LANG_NOW_PLAYING);
517 return ID2P(LANG_RESUME_PLAYBACK);
518}
519MENUITEM_RETURNVALUE_DYNTEXT(wps_item, GO_TO_WPS, NULL, get_wps_item_name,
520 NULL, NULL, Icon_Playback_menu);
521#ifdef HAVE_RECORDING
522MENUITEM_RETURNVALUE(rec, ID2P(LANG_RECORDING), GO_TO_RECSCREEN,
523 NULL, Icon_Recording);
524#endif
525#if CONFIG_TUNER
526MENUITEM_RETURNVALUE(fm, ID2P(LANG_FM_RADIO), GO_TO_FM,
527 item_callback, Icon_Radio_screen);
528#endif
529MENUITEM_RETURNVALUE(menu_, ID2P(LANG_SETTINGS), GO_TO_MAINMENU,
530 NULL, Icon_Submenu_Entered);
531MENUITEM_RETURNVALUE(bookmarks, ID2P(LANG_BOOKMARK_MENU_RECENT_BOOKMARKS),
532 GO_TO_RECENTBMARKS, item_callback,
533 Icon_Bookmark);
534MENUITEM_RETURNVALUE(playlists, ID2P(LANG_PLAYLISTS), GO_TO_PLAYLISTS_SCREEN,
535 NULL, Icon_Playlist);
536MENUITEM_RETURNVALUE(system_menu_, ID2P(LANG_SYSTEM), GO_TO_SYSTEM_SCREEN,
537 NULL, Icon_System_menu);
538
539struct menu_item_ex root_menu_;
540static struct menu_callback_with_desc root_menu_desc = {
541 item_callback, ID2P(LANG_ROCKBOX_TITLE), Icon_Rockbox };
542
543static struct menu_table menu_table[] = {
544 /* Order here represents the default ordering */
545 { "bookmarks", &bookmarks },
546 { "files", &file_browser },
547#ifdef HAVE_TAGCACHE
548 { "database", &db_browser },
549#endif
550 { "wps", &wps_item },
551 { "settings", &menu_ },
552#ifdef HAVE_RECORDING
553 { "recording", &rec },
554#endif
555#if CONFIG_TUNER
556 { "radio", &fm },
557#endif
558 { "playlists", &playlists },
559 { "plugins", &rocks_browser },
560 { "system_menu", &system_menu_ },
561 { "shortcuts", &shortcut_menu },
562};
563#define MAX_MENU_ITEMS (sizeof(menu_table) / sizeof(struct menu_table))
564static struct menu_item_ex *root_menu__[MAX_MENU_ITEMS];
565
566struct menu_table *root_menu_get_options(int *nb_options)
567{
568 *nb_options = MAX_MENU_ITEMS;
569
570 return menu_table;
571}
572
573void root_menu_load_from_cfg(void* setting, char *value)
574{
575 char *next = value, *start, *end;
576 unsigned int menu_item_count = 0, i;
577 bool main_menu_added = false;
578
579 if (*value == '-')
580 {
581 root_menu_set_default(setting, NULL);
582 return;
583 }
584 root_menu_.flags = MENU_HAS_DESC | MT_MENU;
585 root_menu_.submenus = (const struct menu_item_ex **)&root_menu__;
586 root_menu_.callback_and_desc = &root_menu_desc;
587
588 while (next && menu_item_count < MAX_MENU_ITEMS)
589 {
590 start = next;
591 next = strchr(next, ',');
592 if (next)
593 {
594 *next = '\0';
595 next++;
596 }
597 start = skip_whitespace(start);
598 if ((end = strchr(start, ' ')))
599 *end = '\0';
600 for (i=0; i<MAX_MENU_ITEMS; i++)
601 {
602 if (*start && !strcmp(start, menu_table[i].string))
603 {
604 root_menu__[menu_item_count++] = (struct menu_item_ex *)menu_table[i].item;
605 if (menu_table[i].item == &menu_)
606 main_menu_added = true;
607 break;
608 }
609 }
610 }
611 if (!main_menu_added)
612 root_menu__[menu_item_count++] = (struct menu_item_ex *)&menu_;
613 root_menu_.flags |= MENU_ITEM_COUNT(menu_item_count);
614 *(bool*)setting = true;
615}
616
617char* root_menu_write_to_cfg(void* setting, char*buf, int buf_len)
618{
619 (void)setting;
620 unsigned i, written, j;
621 for (i = 0; i < MENU_GET_COUNT(root_menu_.flags); i++)
622 {
623 for (j=0; j<MAX_MENU_ITEMS; j++)
624 {
625 if (menu_table[j].item == root_menu__[i])
626 {
627 written = snprintf(buf, buf_len, "%s, ", menu_table[j].string);
628 buf_len -= written;
629 buf += written;
630 break;
631 }
632 }
633 }
634 return buf;
635}
636
637void root_menu_set_default(void* setting, void* defaultval)
638{
639 unsigned i;
640 (void)defaultval;
641
642 root_menu_.flags = MENU_HAS_DESC | MT_MENU;
643 root_menu_.submenus = (const struct menu_item_ex **)&root_menu__;
644 root_menu_.callback_and_desc = &root_menu_desc;
645
646 for (i=0; i<MAX_MENU_ITEMS; i++)
647 {
648 root_menu__[i] = (struct menu_item_ex *)menu_table[i].item;
649 }
650 root_menu_.flags |= MENU_ITEM_COUNT(MAX_MENU_ITEMS);
651 *(bool*)setting = false;
652}
653
654bool root_menu_is_changed(void* setting, void* defaultval)
655{
656 (void)defaultval;
657 return *(bool*)setting;
658}
659
660static int item_callback(int action,
661 const struct menu_item_ex *this_item,
662 struct gui_synclist *this_list)
663{
664 (void)this_list;
665 switch (action)
666 {
667 case ACTION_TREE_STOP:
668 return ACTION_REDRAW;
669 case ACTION_REQUEST_MENUITEM:
670#if CONFIG_TUNER
671 if (this_item == &fm)
672 {
673 if (radio_hardware_present() == 0)
674 return ACTION_EXIT_MENUITEM;
675 }
676 else
677#endif
678 if (this_item == &bookmarks)
679 {
680 if (global_settings.usemrb == 0)
681 return ACTION_EXIT_MENUITEM;
682 }
683 break;
684 }
685 return action;
686}
687
688static int get_selection(int last_screen)
689{
690 int i;
691 int len = ARRAYLEN(root_menu__);
692 for(i=0; i < len; i++)
693 {
694 if (((root_menu__[i]->flags&MENU_TYPE_MASK) == MT_RETURN_VALUE) &&
695 (root_menu__[i]->value == last_screen))
696 {
697 return i;
698 }
699 }
700 return 0;
701}
702
703static inline int load_screen(int screen)
704{
705 /* set the global_status.last_screen before entering,
706 if we dont we will always return to the wrong screen on boot */
707 int old_previous = last_screen;
708 int ret_val;
709 enum current_activity activity = ACTIVITY_UNKNOWN;
710 if (screen <= GO_TO_ROOT)
711 return screen;
712 if (screen == old_previous)
713 old_previous = GO_TO_ROOT;
714 global_status.last_screen = (char)screen;
715 status_save(false);
716
717 if (screen == GO_TO_BROWSEPLUGINS)
718 activity = ACTIVITY_PLUGINBROWSER;
719 else if (screen == GO_TO_MAINMENU)
720 activity = ACTIVITY_SETTINGS;
721 else if (screen == GO_TO_SYSTEM_SCREEN)
722 activity = ACTIVITY_SYSTEMSCREEN;
723
724 if (activity != ACTIVITY_UNKNOWN)
725 push_current_activity(activity);
726
727 ret_val = items[screen].function(items[screen].param);
728
729 if (activity != ACTIVITY_UNKNOWN)
730 {
731 if (ret_val == GO_TO_PLUGIN
732 || ret_val == GO_TO_WPS
733 || ret_val == GO_TO_PREVIOUS_MUSIC
734 || ret_val == GO_TO_PREVIOUS_BROWSER
735 || ret_val == GO_TO_FILEBROWSER)
736 {
737 pop_current_activity_without_refresh();
738 }
739 else
740 pop_current_activity();
741 }
742
743 last_screen = screen;
744 if (ret_val == GO_TO_PREVIOUS)
745 last_screen = old_previous;
746 return ret_val;
747}
748
749static int load_context_screen(int selection)
750{
751 const struct menu_item_ex *context_menu = NULL;
752 int retval = GO_TO_PREVIOUS;
753 push_current_activity(ACTIVITY_CONTEXTMENU);
754 if ((root_menu__[selection]->flags&MENU_TYPE_MASK) == MT_RETURN_VALUE)
755 {
756 int item = root_menu__[selection]->value;
757 context_menu = items[item].context_menu;
758 }
759 /* special cases */
760 else if (root_menu__[selection] == &info_menu)
761 {
762 context_menu = &system_menu;
763 }
764
765 if (context_menu)
766 retval = do_menu(context_menu, NULL, NULL, false);
767 pop_current_activity();
768 return retval;
769}
770
771static int load_plugin_screen(char *key)
772{
773 int ret_val = PLUGIN_ERROR;
774 int loops = 100;
775 int old_previous = last_screen;
776 int old_global = global_status.last_screen;
777 last_screen = next_screen;
778 global_status.last_screen = (char)next_screen;
779
780 while(loops-- > 0) /* just to keep things from getting out of hand */
781 {
782 int opret = open_plugin_load_entry(key);
783 struct open_plugin_entry_t *op_entry = open_plugin_get_entry();
784 char *path = op_entry->path;
785 char *param = op_entry->param;
786 if (param[0] == '\0')
787 param = NULL;
788 if (path[0] == '\0' && key)
789 path = P2STR((unsigned char *)key);
790 int ret = plugin_load(path, param);
791
792 if (ret == PLUGIN_USB_CONNECTED || ret == PLUGIN_ERROR)
793 ret_val = GO_TO_ROOT;
794 else if (ret == PLUGIN_GOTO_WPS)
795 ret_val = GO_TO_WPS;
796 else if (ret == PLUGIN_GOTO_PLUGIN)
797 {
798 if(op_entry->lang_id == LANG_OPEN_PLUGIN)
799 {
800 if (key == (char*)ID2P(LANG_SHORTCUTS))
801 {
802 op_entry->lang_id = LANG_SHORTCUTS;
803 }
804 else /* Bugfix ensure proper key */
805 {
806 key = ID2P(LANG_OPEN_PLUGIN);
807 }
808 }
809 continue;
810 }
811 else
812 {
813 ret_val = GO_TO_PREVIOUS;
814 /* Prevents infinite loop with WPS, Plugins, Previous Screen*/
815 if (ret == PLUGIN_OK && old_global == GO_TO_WPS && !audio_status())
816 ret_val = GO_TO_ROOT;
817 last_screen = (old_previous == next_screen || old_global == GO_TO_ROOT)
818 ? GO_TO_ROOT : old_previous;
819 if (last_screen == GO_TO_ROOT)
820 global_status.last_screen = GO_TO_ROOT;
821 }
822 /* ret_val != GO_TO_PLUGIN */
823
824 if (opret != OPEN_PLUGIN_NEEDS_FLUSHED || last_screen != GO_TO_WPS)
825 {
826 /* Keep the entry in case of GO_TO_PREVIOUS */
827 op_entry->hash = 0; /*remove hash -- prevents flush to disk */
828 op_entry->lang_id = LANG_PREVIOUS_SCREEN;
829 /*open_plugin_add_path(NULL, NULL, NULL);// clear entry */
830 }
831 break;
832 } /*while */
833 return ret_val;
834}
835
836static void ignore_back_button_stub(bool ignore)
837{
838#if (CONFIG_PLATFORM&PLATFORM_ANDROID)
839 /* BACK button to be handled by Android instead of rockbox */
840 android_ignore_back_button(ignore);
841#else
842 (void) ignore;
843#endif
844}
845
846static int root_menu_setup_screens(void)
847{
848 int new_screen = next_screen;
849 if (global_settings.start_in_screen == 0)
850 new_screen = (int)global_status.last_screen;
851 else new_screen = global_settings.start_in_screen - 2;
852 if (new_screen == GO_TO_PLUGIN)
853 {
854 if (global_status.last_screen == GO_TO_SHORTCUTMENU)
855 {
856 /* Can make this any value other than GO_TO_SHORTCUTMENU
857 otherwise it takes over on startup when the user wanted
858 the plugin at key - LANG_START_SCREEN */
859 global_status.last_screen = GO_TO_PLUGIN;
860 }
861 if(global_status.last_screen == GO_TO_SHORTCUTMENU ||
862 global_status.last_screen == GO_TO_PLUGIN)
863 {
864 if (global_settings.start_in_screen == 0)
865 { /* Start in: Previous Screen */
866 last_screen = GO_TO_PREVIOUS;
867 global_status.last_screen = GO_TO_ROOT;
868 /* since the plugin has GO_TO_PLUGIN as origin it
869 will just return GO_TO_PREVIOUS <=> GO_TO_PLUGIN in a loop
870 To allow exit after restart we check for GO_TO_ROOT
871 if so exit to ROOT after the plugin exits */
872 }
873 }
874 }
875#if CONFIG_TUNER
876 add_event(PLAYBACK_EVENT_START_PLAYBACK, rootmenu_start_playback_callback);
877#endif
878 add_event(PLAYBACK_EVENT_TRACK_CHANGE, rootmenu_track_changed_callback);
879#ifdef HAVE_RTC_ALARM
880 int alarm_wake_up_screen = 0;
881 if ( rtc_check_alarm_started(true) )
882 {
883 rtc_enable_alarm(false);
884
885#if (defined(HAVE_RECORDING) || CONFIG_TUNER)
886 alarm_wake_up_screen = global_settings.alarm_wake_up_screen;
887#endif
888 switch (alarm_wake_up_screen)
889 {
890#if CONFIG_TUNER
891 case ALARM_START_FM:
892 new_screen = GO_TO_FM;
893 break;
894#endif
895#ifdef HAVE_RECORDING
896 case ALARM_START_REC:
897 recording_start_automatic = true;
898 new_screen = GO_TO_RECSCREEN;
899 break;
900#endif
901 default:
902 new_screen = GO_TO_WPS;
903 break;
904 } /* switch() */
905 }
906#endif /* HAVE_RTC_ALARM */
907
908#if defined(HAVE_HEADPHONE_DETECTION) || defined(HAVE_LINEOUT_DETECTION)
909 if (new_screen == GO_TO_WPS && global_settings.unplug_autoresume)
910 {
911 new_screen = GO_TO_ROOT;
912#ifdef HAVE_HEADPHONE_DETECTION
913 if (headphones_inserted())
914 new_screen = GO_TO_WPS;
915#endif
916#ifdef HAVE_LINEOUT_DETECTION
917 if (lineout_inserted())
918 new_screen = GO_TO_WPS;
919#endif
920 }
921#endif /*(HAVE_HEADPHONE_DETECTION) || (HAVE_LINEOUT_DETECTION)*/
922 return new_screen;
923}
924
925static int browser_default(void)
926{
927 switch (global_settings.browser_default)
928 {
929#ifdef HAVE_TAGCACHE
930 case BROWSER_DEFAULT_DB:
931 return GO_TO_DBBROWSER;
932#endif
933 case BROWSER_DEFAULT_PL_CAT:
934 return GO_TO_PLAYLISTS_SCREEN;
935 case BROWSER_DEFAULT_FILES:
936 default:
937 return GO_TO_FILEBROWSER;
938 }
939}
940
941void root_menu(void)
942{
943 int previous_browser = browser_default();
944 int selected = 0;
945 int shortcut_origin = GO_TO_ROOT;
946
947 push_current_activity(ACTIVITY_MAINMENU);
948 next_screen = root_menu_setup_screens();
949
950 while (true)
951 {
952 switch (next_screen)
953 {
954 case MENU_ATTACHED_USB:
955 case MENU_SELECTED_EXIT:
956 /* fall through */
957 case GO_TO_ROOT:
958 if (last_screen != GO_TO_ROOT)
959 selected = get_selection(last_screen);
960 global_status.last_screen = GO_TO_ROOT; /* We've returned to ROOT */
961 /* When we are in the main menu we want the hardware BACK
962 * button to be handled by HOST instead of rockbox */
963 ignore_back_button_stub(true);
964
965 next_screen = do_menu(&root_menu_, &selected, NULL, false);
966
967 ignore_back_button_stub(false);
968
969 if (next_screen != GO_TO_PREVIOUS)
970 last_screen = GO_TO_ROOT;
971 break;
972#ifdef HAVE_TAGCACHE
973 case GO_TO_DBBROWSER:
974#endif
975 case GO_TO_FILEBROWSER:
976 case GO_TO_PLAYLISTS_SCREEN:
977 previous_browser = next_screen;
978 goto load_next_screen;
979 break;
980#if CONFIG_TUNER
981 case GO_TO_WPS:
982 case GO_TO_FM:
983 previous_music = next_screen;
984 goto load_next_screen;
985 break;
986#endif /* With !CONFIG_TUNER previous_music is always GO_TO_WPS */
987
988 case GO_TO_PREVIOUS:
989 {
990 next_screen = last_screen;
991 if (last_screen == GO_TO_PLUGIN)/* for WPS */
992 last_screen = GO_TO_PREVIOUS;
993 else if (last_screen == GO_TO_PREVIOUS)
994 next_screen = GO_TO_ROOT;
995 break;
996 }
997
998 case GO_TO_PREVIOUS_BROWSER:
999 next_screen = previous_browser;
1000 break;
1001
1002 case GO_TO_PREVIOUS_MUSIC:
1003 next_screen = previous_music;
1004 break;
1005 case GO_TO_ROOTITEM_CONTEXT:
1006 next_screen = load_context_screen(selected);
1007 break;
1008 case GO_TO_PLUGIN:
1009 {
1010
1011 char *key;
1012 if (global_status.last_screen == GO_TO_SHORTCUTMENU)
1013 {
1014 struct open_plugin_entry_t *op_entry = open_plugin_get_entry();
1015 if (op_entry->lang_id == LANG_OPEN_PLUGIN)
1016 op_entry->lang_id = LANG_SHORTCUTS;
1017 shortcut_origin = last_screen;
1018 key = ID2P(LANG_SHORTCUTS);
1019 }
1020 else
1021 {
1022 switch (last_screen)
1023 {
1024 case GO_TO_ROOT:
1025 key = ID2P(LANG_START_SCREEN);
1026 break;
1027 case GO_TO_WPS:
1028 key = ID2P(LANG_OPEN_PLUGIN_SET_WPS_CONTEXT_PLUGIN);
1029 break;
1030 case GO_TO_SHORTCUTMENU:
1031 key = ID2P(LANG_SHORTCUTS);
1032 break;
1033 case GO_TO_PREVIOUS:
1034 key = ID2P(LANG_PREVIOUS_SCREEN);
1035 break;
1036 default:
1037 key = ID2P(LANG_OPEN_PLUGIN);
1038 break;
1039 }
1040 }
1041
1042
1043 push_activity_without_refresh(ACTIVITY_UNKNOWN); /* prevent plugin_load */
1044 next_screen = load_plugin_screen(key); /* from flashing root */
1045 pop_current_activity_without_refresh(); /* menu activity */
1046
1047 if (next_screen == GO_TO_PREVIOUS)
1048 {
1049 /* shortcuts may take several trips through the GO_TO_PLUGIN
1050 case make sure we preserve and restore the origin */
1051 if(tree_get_context()->out_of_tree > 0) /* a shortcut has been selected */
1052 {
1053 next_screen = GO_TO_FILEBROWSER;
1054 shortcut_origin = GO_TO_ROOT;
1055 /* note in some cases there is a screen to return to
1056 but the history is rewritten as if you browsed here
1057 from the root so return there when finished */
1058 }
1059 else if (shortcut_origin != GO_TO_ROOT)
1060 {
1061 if (shortcut_origin != GO_TO_WPS)
1062 next_screen = shortcut_origin;
1063 shortcut_origin = GO_TO_ROOT;
1064 }
1065 /* skip GO_TO_PREVIOUS */
1066 if (last_screen == GO_TO_BROWSEPLUGINS)
1067 {
1068 next_screen = last_screen;
1069 last_screen = GO_TO_PLUGIN;
1070 }
1071 }
1072 previous_browser = (next_screen != GO_TO_WPS) ? browser_default() :
1073 GO_TO_PLUGIN;
1074 break;
1075 }
1076 default:
1077 goto load_next_screen;
1078 break;
1079 } /* switch() */
1080 continue;
1081load_next_screen: /* load_screen is inlined */
1082 next_screen = load_screen(next_screen);
1083 }
1084
1085}