From e3097bf92c9fc74ed3b724caf338dd24d7f21621 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Tue, 30 Jul 2024 12:47:36 -0400 Subject: [Feature] File Picker Plugin allows viewers to pop a file browser to let the user pick a relevant file which then gets run by the viewer (only lua so far) Change-Id: I7e6b4c2827fab5e9f596d336f546100636c4b871 --- apps/plugins/CATEGORIES | 1 + apps/plugins/SOURCES | 1 + apps/plugins/file_picker.c | 295 +++++++++++++++++++++++++++++++++++++++++++++ apps/plugins/lua/rocklua.c | 17 +++ 4 files changed, 314 insertions(+) create mode 100644 apps/plugins/file_picker.c (limited to 'apps/plugins') diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index 814fb2f130..79186b7aed 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -35,6 +35,7 @@ doom,games duke3d,games euroconverter,apps fft,demos +file_picker,viewers fire,demos fireworks,demos firmware_flash,apps diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index 0b1d48d5de..a070637a77 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -12,6 +12,7 @@ cube.c cue_playlist.c dart_scorer.c dict.c +file_picker.c jackpot.c keybox.c keyremap.c diff --git a/apps/plugins/file_picker.c b/apps/plugins/file_picker.c new file mode 100644 index 0000000000..ce9d81728e --- /dev/null +++ b/apps/plugins/file_picker.c @@ -0,0 +1,295 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2024 William Wilgus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +/*File Picker Plugin + +FPP accepts several arguments to help in your file picking adventure +* NOTE: anything with spaces should be quoted, -f and -l can't be used together * + -r "" doesn't need to be a full path demos/file.rock works + the file (if choosen, cancel exits) will be passed as a parameter + -t "" + -s "<START DIR>" + -f "<.EXT (max 64 chars) >" the extension of the files you are looking for + must have the '.' and ".*" accepts any file + -l "<.EXT.EXT,.EXT .EXT (max 64 chars) >" list of extensions for files you are looking for + each must have the '.' spaces and commas are ignored + -a attrib flags eg FILE_ATTR_AUDIO + -d disallow changing directories (hide directories doesn't allow changing from start dir) +*/ +#include "plugin.h" +#include "lang_enum.h" +#include "lib/arg_helper.h" + +#if defined(DEBUG) || defined(SIMULATOR) + #define logf(...) rb->debugf(__VA_ARGS__); rb->debugf("\n") +#elif defined(ROCKBOX_HAS_LOGF) + #define logf rb->logf +#else + #define logf(...) do { } while(0) +#endif + +#define FIND_NODIRS 0x01 +#define FIND_ATTRIB 0x02 +#define FIND_WILDCARD 0x04 +#define FIND_EXT 0x08 +#define FIND_EXT_IN_LIST 0x10 + +static struct fpp +{ + char return_plugin[MAX_PATH]; + char start_dir[MAX_PATH]; + char file_ext[64]; + char title[64]; + int tree_attr; + int flags; +}fpp; + +static int arg_callback(char argchar, const char **parameter, void *userdata) +{ + struct fpp *pfp = userdata; + int ret; + long num; + const char* start = *parameter; + while (*parameter[0] > '/' && ispunct(*parameter[0])) (*parameter)++; + switch (tolower(argchar)) + { + case 'd' : + pfp->flags |= FIND_NODIRS; + logf ("Find no dirs"); + break; + case 'r' : /*return_plugin*/ + logf ("trying PLUGIN_DIR..."); + size_t l = rb->strlcpy(pfp->return_plugin, + PLUGIN_DIR, + sizeof(pfp->return_plugin)); + + ret = string_parse(parameter, + pfp->return_plugin + l, + sizeof(pfp->return_plugin) - l); + + if (ret && !rb->file_exists(pfp->return_plugin)) + { + logf("Failed"); + *parameter = start; + string_parse(parameter, pfp->return_plugin, + sizeof(pfp->return_plugin)); + } + + if (ret) + { + logf ("Ret plugin: Val: %s\n", pfp->return_plugin); + logf("ate %d chars\n", ret); + } + break; + case 't' : /* title */ + ret = string_parse(parameter, pfp->title, sizeof(pfp->title)); + if (ret) + { + logf ("Title: Val: %s\n", pfp->title); + logf("ate %d chars\n", ret); + } + break; + case 's' : /* start directory */ + ret = string_parse(parameter, pfp->start_dir, sizeof(pfp->start_dir)); + if (ret) + { + if (!rb->dir_exists(pfp->start_dir)) + { + rb->strlcpy(pfp->start_dir, PATH_ROOTSTR, sizeof(pfp->start_dir)); + } + + logf ("Start dir: Val: %s\n", pfp->start_dir); + logf("ate %d chars\n", ret); + } + break; + case 'f' : /* file extension */ + if (pfp->flags & FIND_EXT_IN_LIST) + { + rb->splash(HZ*5, "list extensions already active -f ignored"); + break; + } + ret = string_parse(parameter, pfp->file_ext, sizeof(pfp->file_ext)); + if (ret) + { + if (pfp->file_ext[1] == '*') + pfp->flags |= FIND_WILDCARD; + else + pfp->flags |= FIND_EXT; + logf ("Extension: Val: %s\n", pfp->file_ext); + logf("ate %d chars\n", ret); + } + break; + case 'l' : /* file extension list */ + if (pfp->flags & FIND_EXT) + { + rb->splash(HZ*5, "extension already active -l ignored"); + break; + } + ret = string_parse(parameter, pfp->file_ext, sizeof(pfp->file_ext)); + if (ret) + { + char *wr = pfp->file_ext; + char *rd = pfp->file_ext; + while (*rd != '\0') /* copy the extensions */ + { + if (*rd == ' ' || *rd == ',' || *rd == ';') + { + /* ignore spaces, commas, and semicolons */ + rd++; + continue; + } + *wr++ = *rd++; + } + *wr = '\0'; + pfp->flags |= FIND_EXT_IN_LIST; + logf ("Extension List: Val: %s\n", pfp->file_ext); + logf("ate %d chars\n", ret); + } + break; + case 'a' : /* tree attribute */ + ret = longnum_parse(parameter, &num, NULL); + if (ret) + { + pfp->tree_attr = (num)&FILE_ATTR_MASK; + pfp->flags |= FIND_ATTRIB; + logf ("Attrib: Val: 0x%x\n", (uint32_t)num); + logf("ate %d chars\n", ret); + } + break; + default : + rb->splashf(HZ, "Unknown switch '%c'",argchar); + logf("Unknown switch '%c'",argchar); + //return 0; + } + + return 1; +} + +static bool cb_show_item(char *name, int attr, struct tree_context *tc) +{ + static int dirlevel = -1; + if(attr & ATTR_DIRECTORY) + { + if (fpp.flags & FIND_NODIRS) + { + if (tc->dirlevel == dirlevel) + return false; + dirlevel = tc->dirlevel; + if (rb->strcasestr(tc->currdir, fpp.start_dir) == NULL) + { + tc->is_browsing = false; /* exit immediately */ + logf("exiting %d", tc->dirlevel); + } + } + + return true; + } + if (fpp.flags & FIND_WILDCARD) + { + return true; + } + if ((fpp.flags & FIND_ATTRIB) && (fpp.tree_attr & attr) != 0) + { + return true; + } + if (fpp.flags & FIND_EXT) + { + const char *p = rb->strrchr(name, '.' ); + if (p != NULL && !rb->strcasecmp( p, fpp.file_ext)) + return true; + } + + if (fpp.flags & FIND_EXT_IN_LIST) + { + const char *p = rb->strrchr(name, '.' ); + if (p != NULL && rb->strcasestr(fpp.file_ext, p) != NULL) + return true; + } + + logf("Excluded: %s", name); + return false; + +} + +static int browse_file_dir(struct fpp *pfp) +{ + char buf[MAX_PATH]; + struct browse_context browse = { + .dirfilter = SHOW_ALL, + .flags = BROWSE_SELECTONLY | BROWSE_DIRFILTER, + .title = pfp->title, + .icon = Icon_Playlist, + .buf = buf, + .bufsize = sizeof(buf), + .root = pfp->start_dir, + .callback_show_item = &cb_show_item, + }; + + if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS) + { + if (rb->file_exists(buf)) + { + logf("Loading %s", buf); + return rb->plugin_open(pfp->return_plugin, buf); + } + else + { + logf("Error opening %s", buf); + rb->splashf(HZ *2, "Error Opening %s", buf); + return PLUGIN_ERROR; + } + } + + return PLUGIN_OK; +} + +enum plugin_status plugin_start(const void* parameter) +{ + if (!parameter) + { + rb->splash(HZ *2, "No Args"); + return PLUGIN_ERROR; + } + + argparse((const char*) parameter, -1, &fpp, &arg_callback); + + if (fpp.title[0] == '\0') + { + if (rb->global_settings->talk_menu) + rb->talk_id(LANG_CHOOSE_FILE, true); + + if ((fpp.flags & FIND_EXT) || (fpp.flags & FIND_EXT_IN_LIST)) + { + rb->snprintf(fpp.title, sizeof(fpp.title), + "%s (%s)", rb->str(LANG_CHOOSE_FILE), fpp.file_ext); + if (rb->global_settings->talk_menu) + rb->talk_spell(fpp.file_ext, true); + } + else + { + rb->snprintf(fpp.title, sizeof(fpp.title), "%s", rb->str(LANG_CHOOSE_FILE)); + } + rb->talk_force_enqueue_next(); + } + if (fpp.start_dir[0] == '\0' || !rb->dir_exists(fpp.start_dir)) + rb->strcpy(fpp.start_dir, PATH_ROOTSTR); + + return browse_file_dir(&fpp); +} diff --git a/apps/plugins/lua/rocklua.c b/apps/plugins/lua/rocklua.c index 3cf0fce945..650c4d6dae 100644 --- a/apps/plugins/lua/rocklua.c +++ b/apps/plugins/lua/rocklua.c @@ -241,6 +241,20 @@ static void display_traceback(const char *errstr) rb->splash(10 * HZ, errstr); #endif } + +int browse_scripts(void) +{ + static char buf[MAX_PATH]; + const char *fname = rb->plugin_get_current_filename(); + /* strip plugin dir to save space in the param buffer */ + if (rb->strncmp(fname, PLUGIN_DIR, sizeof(PLUGIN_DIR) - 1) == 0) + fname += sizeof(PLUGIN_DIR) - 1; /* leave slash */ + /* -r return to this plugin, -f looking for lua files, + -s start in lua_scripts, -d lock to that directory */ + snprintf(buf, sizeof(buf), "-r'%s'-f'.lua'-s'%s'-d", + fname, PLUGIN_DEMOS_DIR"/lua_scripts/"); + return rb->plugin_open(VIEWERS_DIR "/file_picker.rock", buf); +} /***************** Plugin Entry Point *****************/ enum plugin_status plugin_start(const void* parameter) { @@ -249,7 +263,10 @@ enum plugin_status plugin_start(const void* parameter) if (parameter == NULL) { if (!Ls) + { rb->splash(HZ, "Play a .lua file!"); + return browse_scripts(); + } } else { -- cgit v1.2.3