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) 2002 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 <errno.h>
22#include <stdio.h>
23#include <string.h>
24#include <stdlib.h>
25#include <stdbool.h>
26
27#include "debug.h"
28#include "lcd.h"
29#include "audio.h"
30#include "menu.h"
31#include "lang.h"
32#include "playlist.h"
33#include "button.h"
34#include "kernel.h"
35#include "keyboard.h"
36#include "mp3data.h"
37#include "metadata.h"
38#include "screens.h"
39#include "tree.h"
40#include "settings.h"
41#include "playlist_viewer.h"
42#include "talk.h"
43#include "onplay.h"
44#include "filetypes.h"
45#include "fileop.h"
46#include "open_plugin.h"
47#include "plugin.h"
48#include "bookmark.h"
49#include "action.h"
50#include "splash.h"
51#include "yesno.h"
52#include "menus/exported_menus.h"
53#include "icons.h"
54#include "sound_menu.h"
55#include "playlist_menu.h"
56#include "playlist_catalog.h"
57#ifdef HAVE_TAGCACHE
58#include "tagtree.h"
59#endif
60#include "cuesheet.h"
61#include "statusbar-skinned.h"
62#include "pitchscreen.h"
63#include "viewport.h"
64#include "pathfuncs.h"
65#include "shortcuts.h"
66#include "misc.h"
67#ifdef HAVE_DISK_STORAGE
68#include "storage.h"
69#endif
70
71static int onplay_result = ONPLAY_OK;
72static bool in_queue_submenu = false;
73
74static bool (*ctx_current_playlist_insert)(int position, bool queue, bool create_new);
75static int (*ctx_add_to_playlist)(const char* playlist, bool new_playlist);
76extern struct menu_item_ex file_menu; /* settings_menu.c */
77
78/* redefine MAKE_MENU so the MENU_EXITAFTERTHISMENU flag can be added easily */
79#define MAKE_ONPLAYMENU( name, str, callback, icon, ... ) \
80 static const struct menu_item_ex *name##_[] = {__VA_ARGS__}; \
81 static const struct menu_callback_with_desc name##__ = {callback,str,icon};\
82 static const struct menu_item_ex name = \
83 {MT_MENU|MENU_HAS_DESC|MENU_EXITAFTERTHISMENU| \
84 MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \
85 { (void*)name##_},{.callback_and_desc = & name##__}};
86
87static struct selected_file
88{
89 char buf[MAX_PATH];
90 const char *path;
91 int attr;
92 int context;
93} selected_file;
94
95static struct clipboard
96{
97 char path[MAX_PATH]; /* Clipped file's path */
98 unsigned int attr; /* Clipped file's attributes */
99 unsigned int flags; /* Operation type flags */
100} clipboard;
101
102/* set selected file (doesn't touch buffer) */
103static void selected_file_set(int context, const char *path, int attr)
104{
105 selected_file.path = path;
106 selected_file.attr = attr;
107 selected_file.context = context;
108}
109
110/* Empty the clipboard */
111static void clipboard_clear_selection(struct clipboard *clip)
112{
113 clip->path[0] = '\0';
114 clip->attr = 0;
115 clip->flags = 0;
116}
117
118/* Store the selection in the clipboard */
119static bool clipboard_clip(struct clipboard *clip, const char *path,
120 unsigned int attr, unsigned int flags)
121{
122 /* if it fits it clips */
123 if (strmemccpy(clip->path, path, sizeof (clip->path)) != NULL)
124 {
125 clip->attr = attr;
126 clip->flags = flags;
127 return true;
128 }
129 else {
130 clipboard_clear_selection(clip);
131 return false;
132 }
133}
134
135/* ----------------------------------------------------------------------- */
136/* Displays the bookmark menu options for the user to decide. This is an */
137/* interface function. */
138/* ----------------------------------------------------------------------- */
139
140
141static int bookmark_load_menu_wrapper(void)
142{
143 if (get_current_activity() == ACTIVITY_CONTEXTMENU) /* get rid of parent activity */
144 pop_current_activity_without_refresh(); /* when called from ctxt menu */
145
146 return bookmark_load_menu();
147}
148
149static int bookmark_menu_callback(int action,
150 const struct menu_item_ex *this_item,
151 struct gui_synclist *this_list);
152MENUITEM_FUNCTION(bookmark_create_menu_item, 0,
153 ID2P(LANG_BOOKMARK_MENU_CREATE),
154 bookmark_create_menu,
155 bookmark_menu_callback, Icon_Bookmark);
156MENUITEM_FUNCTION(bookmark_load_menu_item, 0,
157 ID2P(LANG_BOOKMARK_MENU_LIST),
158 bookmark_load_menu_wrapper,
159 bookmark_menu_callback, Icon_Bookmark);
160MAKE_ONPLAYMENU(bookmark_menu, ID2P(LANG_BOOKMARK_MENU),
161 bookmark_menu_callback, Icon_Bookmark,
162 &bookmark_create_menu_item, &bookmark_load_menu_item);
163static int bookmark_menu_callback(int action,
164 const struct menu_item_ex *this_item,
165 struct gui_synclist *this_list)
166{
167 (void) this_list;
168 if (action == ACTION_REQUEST_MENUITEM)
169 {
170 /* hide loading bookmarks menu if no bookmarks exist */
171 if (this_item == &bookmark_load_menu_item)
172 {
173 if (!bookmark_exists())
174 return ACTION_EXIT_MENUITEM;
175 }
176 }
177 else if (action == ACTION_EXIT_MENUITEM)
178 settings_save();
179
180 return action;
181}
182
183/* CONTEXT_WPS playlist options */
184static bool shuffle_playlist(void)
185{
186 if (!yesno_pop_confirm(ID2P(LANG_SHUFFLE)))
187 return false;
188 playlist_sort(NULL, true);
189 playlist_randomise(NULL, current_tick, true);
190 playlist_set_modified(NULL, true);
191
192 return false;
193}
194
195static bool save_playlist(void)
196{
197 /* save_playlist_screen should load the newly saved playlist and resume */
198 save_playlist_screen(NULL);
199 return false;
200}
201
202static int wps_view_cur_playlist(void)
203{
204 if (get_current_activity() == ACTIVITY_CONTEXTMENU) /* get rid of parent activity */
205 pop_current_activity_without_refresh(); /* when called from ctxt menu */
206
207 playlist_viewer_ex(NULL, NULL);
208
209 return 0;
210}
211
212static void playing_time(void)
213{
214 plugin_load(PLUGIN_APPS_DIR"/playing_time.rock", NULL);
215}
216
217#ifdef HAVE_ALBUMART
218static void view_album_art(void)
219{
220 plugin_load(VIEWERS_DIR"/imageviewer.rock", NULL);
221}
222#endif
223
224MENUITEM_FUNCTION(wps_view_cur_playlist_item, 0, ID2P(LANG_VIEW_DYNAMIC_PLAYLIST),
225 wps_view_cur_playlist, NULL, Icon_NOICON);
226MENUITEM_FUNCTION(search_playlist_item, 0, ID2P(LANG_SEARCH_IN_PLAYLIST),
227 search_playlist, NULL, Icon_Playlist);
228MENUITEM_FUNCTION(playlist_save_item, 0, ID2P(LANG_SAVE_DYNAMIC_PLAYLIST),
229 save_playlist, NULL, Icon_Playlist);
230MENUITEM_FUNCTION(reshuffle_item, 0, ID2P(LANG_SHUFFLE_PLAYLIST),
231 shuffle_playlist, NULL, Icon_Playlist);
232MENUITEM_FUNCTION(playing_time_item, 0, ID2P(LANG_PLAYING_TIME),
233 playing_time, NULL, Icon_Playlist);
234MAKE_ONPLAYMENU( wps_playlist_menu, ID2P(LANG_CURRENT_PLAYLIST),
235 NULL, Icon_Playlist,
236 &wps_view_cur_playlist_item, &playlist_save_item,
237 &search_playlist_item, &reshuffle_item, &playing_time_item
238 );
239
240/* argument for add_to_playlist (for use by menu callbacks) */
241#define PL_NONE 0x00
242#define PL_QUEUE 0x01
243#define PL_REPLACE 0x02
244struct add_to_pl_param
245{
246 int8_t position;
247 uint8_t flags;
248};
249
250static struct add_to_pl_param addtopl_insert = {PLAYLIST_INSERT, PL_NONE};
251static struct add_to_pl_param addtopl_insert_first = {PLAYLIST_INSERT_FIRST, PL_NONE};
252static struct add_to_pl_param addtopl_insert_last = {PLAYLIST_INSERT_LAST, PL_NONE};
253static struct add_to_pl_param addtopl_insert_shuf = {PLAYLIST_INSERT_SHUFFLED, PL_NONE};
254static struct add_to_pl_param addtopl_insert_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_NONE};
255
256static struct add_to_pl_param addtopl_queue = {PLAYLIST_INSERT, PL_QUEUE};
257static struct add_to_pl_param addtopl_queue_first = {PLAYLIST_INSERT_FIRST, PL_QUEUE};
258static struct add_to_pl_param addtopl_queue_last = {PLAYLIST_INSERT_LAST, PL_QUEUE};
259static struct add_to_pl_param addtopl_queue_shuf = {PLAYLIST_INSERT_SHUFFLED, PL_QUEUE};
260static struct add_to_pl_param addtopl_queue_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_QUEUE};
261
262static struct add_to_pl_param addtopl_replace = {PLAYLIST_INSERT, PL_REPLACE};
263static struct add_to_pl_param addtopl_replace_shuffled = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_REPLACE};
264
265static void op_playlist_insert_selected(int position, bool queue)
266{
267#ifdef HAVE_TAGCACHE
268 if (selected_file.context == CONTEXT_STD && ctx_current_playlist_insert != NULL)
269 {
270 ctx_current_playlist_insert(position, queue, false);
271 return;
272 }
273 else if (selected_file.context == CONTEXT_ID3DB)
274 {
275 tagtree_current_playlist_insert(position, queue);
276 return;
277 }
278#endif
279 if ((selected_file.attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
280 playlist_insert_track(NULL, selected_file.path, position, queue, true);
281 else if ((selected_file.attr & FILE_ATTR_MASK) == FILE_ATTR_M3U)
282 playlist_insert_playlist(NULL, selected_file.path, position, queue);
283 else if (selected_file.attr & ATTR_DIRECTORY)
284 {
285 bool recurse = (global_settings.recursive_dir_insert == RECURSE_ON);
286 if (global_settings.recursive_dir_insert == RECURSE_ASK)
287 {
288
289 const char *lines[] = {
290 ID2P(LANG_RECURSE_DIRECTORY_QUESTION),
291 selected_file.path
292 };
293 const struct text_message message={lines, 2};
294 /* Ask if user wants to recurse directory */
295 recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES);
296 }
297
298 playlist_insert_directory(NULL, selected_file.path, position, queue,
299 recurse == RECURSE_ON);
300 }
301}
302
303/* CONTEXT_[TREE|ID3DB|STD] playlist options */
304static int add_to_playlist(void* arg)
305{
306 struct add_to_pl_param* param = arg;
307 int position = param->position;
308 bool new_playlist = (param->flags & PL_REPLACE) == PL_REPLACE;
309 bool queue = (param->flags & PL_QUEUE) == PL_QUEUE;
310
311 /* warn if replacing the playlist */
312 if (new_playlist && !warn_on_pl_erase())
313 return 1;
314
315 splash(0, ID2P(LANG_WAIT));
316
317 if (new_playlist && global_settings.keep_current_track_on_replace_playlist)
318 {
319 if (audio_status() & AUDIO_STATUS_PLAY)
320 {
321 playlist_remove_all_tracks(NULL);
322 new_playlist = false;
323 }
324 }
325
326 if (new_playlist)
327 playlist_create(NULL, NULL);
328
329 /* always set seed before inserting shuffled */
330 if (position == PLAYLIST_INSERT_SHUFFLED ||
331 position == PLAYLIST_INSERT_LAST_SHUFFLED)
332 {
333 srand(current_tick);
334 if (position == PLAYLIST_INSERT_LAST_SHUFFLED)
335 playlist_set_last_shuffled_start();
336 }
337
338 op_playlist_insert_selected(position, queue);
339
340 if (new_playlist && (playlist_amount() > 0))
341 {
342 /* nothing is currently playing so begin playing what we just
343 inserted */
344 if (global_settings.playlist_shuffle)
345 playlist_shuffle(current_tick, -1);
346 playlist_start(0, 0, 0);
347 onplay_result = ONPLAY_START_PLAY;
348 }
349
350 playlist_set_modified(NULL, true);
351 return 0;
352}
353
354static bool view_playlist(void)
355{
356 bool result;
357
358 result = playlist_viewer_ex(selected_file.path, NULL);
359
360 if (result == PLAYLIST_VIEWER_OK &&
361 onplay_result == ONPLAY_OK)
362 /* playlist was started from viewer */
363 onplay_result = ONPLAY_START_PLAY;
364
365 return result;
366}
367
368static int treeplaylist_callback(int action,
369 const struct menu_item_ex *this_item,
370 struct gui_synclist *this_list);
371
372/* insert items */
373MENUITEM_FUNCTION_W_PARAM(i_pl_item, 0, ID2P(LANG_ADD),
374 add_to_playlist, &addtopl_insert,
375 treeplaylist_callback, Icon_Playlist);
376MENUITEM_FUNCTION_W_PARAM(i_first_pl_item, 0, ID2P(LANG_PLAY_NEXT),
377 add_to_playlist, &addtopl_insert_first,
378 treeplaylist_callback, Icon_Playlist);
379MENUITEM_FUNCTION_W_PARAM(i_last_pl_item, 0, ID2P(LANG_PLAY_LAST),
380 add_to_playlist, &addtopl_insert_last,
381 treeplaylist_callback, Icon_Playlist);
382MENUITEM_FUNCTION_W_PARAM(i_shuf_pl_item, 0, ID2P(LANG_ADD_SHUFFLED),
383 add_to_playlist, &addtopl_insert_shuf,
384 treeplaylist_callback, Icon_Playlist);
385MENUITEM_FUNCTION_W_PARAM(i_last_shuf_pl_item, 0, ID2P(LANG_PLAY_LAST_SHUFFLED),
386 add_to_playlist, &addtopl_insert_last_shuf,
387 treeplaylist_callback, Icon_Playlist);
388/* queue items */
389MENUITEM_FUNCTION_W_PARAM(q_pl_item, 0, ID2P(LANG_QUEUE),
390 add_to_playlist, &addtopl_queue,
391 treeplaylist_callback, Icon_Playlist);
392MENUITEM_FUNCTION_W_PARAM(q_first_pl_item, 0, ID2P(LANG_QUEUE_FIRST),
393 add_to_playlist, &addtopl_queue_first,
394 treeplaylist_callback, Icon_Playlist);
395MENUITEM_FUNCTION_W_PARAM(q_last_pl_item, 0, ID2P(LANG_QUEUE_LAST),
396 add_to_playlist, &addtopl_queue_last,
397 treeplaylist_callback, Icon_Playlist);
398MENUITEM_FUNCTION_W_PARAM(q_shuf_pl_item, 0, ID2P(LANG_QUEUE_SHUFFLED),
399 add_to_playlist, &addtopl_queue_shuf,
400 treeplaylist_callback, Icon_Playlist);
401MENUITEM_FUNCTION_W_PARAM(q_last_shuf_pl_item, 0, ID2P(LANG_QUEUE_LAST_SHUFFLED),
402 add_to_playlist, &addtopl_queue_last_shuf,
403 treeplaylist_callback, Icon_Playlist);
404
405/* queue submenu */
406MAKE_ONPLAYMENU(queue_menu, ID2P(LANG_QUEUE_MENU),
407 treeplaylist_callback, Icon_Playlist,
408 &q_first_pl_item,
409 &q_pl_item,
410 &q_shuf_pl_item,
411 &q_last_pl_item,
412 &q_last_shuf_pl_item);
413
414/* replace playlist */
415MENUITEM_FUNCTION_W_PARAM(replace_pl_item, 0, ID2P(LANG_PLAY),
416 add_to_playlist, &addtopl_replace,
417 treeplaylist_callback, Icon_Playlist);
418
419MENUITEM_FUNCTION_W_PARAM(replace_shuf_pl_item, 0, ID2P(LANG_PLAY_SHUFFLED),
420 add_to_playlist, &addtopl_replace_shuffled,
421 treeplaylist_callback, Icon_Playlist);
422
423MAKE_ONPLAYMENU(tree_playlist_menu, ID2P(LANG_PLAYING_NEXT),
424 treeplaylist_callback, Icon_Playlist,
425
426 /* insert */
427 &i_first_pl_item,
428 &i_pl_item,
429 &i_last_pl_item,
430 &i_shuf_pl_item,
431 &i_last_shuf_pl_item,
432
433 /* queue */
434 &q_first_pl_item,
435 &q_pl_item,
436 &q_last_pl_item,
437 &q_shuf_pl_item,
438 &q_last_shuf_pl_item,
439
440 /* Queue submenu */
441 &queue_menu,
442
443 /* replace */
444 &replace_pl_item,
445 &replace_shuf_pl_item
446 );
447
448static int treeplaylist_callback(int action,
449 const struct menu_item_ex *this_item,
450 struct gui_synclist *this_list)
451{
452 (void)this_list;
453 int sel_file_attr = (selected_file.attr & FILE_ATTR_MASK);
454
455 switch (action)
456 {
457 case ACTION_REQUEST_MENUITEM:
458 if (this_item == &tree_playlist_menu)
459 {
460 if (sel_file_attr != FILE_ATTR_AUDIO &&
461 sel_file_attr != FILE_ATTR_M3U &&
462 (selected_file.attr & ATTR_DIRECTORY) == 0)
463 return ACTION_EXIT_MENUITEM;
464 }
465 else if (this_item == &queue_menu)
466 {
467 if (global_settings.show_queue_options != QUEUE_SHOW_IN_SUBMENU)
468 return ACTION_EXIT_MENUITEM;
469
470 /* queueing options only work during playback */
471 if (!(audio_status() & AUDIO_STATUS_PLAY))
472 return ACTION_EXIT_MENUITEM;
473 }
474 else if ((this_item->flags & MENU_TYPE_MASK) == MT_FUNCTION_CALL_W_PARAM &&
475 this_item->function_param->function_w_param == add_to_playlist)
476 {
477 struct add_to_pl_param *param = this_item->function_param->param;
478
479 if ((param->flags & PL_QUEUE) == PL_QUEUE)
480 {
481 if (global_settings.show_queue_options != QUEUE_SHOW_AT_TOPLEVEL &&
482 !in_queue_submenu)
483 return ACTION_EXIT_MENUITEM;
484 }
485
486 if (param->position == PLAYLIST_INSERT_SHUFFLED ||
487 param->position == PLAYLIST_INSERT_LAST_SHUFFLED)
488 {
489 if (!global_settings.show_shuffled_adding_options)
490 return ACTION_EXIT_MENUITEM;
491
492 if (sel_file_attr != FILE_ATTR_M3U &&
493 (selected_file.attr & ATTR_DIRECTORY) == 0)
494 return ACTION_EXIT_MENUITEM;
495 }
496
497 if ((param->flags & PL_REPLACE) != PL_REPLACE)
498 {
499 if (!(audio_status() & AUDIO_STATUS_PLAY))
500 return ACTION_EXIT_MENUITEM;
501 }
502 }
503
504 break;
505
506 case ACTION_ENTER_MENUITEM:
507 in_queue_submenu = this_item == &queue_menu;
508 break;
509 }
510
511 return action;
512}
513
514void onplay_show_playlist_menu(const char* path, int attr, void (*playlist_insert_cb))
515{
516 ctx_current_playlist_insert = playlist_insert_cb;
517 selected_file_set(CONTEXT_STD, path, attr);
518 in_queue_submenu = false;
519 do_menu(&tree_playlist_menu, NULL, NULL, false);
520}
521
522/* playlist catalog options */
523static bool cat_add_to_a_playlist(void)
524{
525 return catalog_add_to_a_playlist(selected_file.path, selected_file.attr,
526 false, NULL, ctx_add_to_playlist);
527}
528
529static bool cat_add_to_a_new_playlist(void)
530{
531 return catalog_add_to_a_playlist(selected_file.path, selected_file.attr,
532 true, NULL, ctx_add_to_playlist);
533}
534
535static int cat_playlist_callback(int action,
536 const struct menu_item_ex *this_item,
537 struct gui_synclist *this_list);
538
539MENUITEM_FUNCTION(cat_add_to_list, 0, ID2P(LANG_ADD_TO_EXISTING_PL),
540 cat_add_to_a_playlist, NULL, Icon_Playlist);
541MENUITEM_FUNCTION(cat_add_to_new, 0, ID2P(LANG_CATALOG_ADD_TO_NEW),
542 cat_add_to_a_new_playlist, NULL, Icon_Playlist);
543MAKE_ONPLAYMENU(cat_playlist_menu, ID2P(LANG_ADD_TO_PL),
544 cat_playlist_callback, Icon_Playlist,
545 &cat_add_to_list, &cat_add_to_new);
546
547void onplay_show_playlist_cat_menu(const char* track_name, int attr, void (*add_to_pl_cb))
548{
549 ctx_add_to_playlist = add_to_pl_cb;
550 selected_file_set(CONTEXT_STD, track_name, attr);
551 do_menu(&cat_playlist_menu, NULL, NULL, false);
552}
553
554static int cat_playlist_callback(int action,
555 const struct menu_item_ex *this_item,
556 struct gui_synclist *this_list)
557{
558 (void)this_item;
559 (void)this_list;
560 if (!selected_file.path ||
561 (((selected_file.attr & FILE_ATTR_MASK) != FILE_ATTR_AUDIO) &&
562 ((selected_file.attr & FILE_ATTR_MASK) != FILE_ATTR_M3U) &&
563 ((selected_file.attr & ATTR_DIRECTORY) == 0)))
564 {
565 return ACTION_EXIT_MENUITEM;
566 }
567
568 if (action == ACTION_REQUEST_MENUITEM)
569 {
570 if ((audio_status() & AUDIO_STATUS_PLAY)
571 || selected_file.context != CONTEXT_WPS)
572 {
573 return action;
574 }
575 return ACTION_EXIT_MENUITEM;
576 }
577 return action;
578}
579
580static void splash_cancelled(void)
581{
582 clear_screen_buffer(true);
583 splash(HZ, ID2P(LANG_CANCEL));
584}
585
586static void splash_failed(int lang_what, int err)
587{
588 cond_talk_ids_fq(lang_what, LANG_FAILED);
589 clear_screen_buffer(true);
590 splashf(HZ*2, "%s %s (%d)", str(lang_what), str(LANG_FAILED), err);
591}
592
593static bool clipboard_cut(void)
594{
595 return clipboard_clip(&clipboard, selected_file.path, selected_file.attr,
596 PASTE_CUT);
597}
598
599static bool clipboard_copy(void)
600{
601 return clipboard_clip(&clipboard, selected_file.path, selected_file.attr,
602 PASTE_COPY);
603}
604
605/* Paste the clipboard to the current directory */
606static int clipboard_paste(void)
607{
608 if (!clipboard.path[0])
609 return 1;
610
611 int rc = copy_move_fileobject(clipboard.path, getcwd(NULL, 0), clipboard.flags);
612
613 switch (rc)
614 {
615 case FORC_CANCELLED:
616 splash_cancelled();
617 /* Fallthrough */
618 case FORC_SUCCESS:
619 onplay_result = ONPLAY_RELOAD_DIR;
620 /* Fallthrough */
621 case FORC_NOOP:
622 clipboard_clear_selection(&clipboard);
623 /* Fallthrough */
624 case FORC_NOOVERWRT:
625 break;
626 default:
627 if (rc < FORC_SUCCESS) {
628 splash_failed(LANG_PASTE, rc);
629 onplay_result = ONPLAY_RELOAD_DIR;
630 }
631 }
632
633 return 1;
634}
635
636#ifdef HAVE_TAGCACHE
637static int set_rating_inline(void)
638{
639 struct mp3entry* id3 = audio_current_track();
640 if (id3 && id3->tagcache_idx && global_settings.runtimedb)
641 {
642 set_int_ex(str(LANG_MENU_SET_RATING), "", UNIT_INT, (void*)(&id3->rating),
643 NULL, 1, 0, 10, NULL, NULL);
644 tagcache_update_numeric(id3->tagcache_idx-1, tag_rating, id3->rating);
645 }
646 else
647 splash(HZ*2, ID2P(LANG_ID3_NO_INFO));
648 return 0;
649}
650static int ratingitem_callback(int action,
651 const struct menu_item_ex *this_item,
652 struct gui_synclist *this_list)
653{
654 (void)this_item;
655 (void)this_list;
656 if (action == ACTION_REQUEST_MENUITEM)
657 {
658 if (!selected_file.path || !global_settings.runtimedb || !tagcache_is_usable())
659 return ACTION_EXIT_MENUITEM;
660 }
661 return action;
662}
663MENUITEM_FUNCTION(rating_item, 0, ID2P(LANG_MENU_SET_RATING),
664 set_rating_inline,
665 ratingitem_callback, Icon_Questionmark);
666#endif
667MENUITEM_RETURNVALUE(plugin_item, ID2P(LANG_OPEN_PLUGIN),
668 GO_TO_PLUGIN, NULL, Icon_Plugin);
669
670static bool view_cue(void)
671{
672 struct mp3entry* id3 = audio_current_track();
673 if (id3 && id3->cuesheet)
674 {
675 browse_cuesheet(id3->cuesheet);
676 }
677 return false;
678}
679static int view_cue_item_callback(int action,
680 const struct menu_item_ex *this_item,
681 struct gui_synclist *this_list)
682{
683 (void)this_item;
684 (void)this_list;
685 struct mp3entry* id3 = audio_current_track();
686 if (action == ACTION_REQUEST_MENUITEM)
687 {
688 if (!selected_file.path || !id3 || !id3->cuesheet)
689 return ACTION_EXIT_MENUITEM;
690 }
691 return action;
692}
693MENUITEM_FUNCTION(view_cue_item, 0, ID2P(LANG_BROWSE_CUESHEET),
694 view_cue, view_cue_item_callback, Icon_NOICON);
695
696
697static int browse_id3_wrapper(void)
698{
699 if (get_current_activity() == ACTIVITY_CONTEXTMENU) /* get rid of parent activity */
700 pop_current_activity_without_refresh(); /* when called from ctxt menu */
701
702 if (browse_id3(audio_current_track(),
703 playlist_get_display_index(),
704 playlist_amount(), NULL, 1, NULL))
705 return GO_TO_ROOT;
706 return GO_TO_PREVIOUS;
707}
708
709/* CONTEXT_WPS items */
710MENUITEM_FUNCTION(browse_id3_item, MENU_FUNC_CHECK_RETVAL, ID2P(LANG_MENU_SHOW_ID3_INFO),
711 browse_id3_wrapper, NULL, Icon_NOICON);
712
713#ifdef HAVE_PITCHCONTROL
714MENUITEM_FUNCTION(pitch_screen_item, 0, ID2P(LANG_PITCH),
715 gui_syncpitchscreen_run, NULL, Icon_Audio);
716MENUITEM_FUNCTION(pitch_reset_item, 0, ID2P(LANG_RESET_SETTING),
717 reset_pitch, NULL, Icon_Submenu_Entered);
718
719static int pitch_callback(int action,
720 const struct menu_item_ex *this_item,
721 struct gui_synclist *this_list);
722
723/* need special handling so we can toggle the icon */
724#define MAKE_PITCHMENU( name, str, callback, icon, ... ) \
725 static const struct menu_item_ex *name##_[] = {__VA_ARGS__}; \
726 struct menu_callback_with_desc name##__ = {callback,str,icon}; \
727 static const struct menu_item_ex name = \
728 {MT_MENU|MENU_HAS_DESC|MENU_EXITAFTERTHISMENU| \
729 MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \
730 { (void*)name##_},{.callback_and_desc = & name##__}};
731
732MAKE_PITCHMENU(pitch_menu, ID2P(LANG_PITCH),
733 pitch_callback, Icon_Audio,
734 &pitch_screen_item,
735 &pitch_reset_item);
736
737static int pitch_callback(int action,
738 const struct menu_item_ex *this_item,
739 struct gui_synclist *this_list)
740{
741 if (action == ACTION_ENTER_MENUITEM || action == ACTION_REQUEST_MENUITEM)
742 {
743 pitch_menu__.icon_id = Icon_Submenu; /* if setting changed show + */
744 int32_t ts = dsp_get_timestretch();
745 if (sound_get_pitch() == PITCH_SPEED_100 && ts == PITCH_SPEED_100)
746 {
747 pitch_menu__.icon_id = Icon_Audio;
748 if (action == ACTION_ENTER_MENUITEM)
749 { /* if default then run pitch screen directly */
750 gui_syncpitchscreen_run();
751 action = ACTION_EXIT_MENUITEM;
752 }
753 }
754 }
755 return action;
756
757 (void)this_item;
758 (void)this_list;
759}
760#endif /*def HAVE_PITCHCONTROL*/
761
762#ifdef HAVE_ALBUMART
763MENUITEM_FUNCTION(view_album_art_item, 0, ID2P(LANG_VIEW_ALBUMART),
764 view_album_art, NULL, Icon_NOICON);
765#endif
766
767static int clipboard_delete_selected_fileobject(void)
768{
769 int rc = delete_fileobject(selected_file.path);
770 if (rc < FORC_SUCCESS) {
771 splash_failed(LANG_DELETE, rc);
772 } else if (rc == FORC_CANCELLED) {
773 splash_cancelled();
774 }
775 if (rc != FORC_NOOP) {
776 /* Could have failed after some but not all needed changes; reload */
777 onplay_result = ONPLAY_RELOAD_DIR;
778 }
779 return 1;
780}
781
782static void show_result(int rc, int lang_what)
783{
784 if (rc < FORC_SUCCESS) {
785 splash_failed(lang_what, rc);
786 } else if (rc == FORC_CANCELLED) {
787 /* splash_cancelled(); kbd_input() splashes it */
788 } else if (rc == FORC_SUCCESS) {
789 onplay_result = ONPLAY_RELOAD_DIR;
790 }
791}
792
793static int clipboard_create_dir(void)
794{
795 int rc = create_dir();
796
797 show_result(rc, LANG_CREATE_DIR);
798
799 return 1;
800}
801
802static int clipboard_rename_selected_file(void)
803{
804 int rc = rename_file(selected_file.path);
805
806 show_result(rc, LANG_RENAME);
807
808 return 1;
809}
810
811/* CONTEXT_[TREE|ID3DB] items */
812static int clipboard_callback(int action,
813 const struct menu_item_ex *this_item,
814 struct gui_synclist *this_list);
815
816MENUITEM_FUNCTION(rename_file_item, 0, ID2P(LANG_RENAME),
817 clipboard_rename_selected_file, clipboard_callback, Icon_NOICON);
818MENUITEM_FUNCTION(clipboard_cut_item, 0, ID2P(LANG_CUT),
819 clipboard_cut, clipboard_callback, Icon_NOICON);
820MENUITEM_FUNCTION(clipboard_copy_item, 0, ID2P(LANG_COPY),
821 clipboard_copy, clipboard_callback, Icon_NOICON);
822MENUITEM_FUNCTION(clipboard_paste_item, 0, ID2P(LANG_PASTE),
823 clipboard_paste, clipboard_callback, Icon_NOICON);
824MENUITEM_FUNCTION(delete_file_item, 0, ID2P(LANG_DELETE),
825 clipboard_delete_selected_fileobject, clipboard_callback, Icon_NOICON);
826MENUITEM_FUNCTION(delete_dir_item, 0, ID2P(LANG_DELETE_DIR),
827 clipboard_delete_selected_fileobject, clipboard_callback, Icon_NOICON);
828MENUITEM_FUNCTION(create_dir_item, 0, ID2P(LANG_CREATE_DIR),
829 clipboard_create_dir, clipboard_callback, Icon_NOICON);
830
831/* other items */
832static bool list_viewers(void)
833{
834 int ret = filetype_list_viewers(selected_file.path);
835 if (ret == PLUGIN_USB_CONNECTED)
836 onplay_result = ONPLAY_RELOAD_DIR;
837 return false;
838}
839
840#ifdef HAVE_TAGCACHE
841static bool prepare_database_sel(void *param)
842{
843 if (selected_file.context == CONTEXT_ID3DB)
844 {
845 if (param && !strcmp(param, "properties")
846 && (selected_file.attr & FILE_ATTR_MASK) != FILE_ATTR_AUDIO)
847 {
848 strmemccpy(selected_file.buf, MAKE_ACT_STR(ACTIVITY_DATABASEBROWSER),
849 sizeof(selected_file.buf));
850 }
851 else
852 {
853 /* If database is not loaded into RAM, or tagcache_ram is
854 set to "quick", filename needs to be retrieved from disk! */
855#ifdef HAVE_DISK_STORAGE
856 if ((selected_file.attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO
857 && !storage_disk_is_active()
858#ifdef HAVE_TC_RAMCACHE
859 && (global_settings.tagcache_ram != TAGCACHE_RAM_ON
860 || !tagcache_is_in_ram())
861#endif
862 )
863 splash(0, ID2P(LANG_WAIT));
864#endif
865 if (!tagtree_get_subentry_filename(selected_file.buf, MAX_PATH))
866 {
867 onplay_result = ONPLAY_RELOAD_DIR;
868 return false;
869 }
870 }
871
872 selected_file.path = selected_file.buf;
873 }
874 return true;
875}
876#endif
877
878static bool onplay_load_plugin(void *param)
879{
880#ifdef HAVE_TAGCACHE
881 if (!prepare_database_sel(param))
882 return false;
883#endif
884
885 if (get_current_activity() == ACTIVITY_CONTEXTMENU) /* get rid of parent activity */
886 pop_current_activity_without_refresh(); /* when called from ctxt menu */
887
888 int ret = filetype_load_plugin((const char*)param, selected_file.path);
889 if (ret == PLUGIN_USB_CONNECTED)
890 onplay_result = ONPLAY_RELOAD_DIR;
891 else if (ret == PLUGIN_GOTO_PLUGIN)
892 onplay_result = ONPLAY_PLUGIN;
893 else if (ret == PLUGIN_GOTO_WPS)
894 onplay_result = ONPLAY_START_PLAY;
895 return false;
896}
897
898MENUITEM_FUNCTION(list_viewers_item, 0, ID2P(LANG_ONPLAY_OPEN_WITH),
899 list_viewers, clipboard_callback, Icon_NOICON);
900MENUITEM_FUNCTION_W_PARAM(properties_item, 0, ID2P(LANG_PROPERTIES),
901 onplay_load_plugin, (void *)"properties",
902 clipboard_callback, Icon_NOICON);
903MENUITEM_FUNCTION_W_PARAM(track_info_item, 0, ID2P(LANG_MENU_SHOW_ID3_INFO),
904 onplay_load_plugin, (void *)"properties",
905 clipboard_callback, Icon_NOICON);
906#ifdef HAVE_TAGCACHE
907MENUITEM_FUNCTION_W_PARAM(pictureflow_item, 0, ID2P(LANG_ONPLAY_PICTUREFLOW),
908 onplay_load_plugin, (void *)"pictureflow",
909 clipboard_callback, Icon_NOICON);
910#endif
911static bool onplay_add_to_shortcuts(void)
912{
913 shortcuts_add(SHORTCUT_BROWSER, selected_file.path);
914 return false;
915}
916MENUITEM_FUNCTION(add_to_faves_item, 0, ID2P(LANG_ADD_TO_FAVES),
917 onplay_add_to_shortcuts,
918 clipboard_callback, Icon_NOICON);
919
920static void set_dir_helper(char* dirnamebuf, size_t bufsz)
921{
922 path_append(dirnamebuf, selected_file.path, PA_SEP_HARD, bufsz);
923 settings_save();
924}
925
926#if LCD_DEPTH > 1
927
928static void show_updated_backdrop(void)
929{
930 skin_backdrop_load_setting();
931 viewportmanager_theme_changed(THEME_STATUSBAR);
932 skin_backdrop_show(sb_get_backdrop(SCREEN_MAIN));
933}
934
935static bool set_backdrop(void)
936{
937 char previous_backdrop[sizeof global_settings.backdrop_file];
938 strcpy(previous_backdrop, global_settings.backdrop_file);
939
940 path_append(global_settings.backdrop_file, selected_file.path,
941 PA_SEP_HARD, sizeof(global_settings.backdrop_file));
942
943 show_updated_backdrop();
944
945 if (!yesno_pop(ID2P(LANG_SET_AS_BACKDROP))) {
946 strcpy(global_settings.backdrop_file, previous_backdrop);
947 show_updated_backdrop();
948 }
949 else
950 settings_save();
951
952 return true;
953}
954MENUITEM_FUNCTION(set_backdrop_item, 0, ID2P(LANG_SET_AS_BACKDROP),
955 set_backdrop, clipboard_callback, Icon_NOICON);
956#endif
957#ifdef HAVE_RECORDING
958static bool set_recdir(void)
959{
960 set_dir_helper(global_settings.rec_directory,
961 sizeof(global_settings.rec_directory));
962 return false;
963}
964MENUITEM_FUNCTION(set_recdir_item, 0, ID2P(LANG_RECORDING_DIR),
965 set_recdir, clipboard_callback, Icon_Recording);
966#endif
967static bool set_startdir(void)
968{
969 set_dir_helper(global_settings.start_directory,
970 sizeof(global_settings.start_directory));
971 return false;
972}
973MENUITEM_FUNCTION(set_startdir_item, 0, ID2P(LANG_START_DIR),
974 set_startdir, clipboard_callback, Icon_file_view_menu);
975
976static bool set_catalogdir(void)
977{
978 catalog_set_directory(selected_file.path);
979 settings_save();
980 return false;
981}
982MENUITEM_FUNCTION(set_catalogdir_item, 0, ID2P(LANG_PLAYLIST_DIR),
983 set_catalogdir, clipboard_callback, Icon_Playlist);
984
985#ifdef HAVE_TAGCACHE
986static bool set_databasedir(void)
987{
988 struct tagcache_stat *tc_stat = tagcache_get_stat();
989 if (strcasecmp(selected_file.path, tc_stat->db_path))
990 {
991 splash(HZ, ID2P(LANG_PLEASE_REBOOT));
992 }
993
994 set_dir_helper(global_settings.tagcache_db_path,
995 sizeof(global_settings.tagcache_db_path));
996 return false;
997}
998MENUITEM_FUNCTION(set_databasedir_item, 0, ID2P(LANG_DATABASE_DIR),
999 set_databasedir, clipboard_callback, Icon_Audio);
1000#endif
1001
1002MAKE_ONPLAYMENU(set_as_dir_menu, ID2P(LANG_SET_AS),
1003 clipboard_callback, Icon_NOICON,
1004 &set_catalogdir_item,
1005#ifdef HAVE_TAGCACHE
1006 &set_databasedir_item,
1007#endif
1008#ifdef HAVE_RECORDING
1009 &set_recdir_item,
1010#endif
1011 &set_startdir_item);
1012
1013static int clipboard_callback(int action,
1014 const struct menu_item_ex *this_item,
1015 struct gui_synclist *this_list)
1016{
1017 (void)this_list;
1018 switch (action)
1019 {
1020 case ACTION_REQUEST_MENUITEM:
1021#ifdef HAVE_MULTIVOLUME
1022 /* no rename+delete for volumes */
1023 if ((selected_file.attr & ATTR_VOLUME) &&
1024 (this_item == &rename_file_item ||
1025 this_item == &delete_dir_item ||
1026 this_item == &clipboard_cut_item ||
1027 this_item == &list_viewers_item))
1028 return ACTION_EXIT_MENUITEM;
1029#endif
1030#ifdef HAVE_TAGCACHE
1031 if (selected_file.context == CONTEXT_ID3DB)
1032 {
1033 if (this_item == &track_info_item ||
1034 this_item == &pictureflow_item)
1035 return action;
1036 return ACTION_EXIT_MENUITEM;
1037 }
1038#endif
1039 if (this_item == &clipboard_paste_item)
1040 { /* visible if there is something to paste */
1041 return (clipboard.path[0] != 0) ?
1042 action : ACTION_EXIT_MENUITEM;
1043 }
1044 else if (this_item == &create_dir_item &&
1045 *tree_get_context()->dirfilter <= NUM_FILTER_MODES)
1046 {
1047 return action;
1048 }
1049 else if (selected_file.path)
1050 {
1051 /* requires an actual file */
1052 if (this_item == &clipboard_cut_item ||
1053 this_item == &clipboard_copy_item)
1054 {
1055 if (*tree_get_context()->dirfilter != SHOW_M3U)
1056 return action;
1057 }
1058 else if (this_item == &rename_file_item ||
1059 (this_item == &track_info_item &&
1060 (selected_file.attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) ||
1061 (this_item == &properties_item &&
1062 (selected_file.attr & FILE_ATTR_MASK) != FILE_ATTR_AUDIO) ||
1063 this_item == &add_to_faves_item)
1064 {
1065 return action;
1066 }
1067 else if ((selected_file.attr & ATTR_DIRECTORY))
1068 {
1069 /* only for directories */
1070 if (this_item == &delete_dir_item ||
1071 this_item == &set_startdir_item ||
1072 this_item == &set_catalogdir_item ||
1073#ifdef HAVE_TAGCACHE
1074 this_item == &set_databasedir_item ||
1075#endif
1076#ifdef HAVE_RECORDING
1077 this_item == &set_recdir_item ||
1078#endif
1079 this_item == &set_as_dir_menu
1080 )
1081 return action;
1082 }
1083 /* only for files */
1084 else if (this_item == &list_viewers_item)
1085 {
1086 if (*tree_get_context()->dirfilter != SHOW_M3U)
1087 return action;
1088 }
1089 else if (this_item == &delete_file_item)
1090 return action;
1091#if LCD_DEPTH > 1
1092 else if (this_item == &set_backdrop_item)
1093 {
1094 char *suffix = strrchr(selected_file.path, '.');
1095 if (suffix)
1096 {
1097 if (strcasecmp(suffix, ".bmp") == 0)
1098 {
1099 return action;
1100 }
1101 }
1102 }
1103#endif
1104 }
1105 return ACTION_EXIT_MENUITEM;
1106 break;
1107 }
1108 return action;
1109}
1110
1111static int onplaymenu_callback(int action,
1112 const struct menu_item_ex *this_item,
1113 struct gui_synclist *this_list);
1114
1115/* used when onplay() is called in the CONTEXT_WPS context */
1116MAKE_ONPLAYMENU( wps_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE),
1117 onplaymenu_callback, Icon_Audio,
1118 &wps_playlist_menu, &cat_playlist_menu,
1119 &sound_settings, &playback_settings,
1120#ifdef HAVE_TAGCACHE
1121 &rating_item,
1122#endif
1123 &bookmark_menu,
1124 &plugin_item,
1125 &browse_id3_item, &list_viewers_item,
1126 &delete_file_item, &view_cue_item,
1127#ifdef HAVE_PITCHCONTROL
1128 &pitch_menu,
1129#endif
1130#ifdef HAVE_ALBUMART
1131 &view_album_art_item,
1132#endif
1133 );
1134
1135int sort_playlists_callback(int action,
1136 const struct menu_item_ex *this_item,
1137 struct gui_synclist *this_list)
1138{
1139 (void) this_list;
1140 (void) this_item;
1141
1142 if (action == ACTION_REQUEST_MENUITEM &&
1143 *tree_get_context()->dirfilter != SHOW_M3U)
1144 {
1145 return ACTION_EXIT_MENUITEM;
1146 }
1147 return action;
1148}
1149
1150MENUITEM_SETTING(sort_playlists, &global_settings.sort_playlists, sort_playlists_callback);
1151
1152MENUITEM_FUNCTION(view_playlist_item, 0, ID2P(LANG_VIEW),
1153 view_playlist,
1154 onplaymenu_callback, Icon_Playlist);
1155
1156/* used when onplay() is not called in the CONTEXT_WPS context */
1157MAKE_ONPLAYMENU( tree_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE),
1158 onplaymenu_callback, Icon_file_view_menu,
1159 &view_playlist_item, &tree_playlist_menu, &cat_playlist_menu,
1160 &rename_file_item, &clipboard_cut_item, &clipboard_copy_item,
1161 &clipboard_paste_item, &delete_file_item, &delete_dir_item,
1162 &list_viewers_item, &create_dir_item, &properties_item, &track_info_item,
1163#ifdef HAVE_TAGCACHE
1164 &pictureflow_item,
1165#endif
1166#if LCD_DEPTH > 1
1167 &set_backdrop_item,
1168#endif
1169 &add_to_faves_item, &set_as_dir_menu, &file_menu, &sort_playlists,
1170 );
1171static int onplaymenu_callback(int action,
1172 const struct menu_item_ex *this_item,
1173 struct gui_synclist *this_list)
1174{
1175 (void)this_list;
1176 switch (action)
1177 {
1178 case ACTION_TREE_STOP:
1179 if (this_item == &wps_onplay_menu)
1180 {
1181 list_stop_handler();
1182 return ACTION_STD_CANCEL;
1183 }
1184 break;
1185 case ACTION_REQUEST_MENUITEM:
1186 if (this_item == &view_playlist_item)
1187 {
1188 if ((selected_file.attr & FILE_ATTR_MASK) == FILE_ATTR_M3U &&
1189 selected_file.context == CONTEXT_TREE)
1190 return action;
1191 }
1192 return ACTION_EXIT_MENUITEM;
1193 break;
1194 case ACTION_EXIT_MENUITEM:
1195 return ACTION_EXIT_AFTER_THIS_MENUITEM;
1196 break;
1197 default:
1198 break;
1199 }
1200 return action;
1201}
1202
1203#ifdef HAVE_HOTKEY
1204/* direct function calls, no need for menu callbacks */
1205static bool hotkey_delete_item(void)
1206{
1207#ifdef HAVE_MULTIVOLUME
1208 /* no delete for volumes */
1209 if (selected_file.attr & ATTR_VOLUME)
1210 return false;
1211#endif
1212
1213#ifdef HAVE_TAGCACHE
1214 if (selected_file.context == CONTEXT_ID3DB &&
1215 (selected_file.attr & FILE_ATTR_MASK) != FILE_ATTR_AUDIO)
1216 return false;
1217
1218 if (!prepare_database_sel(NULL))
1219 return false;
1220#endif
1221
1222 return clipboard_delete_selected_fileobject();
1223}
1224
1225static bool hotkey_open_with(void)
1226{
1227 /* only open files */
1228 if (selected_file.attr & ATTR_DIRECTORY)
1229 return false;
1230#ifdef HAVE_MULTIVOLUME
1231 if (selected_file.attr & ATTR_VOLUME)
1232 return false;
1233#endif
1234#ifdef HAVE_TAGCACHE
1235 if (!prepare_database_sel(NULL))
1236 return false;
1237#endif
1238 return list_viewers();
1239}
1240
1241static int hotkey_tree_pl_insert_shuffled(void)
1242{
1243 if ((audio_status() & AUDIO_STATUS_PLAY) ||
1244 (selected_file.attr & ATTR_DIRECTORY) ||
1245 ((selected_file.attr & FILE_ATTR_MASK) == FILE_ATTR_M3U))
1246 {
1247 add_to_playlist(&addtopl_insert_shuf);
1248 }
1249 return ONPLAY_RELOAD_DIR;
1250}
1251
1252static int hotkey_tree_run_plugin(void *param)
1253{
1254#ifdef HAVE_TAGCACHE
1255 if (!prepare_database_sel(param))
1256 return ONPLAY_RELOAD_DIR;
1257#endif
1258 if (filetype_load_plugin((const char*)param, selected_file.path) == PLUGIN_GOTO_WPS)
1259 return ONPLAY_START_PLAY;
1260
1261 return ONPLAY_RELOAD_DIR;
1262}
1263
1264static int hotkey_wps_run_plugin(void)
1265{
1266 open_plugin_run(ID2P(LANG_HOTKEY_WPS));
1267 return ONPLAY_OK;
1268}
1269#define HOTKEY_FUNC(func, param) {{(void *)func}, param}
1270
1271/* Any desired hotkey functions go here, in the enum in onplay.h,
1272 and in the settings menu in settings_list.c. The order here
1273 is not important. */
1274static const struct hotkey_assignment hotkey_items[] = {
1275 [0]{ .action = HOTKEY_OFF,
1276 .lang_id = LANG_OFF,
1277 .func = HOTKEY_FUNC(NULL,NULL),
1278 .return_code = ONPLAY_RELOAD_DIR,
1279 .flags = HOTKEY_FLAG_WPS | HOTKEY_FLAG_TREE },
1280 { .action = HOTKEY_VIEW_PLAYLIST,
1281 .lang_id = LANG_VIEW_DYNAMIC_PLAYLIST,
1282 .func = HOTKEY_FUNC(NULL, NULL),
1283 .return_code = ONPLAY_PLAYLIST,
1284 .flags = HOTKEY_FLAG_WPS },
1285 { .action = HOTKEY_SHOW_TRACK_INFO,
1286 .lang_id = LANG_MENU_SHOW_ID3_INFO,
1287 .func = HOTKEY_FUNC(browse_id3_wrapper, NULL),
1288 .return_code = ONPLAY_RELOAD_DIR,
1289 .flags = HOTKEY_FLAG_WPS },
1290#ifdef HAVE_PITCHCONTROL
1291 { .action = HOTKEY_PITCHSCREEN,
1292 .lang_id = LANG_PITCH,
1293 .func = HOTKEY_FUNC(gui_syncpitchscreen_run, NULL),
1294 .return_code = ONPLAY_RELOAD_DIR,
1295 .flags = HOTKEY_FLAG_WPS | HOTKEY_FLAG_NOSBS },
1296#endif
1297 { .action = HOTKEY_OPEN_WITH,
1298 .lang_id = LANG_ONPLAY_OPEN_WITH,
1299 .func = HOTKEY_FUNC(hotkey_open_with, NULL),
1300 .return_code = ONPLAY_RELOAD_DIR,
1301 .flags = HOTKEY_FLAG_WPS | HOTKEY_FLAG_TREE },
1302 { .action = HOTKEY_DELETE,
1303 .lang_id = LANG_DELETE,
1304 .func = HOTKEY_FUNC(hotkey_delete_item, NULL),
1305 .return_code = ONPLAY_RELOAD_DIR,
1306 .flags = HOTKEY_FLAG_WPS | HOTKEY_FLAG_TREE },
1307 { .action = HOTKEY_INSERT,
1308 .lang_id = LANG_ADD,
1309 .func = HOTKEY_FUNC(add_to_playlist, (intptr_t*)&addtopl_insert),
1310 .return_code = ONPLAY_RELOAD_DIR,
1311 .flags = HOTKEY_FLAG_TREE },
1312 { .action = HOTKEY_INSERT_SHUFFLED,
1313 .lang_id = LANG_ADD_SHUFFLED,
1314 .func = HOTKEY_FUNC(hotkey_tree_pl_insert_shuffled, NULL),
1315 .return_code = ONPLAY_FUNC_RETURN,
1316 .flags = HOTKEY_FLAG_TREE },
1317 { .action = HOTKEY_PLUGIN,
1318 .lang_id = LANG_OPEN_PLUGIN,
1319 .func = HOTKEY_FUNC(hotkey_wps_run_plugin, NULL),
1320 .return_code = ONPLAY_FUNC_RETURN,
1321 .flags = HOTKEY_FLAG_WPS | HOTKEY_FLAG_NOSBS },
1322 { .action = HOTKEY_BOOKMARK,
1323 .lang_id = LANG_BOOKMARK_MENU_CREATE,
1324 .func = HOTKEY_FUNC(bookmark_create_menu, NULL),
1325 .return_code = ONPLAY_OK,
1326 .flags = HOTKEY_FLAG_WPS | HOTKEY_FLAG_NOSBS },
1327 { .action = HOTKEY_BOOKMARK_LIST,
1328 .lang_id = LANG_BOOKMARK_MENU_LIST,
1329 .func = HOTKEY_FUNC(bookmark_load_menu, NULL),
1330 .return_code = ONPLAY_START_PLAY,
1331 .flags = HOTKEY_FLAG_WPS },
1332 { .action = HOTKEY_PROPERTIES,
1333 .lang_id = LANG_PROPERTIES,
1334 .func = HOTKEY_FUNC(hotkey_tree_run_plugin, (void *)"properties"),
1335 .return_code = ONPLAY_FUNC_RETURN,
1336 .flags = HOTKEY_FLAG_TREE },
1337#ifdef HAVE_TAGCACHE
1338 { .action = HOTKEY_PICTUREFLOW,
1339 .lang_id = LANG_ONPLAY_PICTUREFLOW,
1340 .func = HOTKEY_FUNC(hotkey_tree_run_plugin, (void *)"pictureflow"),
1341 .return_code = ONPLAY_FUNC_RETURN,
1342 .flags = HOTKEY_FLAG_TREE },
1343#endif
1344};
1345
1346const struct hotkey_assignment *get_hotkey(int action)
1347{
1348 for (size_t i = ARRAYLEN(hotkey_items) - 1; i < ARRAYLEN(hotkey_items); i--)
1349 {
1350 if (hotkey_items[i].action == action)
1351 return &hotkey_items[i];
1352 }
1353 return &hotkey_items[0]; /* no valid hotkey set, return HOTKEY_OFF*/
1354}
1355
1356/* Execute the hotkey function, if listed */
1357static int execute_hotkey(bool is_wps)
1358{
1359 const int action = (is_wps ? global_settings.hotkey_wps :
1360 global_settings.hotkey_tree);
1361
1362 /* search assignment struct for a match for the hotkey setting */
1363 const struct hotkey_assignment *this_item = get_hotkey(action);
1364
1365 /* run the associated function (with optional param), if any */
1366 const struct menu_func_param func = this_item->func;
1367
1368 int func_return = ONPLAY_RELOAD_DIR;
1369 if (func.function != NULL)
1370 {
1371 if (func.param != NULL)
1372 func_return = (*func.function_w_param)(func.param);
1373 else
1374 func_return = (*func.function)();
1375 }
1376 const int return_code = this_item->return_code;
1377
1378 if (return_code == ONPLAY_FUNC_RETURN)
1379 return func_return; /* Use value returned by function */
1380 return return_code; /* or return the associated value */
1381}
1382#endif /* HOTKEY */
1383
1384int onplay(char* file, int attr, int from_context, bool hotkey, int customaction)
1385{
1386 const struct menu_item_ex *menu;
1387 onplay_result = ONPLAY_OK;
1388 ctx_current_playlist_insert = NULL;
1389 selected_file_set(from_context, NULL, attr);
1390
1391#ifdef HAVE_TAGCACHE
1392 if (from_context == CONTEXT_ID3DB)
1393 {
1394 ctx_add_to_playlist = tagtree_add_to_playlist;
1395 if (file != NULL)
1396 {
1397 /* add a leading slash so that catalog_add_to_a_playlist
1398 later prefills the name when creating a new playlist */
1399 snprintf(selected_file.buf, MAX_PATH, "/%s", file);
1400 selected_file.path = selected_file.buf;
1401 }
1402 }
1403 else
1404#endif
1405 {
1406 ctx_add_to_playlist = NULL;
1407 if (file != NULL)
1408 {
1409 strmemccpy(selected_file.buf, file, MAX_PATH);
1410 selected_file.path = selected_file.buf;
1411 }
1412
1413 }
1414 int menu_selection;
1415
1416#ifdef HAVE_HOTKEY
1417 if (hotkey)
1418 return execute_hotkey(from_context == CONTEXT_WPS);
1419#else
1420 (void)hotkey;
1421#endif
1422 if (customaction == ONPLAY_CUSTOMACTION_SHUFFLE_SONGS)
1423 {
1424 int returnCode = add_to_playlist(&addtopl_replace_shuffled);
1425 if (returnCode == 1)
1426 // User did not want to erase his current playlist, so let's show again the database main menu
1427 return ONPLAY_RELOAD_DIR;
1428 return ONPLAY_START_PLAY;
1429 }
1430
1431 push_current_activity(ACTIVITY_CONTEXTMENU);
1432 if (from_context == CONTEXT_WPS)
1433 menu = &wps_onplay_menu;
1434 else
1435 menu = &tree_onplay_menu;
1436 menu_selection = do_menu(menu, NULL, NULL, false);
1437
1438 if (get_current_activity() == ACTIVITY_CONTEXTMENU) /* Activity may have been */
1439 pop_current_activity(); /* popped already by menu item */
1440
1441
1442 if (menu_selection == GO_TO_WPS)
1443 return ONPLAY_START_PLAY;
1444 if (menu_selection == GO_TO_ROOT)
1445 return ONPLAY_MAINMENU;
1446 if (menu_selection == GO_TO_MAINMENU)
1447 return ONPLAY_MAINMENU;
1448 if (menu_selection == GO_TO_PLAYLIST_VIEWER)
1449 return ONPLAY_PLAYLIST;
1450 if (menu_selection == GO_TO_PLUGIN)
1451 return ONPLAY_PLUGIN;
1452
1453 return onplay_result;
1454}
1455
1456int get_onplay_context(void)
1457{
1458 return selected_file.context;
1459}