summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorWilliam Wilgus <wilgus.william@gmail.com>2020-08-17 11:05:26 -0400
committerWilliam Wilgus <wilgus.william@gmail.com>2020-08-17 22:17:15 -0400
commit889bcc0f763fcb31b9ac66a23199ce31746664ed (patch)
treeab566a98dfb4bad3812c2ba3a124196b223d7618 /apps
parent96e1bb655651ad7e0d5bd7841ce5b4642e9458dc (diff)
downloadrockbox-889bcc0f763fcb31b9ac66a23199ce31746664ed.tar.gz
rockbox-889bcc0f763fcb31b9ac66a23199ce31746664ed.zip
WIP open_plugins.rock viewer
OP allows you to use Open With.. to call plugins with parameters called directly it acts as a shortcut list for plugins open_plugins.rock interfaces with the open_plugin core When opened directly it acts as a viewer for the plugin.dat file this allows you to edit the paths and parameters for core shortcuts as well as your added plugins If a plugin is supplied to the viewer it is added to the dat file If instead the plugin has previously been added then it is run with the parameters you previously supplied ----------------------------------------------------------------------------- Added export to .opx files this allows shortcuts to plugins with parameters to be called from the file browser Change-Id: Ib8b05a60b049fb1d5881031ca09a07e3307d375a
Diffstat (limited to 'apps')
-rw-r--r--apps/lang/english.lang98
-rw-r--r--apps/plugins/CATEGORIES1
-rw-r--r--apps/plugins/SOURCES1
-rw-r--r--apps/plugins/open_plugins.c823
-rw-r--r--apps/plugins/plugins.make5
-rw-r--r--apps/plugins/viewers.config2
6 files changed, 930 insertions, 0 deletions
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index 8af6e55c5e..fc5a37c569 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -16206,3 +16206,101 @@
*: "Parameter"
</voice>
</phrase>
+<phrase>
+ id: LANG_NAME
+ desc:
+ user: core
+ <source>
+ *: "Name"
+ </source>
+ <dest>
+ *: "Name"
+ </dest>
+ <voice>
+ *: "Name"
+ </voice>
+</phrase>
+<phrase>
+ id: LANG_ADD
+ desc:
+ user: core
+ <source>
+ *: "Add"
+ </source>
+ <dest>
+ *: "Add"
+ </dest>
+ <voice>
+ *: "Add"
+ </voice>
+</phrase>
+<phrase>
+ id: LANG_BACK
+ desc:
+ user: core
+ <source>
+ *: "Back"
+ </source>
+ <dest>
+ *: "Back"
+ </dest>
+ <voice>
+ *: "Back"
+ </voice>
+</phrase>
+<phrase>
+ id: LANG_EDIT
+ desc:
+ user: core
+ <source>
+ *: "Edit"
+ </source>
+ <dest>
+ *: "Edit"
+ </dest>
+ <voice>
+ *: "Edit"
+ </voice>
+</phrase>
+<phrase>
+ id: LANG_RUN
+ desc:
+ user: core
+ <source>
+ *: "Run"
+ </source>
+ <dest>
+ *: "Run"
+ </dest>
+ <voice>
+ *: "Run"
+ </voice>
+</phrase>
+<phrase>
+ id: LANG_EXPORT
+ desc:
+ user: core
+ <source>
+ *: "Export"
+ </source>
+ <dest>
+ *: "Export"
+ </dest>
+ <voice>
+ *: "Export"
+ </voice>
+</phrase>
+<phrase>
+ id: LANG_BROWSE
+ desc:
+ user: core
+ <source>
+ *: "Browse"
+ </source>
+ <dest>
+ *: "Browse"
+ </dest>
+ <voice>
+ *: "Browse"
+ </voice>
+</phrase>
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index 07bc4df847..d3093689f9 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -67,6 +67,7 @@ mosaique,demos
mp3_encoder,apps
mpegplayer,viewers
nim,games
+open_plugins,viewers
oscilloscope,demos
otp,apps
pacbox,games
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 46a3c61537..408dc6bc67 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -137,6 +137,7 @@ chopper.c
demystify.c
jewels.c
minesweeper.c
+open_plugins.c
oscilloscope.c
pegbox.c
periodic_table.c
diff --git a/apps/plugins/open_plugins.c b/apps/plugins/open_plugins.c
new file mode 100644
index 0000000000..f06a790d02
--- /dev/null
+++ b/apps/plugins/open_plugins.c
@@ -0,0 +1,823 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// __ \_/ ___\| |/ /| __ \ / __ \ \/ /
+ * Jukebox | | ( (__) ) \___| ( | \_\ ( (__) ) (
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2020 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.
+ *
+ ****************************************************************************/
+
+/* open_plugins.rock interfaces with the open_plugin core
+ *
+ * When opened directly it acts as a viewer for the plugin.dat file
+ * this allows you to edit the paths and parameters for
+ * core shortcuts as well as your added plugins
+ *
+ * If a plugin is supplied to the viewer it is added to the dat file
+ *
+ * If instead the plugin has previously been added then it is run
+ * with the parameters previously supplied
+ */
+
+#include "plugin.h"
+#include "lang_enum.h"
+#include "../open_plugin.h"
+
+#define ROCK_EXT "rock"
+#define OP_EXT "opx"
+
+#define OP_PLUGIN_RESTART (PLUGIN_GOTO_PLUGIN | 0x8000)
+
+#define MENU_ID_MAIN "0"
+#define MENU_ID_EDIT "1"
+
+static int fd_dat;
+static struct gui_synclist lists;
+struct open_plugin_entry_t op_entry;
+const off_t op_entry_sz = sizeof(struct open_plugin_entry_t);
+
+/* we only need the names for the first menu so don't bother reading paths yet */
+const off_t op_name_sz = OPEN_PLUGIN_NAMESZ + (op_entry.name - (char*)&op_entry);
+
+static uint32_t op_entry_add_path(const char *key, const char *plugin, const char *parameter);
+
+static bool _yesno_pop(const char* text)
+{
+ const char *lines[]={text};
+ const struct text_message message={lines, 1};
+ bool ret = (rb->gui_syncyesno_run(&message,NULL,NULL)== YESNO_YES);
+ FOR_NB_SCREENS(i)
+ rb->screens[i]->clear_viewport();
+ return ret;
+}
+
+static size_t pathbasename(const char *name, const char **nameptr)
+{
+ const char *p = name;
+ const char *q = p;
+ const char *r = q;
+
+ while (*(p = GOBBLE_PATH_SEPCH(p)))
+ {
+ q = p;
+ p = GOBBLE_PATH_COMP(++p);
+ r = p;
+ }
+
+ if (r == name && p > name)
+ q = p, r = q--; /* root - return last slash */
+ /* else path is an empty string */
+
+ *nameptr = q;
+ return r - q;
+}
+
+static bool op_entry_read(int fd, int selected_item, off_t data_sz)
+{
+ rb->memset(&op_entry, 0, op_entry_sz);
+ op_entry.lang_id = -1;
+ return ((selected_item >= 0) &&
+ (rb->lseek(fd, selected_item * op_entry_sz, SEEK_SET) >= 0) &&
+ (rb->read(fd, &op_entry, data_sz) == data_sz));
+}
+
+static bool op_entry_read_name(int fd, int selected_item)
+{
+ return op_entry_read(fd, selected_item, op_name_sz);
+}
+
+static void op_entry_export(int selection)
+{
+ int len;
+ int fd = -1;
+ char filename [MAX_PATH + 1];
+
+ if (!op_entry_read(fd_dat, selection, op_entry_sz))
+ goto failure;
+
+ rb->snprintf(filename, MAX_PATH, "%s/%s", PLUGIN_APPS_DIR, op_entry.name);
+
+ if( !rb->kbd_input( filename, MAX_PATH, NULL ) )
+ {
+ len = rb->strlen(filename);
+ if(len > 4 && filename[len] != PATH_SEPCH &&
+ rb->strcasecmp(&((filename)[len-4]), "." OP_EXT) != 0)
+ {
+ rb->strcat(filename, "." OP_EXT);
+ }
+
+ fd = rb->open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+
+ if (fd >= 0 && rb->write(fd, &op_entry, op_entry_sz) == op_entry_sz)
+ {
+ rb->close(fd);
+ rb->splashf( 1*HZ, "File Saved (%s)", filename );
+ return;
+ }
+ rb->close(fd);
+ }
+
+failure:
+ rb->splashf( 2*HZ, "Save Failed (%s)", filename );
+
+}
+
+static void op_entry_set_name(void)
+{
+ char tmp_buf[OPEN_PLUGIN_NAMESZ+1];
+ rb->strlcpy(tmp_buf, op_entry.name, OPEN_PLUGIN_NAMESZ);
+ if (rb->kbd_input(tmp_buf, OPEN_PLUGIN_NAMESZ, NULL) >= 0)
+ rb->strlcpy(op_entry.name, tmp_buf, OPEN_PLUGIN_NAMESZ);
+}
+
+static int op_entry_set_path(void)
+{
+ int ret = 0;
+ struct browse_context browse;
+ char tmp_buf[OPEN_PLUGIN_BUFSZ+1];
+
+ if (op_entry.path[0] == '\0')
+ rb->strcpy(op_entry.path, PLUGIN_DIR"/");
+
+ rb->browse_context_init(&browse, SHOW_ALL, BROWSE_SELECTONLY, "",
+ Icon_Plugin, op_entry.path, NULL);
+
+ browse.buf = tmp_buf;
+ browse.bufsize = OPEN_PLUGIN_BUFSZ;
+
+ if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS)
+ {
+ ret = rb->strlcpy(op_entry.path, tmp_buf, OPEN_PLUGIN_BUFSZ);
+ if (ret > OPEN_PLUGIN_BUFSZ)
+ ret = 0;
+ }
+ return ret;
+}
+
+static int op_entry_set_param_path(void)
+{
+ int ret = 0;
+ struct browse_context browse;
+ char tmp_buf[OPEN_PLUGIN_BUFSZ+1];
+
+ if (op_entry.param[0] == '\0')
+ rb->strcpy(tmp_buf, "/");
+ else
+ rb->strcpy(tmp_buf, op_entry.param);
+
+ rb->browse_context_init(&browse, SHOW_ALL, BROWSE_SELECTONLY, "",
+ Icon_Plugin, tmp_buf, NULL);
+
+ browse.buf = tmp_buf;
+ browse.bufsize = OPEN_PLUGIN_BUFSZ;
+
+ if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS)
+ {
+ ret = rb->strlcpy(op_entry.param, tmp_buf, OPEN_PLUGIN_BUFSZ);
+ if (ret > OPEN_PLUGIN_BUFSZ)
+ ret = 0;
+ }
+ return ret;
+}
+
+static void op_entry_set_param(void)
+{
+ if (_yesno_pop(ID2P(LANG_BROWSE)))
+ op_entry_set_param_path();
+
+ char tmp_buf[OPEN_PLUGIN_BUFSZ+1];
+ rb->strlcpy(tmp_buf, op_entry.param, OPEN_PLUGIN_BUFSZ);
+ if (rb->kbd_input(tmp_buf, OPEN_PLUGIN_BUFSZ, NULL) >= 0)
+ rb->strlcpy(op_entry.param, tmp_buf, OPEN_PLUGIN_BUFSZ);
+}
+
+static int op_et_exclude_hash(struct open_plugin_entry_t *op_entry, int item, void *data)
+{
+ (void)item;
+
+ if (op_entry->hash == 0 || op_entry->name[0] == '\0')
+ return 0;
+
+ if (data)
+ {
+ uint32_t *hash = data;
+ if (op_entry->hash != *hash)
+ return 1;
+ }
+ return 0;
+}
+
+static int op_et_exclude_builtin(struct open_plugin_entry_t *op_entry, int item, void *data)
+{
+ (void)item;
+ (void)data;
+
+ if (op_entry->lang_id >= 0)
+ return 0;
+ else if(op_entry->hash == 0 || op_entry->name[0] == '\0')
+ return 0;
+
+ return 1;
+}
+
+static int op_et_exclude_user(struct open_plugin_entry_t *op_entry, int item, void *data)
+{
+ (void)item;
+ (void)data;
+
+ if (op_entry->lang_id < 0)
+ return 0;
+ else if (op_entry->hash == 0 || op_entry->name[0] == '\0')
+ return 0;
+
+ return 1;
+}
+
+static int op_entry_transfer(int fd, int fd_tmp,
+ int(*compfn)(struct open_plugin_entry_t*, int, void*),
+ void *data)
+{
+ int entries = -1;
+ if (fd_tmp && fd && rb->lseek(fd, 0, SEEK_SET) == 0)
+ {
+ entries = 0;
+ while (rb->read(fd, &op_entry, op_entry_sz) == op_entry_sz)
+ {
+ if (compfn && compfn(&op_entry, entries, data) > 0)
+ {
+ rb->write(fd_tmp, &op_entry, op_entry_sz);
+ entries++;
+ }
+ }
+ }
+ return entries + 1;
+}
+
+static uint32_t op_entry_add_path(const char *key, const char *plugin, const char *parameter)
+{
+ int len;
+ uint32_t hash;
+ char *pos = "";;
+ int fd_tmp = -1;
+
+ if (key)
+ {
+ open_plugin_get_hash(key, &hash);
+ op_entry.hash = hash;
+ }
+ else if (op_entry.lang_id < 0 && plugin)
+ {
+ /* need to keep the old hash so we can remove the old entry */
+ hash = op_entry.hash;
+ open_plugin_get_hash(plugin, &op_entry.hash);
+ }
+ else
+ hash = op_entry.hash;
+
+ if (plugin)
+ {
+ /* name */
+ if (pathbasename(plugin, (const char **)&pos) == 0)
+ pos = "\0";
+ if (op_entry.name[0] == '\0' || op_entry.lang_id >= 0)
+ rb->strlcpy(op_entry.name, pos, OPEN_PLUGIN_NAMESZ);
+
+ len = rb->strlen(pos);
+ if(len > 5 && rb->strcasecmp(&(pos[len-5]), "." ROCK_EXT) == 0)
+ {
+ fd_tmp = rb->open(OPEN_PLUGIN_DAT ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd_tmp < 0)
+ return 0;
+
+ /* path */
+ if (plugin != op_entry.path)
+ rb->strlcpy(op_entry.path, plugin, OPEN_PLUGIN_BUFSZ);
+
+ if(parameter)
+ {
+ if (parameter[0] == '\0' &&
+ _yesno_pop(ID2P(LANG_PARAMETER)))
+ {
+ op_entry_set_param();
+ }
+ else
+ rb->strlcpy(op_entry.param, parameter, OPEN_PLUGIN_BUFSZ);
+ }
+
+ rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */
+ }
+ else
+ {
+ if (op_entry.lang_id != LANG_SHORTCUTS)
+ rb->splashf(HZ / 2, rb->str(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), pos);
+ return 0;
+ }
+ }
+
+ if (op_entry_transfer(fd_dat, fd_tmp, op_et_exclude_hash, &hash) > 0)
+ {
+ rb->close(fd_tmp);
+ rb->close(fd_dat);
+ rb->remove(OPEN_PLUGIN_DAT);
+ rb->rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT);
+ }
+ else
+ {
+ rb->close(fd_tmp);
+ rb->remove(OPEN_PLUGIN_DAT ".tmp");
+ hash = 0;
+ }
+
+ return hash;
+}
+
+void op_entry_browse_add(int selection)
+{
+ char* key;
+ op_entry_read(fd_dat, selection, op_entry_sz);
+ if (op_entry_set_path() > 0)
+ {
+ if (op_entry.lang_id >= 0)
+ key = rb->str(op_entry.lang_id);
+ else
+ key = op_entry.path;
+
+ op_entry_add_path(key, op_entry.path, NULL);
+ }
+}
+
+static void op_entry_remove(int selection)
+{
+
+ int entries = rb->lseek(fd_dat, 0, SEEK_END) / op_entry_sz;
+ int32_t hash = 0;
+ int lang_id = -1;
+
+ if (entries > 0 && _yesno_pop(ID2P(LANG_REMOVE)))
+ {
+ op_entry_read(fd_dat, selection, op_entry_sz);
+ if (rb->lseek(fd_dat, selection * op_entry_sz, SEEK_SET) >= 0)
+ {
+ if (op_entry.lang_id >= 0)
+ {
+ lang_id = op_entry.lang_id;
+ hash = op_entry.hash;
+ }
+ rb->memset(&op_entry, 0, op_entry_sz);
+ op_entry.lang_id = lang_id;
+ op_entry.hash = hash;
+ rb->write(fd_dat, &op_entry, op_entry_sz);
+ }
+ }
+}
+
+static void op_entry_remove_empty(void)
+{
+ bool resave = false;
+ if (fd_dat && rb->lseek(fd_dat, 0, SEEK_SET) == 0)
+ {
+ while (resave == false &&
+ rb->read(fd_dat, &op_entry, op_entry_sz) == op_entry_sz)
+ {
+ if (op_entry.hash == 0)
+ resave = true;
+ }
+ }
+
+ if (resave)
+ {
+ int fd_tmp = rb->open(OPEN_PLUGIN_DAT ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd_tmp < 0)
+ return;
+
+ if ((op_entry_transfer(fd_dat, fd_tmp, &op_et_exclude_builtin, NULL)
+ + op_entry_transfer(fd_dat, fd_tmp, &op_et_exclude_user, NULL)) > 0)
+ {
+ rb->close(fd_tmp);
+ rb->close(fd_dat);
+ rb->remove(OPEN_PLUGIN_DAT);
+ rb->rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT);
+ }
+ else
+ {
+ rb->close(fd_tmp);
+ rb->remove(OPEN_PLUGIN_DAT ".tmp");
+ }
+ }
+
+}
+
+static int op_entry_run(void)
+{
+ int ret = PLUGIN_ERROR;
+ char* path;
+ char* param;
+ if (op_entry.hash != 0 && op_entry.path[0] != '\0')
+ {
+ rb->splash(1, ID2P(LANG_OPEN_PLUGIN));
+ path = op_entry.path;
+ param = op_entry.param;
+ if (param[0] == '\0')
+ param = NULL;
+
+ ret = rb->plugin_open(path, param);
+ }
+ return ret;
+}
+
+static const char* list_get_name_cb(int selected_item, void* data,
+ char* buf, size_t buf_len)
+{
+ /*TODO memoize names so we don't keep reading the disk when not necessary */
+ if (data == &MENU_ID_MAIN)
+ {
+ if (op_entry_read_name(fd_dat, selected_item))
+ {
+ if (op_entry.lang_id >= 0)
+ rb->snprintf(buf, buf_len, "%s [%s] ",
+ rb->str(op_entry.lang_id), op_entry.name);
+ else if (rb->strlcpy(buf, op_entry.name, buf_len) >= buf_len)
+ rb->strcpy(&buf[buf_len-10], " ...");
+ }
+ else
+ return "?";
+ }
+ else /* op_entry should already be loaded */
+ {
+ switch(selected_item)
+ {
+ case 0:
+ return ID2P(LANG_NAME);
+ case 1:
+ if (op_entry.lang_id >= 0)
+ rb->snprintf(buf, buf_len, "%s [%s] ",
+ rb->str(op_entry.lang_id), op_entry.name);
+ else if (rb->strlcpy(buf, op_entry.name, buf_len) >= buf_len)
+ rb->strcpy(&buf[buf_len-10], " ...");
+ break;
+ case 2:
+ return ID2P(LANG_DISPLAY_FULL_PATH);
+ case 3:
+ if (rb->strlcpy(buf, op_entry.path, buf_len) >= buf_len)
+ rb->strcpy(&buf[buf_len-10], " ...");
+ break;
+ case 4:
+ return ID2P(LANG_PARAMETER);
+ case 5:
+ if (op_entry.param[0] == '\0')
+ return "[NULL]";
+ else if (rb->strlcpy(buf, op_entry.param, buf_len) >= buf_len)
+ rb->strcpy(&buf[buf_len-10], " ...");
+ break;
+ case 6:
+ return "";
+ case 7:
+ return ID2P(LANG_BACK);
+ default:
+ return "?";
+ }
+ }
+
+ return buf;
+}
+
+static int list_voice_cb(int list_index, void* data)
+{
+ if (data == &MENU_ID_MAIN)
+ {
+ if (op_entry_read_name(fd_dat, list_index))
+ {
+ if (op_entry.lang_id >= 0)
+ {
+ rb->talk_id(op_entry.lang_id, false);
+ rb->talk_id(VOICE_PAUSE, true);
+ rb->talk_force_enqueue_next();
+ }
+ return rb->talk_spell(op_entry.name, false);
+ }
+ }
+ else
+ {
+ switch(list_index)
+ {
+ case 0:
+ rb->talk_id(LANG_NAME, false);
+ rb->talk_id(VOICE_PAUSE, true);
+
+ if (op_entry.lang_id >= 0)
+ {
+ rb->talk_id(op_entry.lang_id, true);
+ rb->talk_id(VOICE_PAUSE, true);
+ rb->talk_force_enqueue_next();
+ }
+ return rb->talk_spell(op_entry.name, false);
+ case 2:
+ return rb->talk_id(LANG_DISPLAY_FULL_PATH, false);
+ case 4:
+ return rb->talk_id(LANG_PARAMETER, false);
+ case 6:
+ return rb->talk_id(LANG_BACK, false);
+ default:
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static void synclist_set(char* menu_id, int selection, int items, int sel_size)
+{
+ if (selection < 0)
+ selection = 0;
+
+ rb->gui_synclist_init(&lists,list_get_name_cb,
+ menu_id, false, sel_size, NULL);
+
+ rb->gui_synclist_set_icon_callback(&lists,NULL);
+ rb->gui_synclist_set_voice_callback(&lists, list_voice_cb);
+ rb->gui_synclist_set_nb_items(&lists,items);
+ rb->gui_synclist_limit_scroll(&lists,true);
+ rb->gui_synclist_select_item(&lists, selection);
+ list_voice_cb(selection, menu_id);
+}
+
+static int context_menu_cb(int action,
+ const struct menu_item_ex *this_item,
+ struct gui_synclist *this_list)
+{
+ (void)this_item;
+
+ int selection = rb->gui_synclist_get_sel_pos(this_list);
+
+ if(action == ACTION_ENTER_MENUITEM)
+ {
+ if (selection == 0 &&
+ op_entry.lang_id >= 0 && op_entry.lang_id != LANG_OPEN_PLUGIN)
+ {
+ rb->gui_synclist_set_title(this_list,
+ rb->str(op_entry.lang_id), 0);
+ }
+ }
+ else if ((action == ACTION_STD_OK))
+ {
+ /*Run, Edit, Remove, Export, Blank, Import, Add, Back*/
+ switch(selection)
+ {
+ case 0:case 1:case 2:case 3:case 5:
+ return ACTION_STD_OK;
+ case 4: /*blank*/
+ break;
+ default:
+ return ACTION_STD_CANCEL;
+ }
+ rb->gui_synclist_draw(this_list); /* redraw */
+ return 0;
+ }
+
+ return action;
+}
+
+static void edit_menu(int selection)
+{
+ int selected_item;
+ bool exit = false;
+ int action = 0;
+
+ if (!op_entry_read(fd_dat, selection, op_entry_sz))
+ return;
+
+ uint32_t crc = rb->crc_32(&op_entry, op_entry_sz, 0xffffffff);
+
+ synclist_set(MENU_ID_EDIT, 2, 8, 2);
+ rb->gui_synclist_draw(&lists);
+
+ while (!exit)
+ {
+ action = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
+
+ if (rb->gui_synclist_do_button(&lists,&action,LIST_WRAP_UNLESS_HELD))
+ continue;
+ selected_item = rb->gui_synclist_get_sel_pos(&lists);
+ switch (action)
+ {
+ case ACTION_STD_OK:
+ if (selected_item == 0)
+ op_entry_set_name();
+ else if (selected_item == 2)
+ op_entry_set_path();
+ else if (selected_item == 4)
+ op_entry_set_param();
+ else
+ exit = true;
+
+ rb->gui_synclist_draw(&lists);
+ break;
+ case ACTION_STD_CANCEL:
+ exit = true;
+ break;
+ }
+ }
+
+ if (crc != rb->crc_32(&op_entry, op_entry_sz, 0xffffffff) &&
+ _yesno_pop(ID2P(LANG_SAVE)) == true)
+ {
+ char *param = op_entry.param;
+ if (param[0] == '\0')
+ param = NULL;
+
+ op_entry_add_path(NULL, op_entry.path, param);
+ fd_dat = rb->open(OPEN_PLUGIN_DAT, O_RDWR, 0666);
+ }
+}
+
+static int context_menu(int selection)
+{
+ int selected_item;
+ if (op_entry_read(fd_dat, selection, op_entry_sz))
+ {
+ MENUITEM_STRINGLIST(menu, op_entry.name, context_menu_cb,
+ ID2P(LANG_RUN), ID2P(LANG_EDIT), ID2P(LANG_REMOVE), ID2P(LANG_EXPORT),
+ ID2P(VOICE_BLANK), ID2P(LANG_ADD), ID2P(LANG_BACK));
+
+ selected_item = rb->do_menu(&menu, 0, NULL, false);
+ switch (selected_item)
+ {
+ case 0: /*run*/
+ return PLUGIN_GOTO_PLUGIN;
+ case 1: /*edit*/
+ edit_menu(selection);
+ break;
+ case 2: /*remove*/
+ op_entry_remove(selection);
+ break;
+ case 3: /*export*/
+ op_entry_export(selection);
+ break;
+ case 4: /*blank*/
+ break;
+ case 5: /*add*/
+ op_entry_browse_add(-1);
+ rb->plugin_open(rb->plugin_get_current_filename(), "\0");
+ return OP_PLUGIN_RESTART;
+ default:
+ break;
+
+ }
+ return PLUGIN_OK;
+ }
+ return PLUGIN_ERROR;
+}
+
+enum plugin_status plugin_start(const void* parameter)
+{
+ int ret = PLUGIN_OK;
+ uint32_t hash = 0;
+ int item = -1;
+ int selection = -1;
+ int action;
+ int items;
+ int len;
+ int fd_opx;
+ off_t filesize;
+ char *path;
+ bool exit = false;
+
+ const int creat_flags = O_RDWR | O_CREAT;
+ fd_dat = rb->open(OPEN_PLUGIN_DAT, creat_flags, 0666);
+ if (!fd_dat)
+ exit = true;
+
+ items = rb->lseek(fd_dat, 0, SEEK_END) / op_entry_sz;
+ if (items == 0 && !parameter)
+ {
+ rb->plugin_open(rb->plugin_get_current_filename(), NULL);
+ rb->close(fd_dat);
+ return PLUGIN_GOTO_PLUGIN;
+ }
+
+ if (parameter)
+ {
+ path = (char*)parameter;
+ len = rb->strlen(path);
+ if(len > 4 && rb->strcasecmp(&((path)[len-4]), "." OP_EXT) == 0)
+ {
+ fd_opx = rb->open(path, O_RDONLY, 0666);
+ if (fd_opx)
+ {
+ filesize = rb->filesize(fd_opx);
+ if (filesize == op_entry_sz)
+ {
+
+ if (op_entry_read(fd_opx, 0, op_entry_sz))
+ {
+ exit = true;
+ ret = op_entry_run();
+ }
+ else
+ rb->splashf(HZ, rb->str(LANG_READ_FAILED), path);
+ }
+ else if (filesize != 0)
+ rb->splashf(2 * HZ, rb->str(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), path);
+
+ rb->close(fd_opx);
+ }
+ }
+ else
+ {
+ open_plugin_get_hash(parameter, &hash);
+ rb->lseek(fd_dat, 0, SEEK_SET);
+ while (rb->read(fd_dat, &op_entry, op_entry_sz) == op_entry_sz)
+ {
+ item++;
+ if (op_entry.hash == hash)
+ {
+ selection = item;
+ break;
+ }
+ }
+
+ if (selection >= 0)
+ {
+ if (op_entry_read(fd_dat, selection, op_entry_sz))
+ {
+ if (op_entry_run() == PLUGIN_GOTO_PLUGIN)
+ return PLUGIN_GOTO_PLUGIN;
+ }
+ }
+ else
+ {
+ op_entry_read(fd_dat, selection, op_entry_sz);
+ op_entry_add_path(parameter, parameter, "\0");
+ selection = 0;
+ items++;
+ fd_dat = rb->open(OPEN_PLUGIN_DAT, creat_flags, 0666);
+ if (!fd_dat)
+ exit = true;
+ }
+ }/* OP_EXT */
+
+ }
+
+ if (!exit)
+ {
+ synclist_set(MENU_ID_MAIN, selection, items, 1);
+ rb->gui_synclist_draw(&lists);
+
+ while (!exit)
+ {
+ action = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
+
+ if (rb->gui_synclist_do_button(&lists,&action,LIST_WRAP_UNLESS_HELD))
+ continue;
+ selection = rb->gui_synclist_get_sel_pos(&lists);
+ switch (action)
+ {
+ case ACTION_STD_CONTEXT:
+ ret = context_menu(selection);
+ if (ret == OP_PLUGIN_RESTART)
+ {
+ ret = PLUGIN_GOTO_PLUGIN;
+ exit = true;
+ break;
+ }
+ else if (ret != PLUGIN_GOTO_PLUGIN)
+ {
+ synclist_set(MENU_ID_MAIN, selection, items, 1);
+ rb->gui_synclist_draw(&lists);
+ break;
+ }
+ case ACTION_STD_OK:
+ if (op_entry_read(fd_dat, selection, op_entry_sz))
+ {
+ ret = op_entry_run();
+ exit = true;
+ }
+ break;
+ case ACTION_STD_CANCEL:
+ {
+ selection = -2;
+ exit = true;
+ break;
+ }
+ }
+ }
+ op_entry_remove_empty();
+ }
+ rb->close(fd_dat);
+ if (ret != PLUGIN_OK)
+ return ret;
+ else
+ return PLUGIN_OK;
+}
diff --git a/apps/plugins/plugins.make b/apps/plugins/plugins.make
index d395c00e82..71eaeb52c0 100644
--- a/apps/plugins/plugins.make
+++ b/apps/plugins/plugins.make
@@ -86,6 +86,11 @@ $(OVERLAYREF_LDS): $(PLUGIN_LDS)
$(BUILDDIR)/credits.raw credits.raw: $(DOCSDIR)/CREDITS
$(call PRINTS,Create credits.raw)perl $(APPSDIR)/plugins/credits.pl < $< > $(BUILDDIR)/$(@F)
+$(BUILDDIR)/apps/plugins/open_plugins.opx:
+ $(call PRINTS,MK open_plugins.opx) touch $< $(BUILDDIR)/apps/plugins/open_plugins.opx
+
+$(BUILDDIR)/apps/plugins/open_plugins.rock: $(BUILDDIR)/apps/plugins/open_plugins.opx
+
# special dependencies
$(BUILDDIR)/apps/plugins/wav2wv.rock: $(RBCODEC_BLD)/codecs/libwavpack.a $(PLUGIN_LIBS)
diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config
index 84f096a409..2bf052bb20 100644
--- a/apps/plugins/viewers.config
+++ b/apps/plugins/viewers.config
@@ -77,6 +77,8 @@ mpv,viewers/mpegplayer,4
m2v,viewers/mpegplayer,4
iriver,viewers/iriver_flash,3
tap,viewers/zxbox,12
+opx,viewers/open_plugins,-
+rock,viewers/open_plugins,-
sna,viewers/zxbox,12
tzx,viewers/zxbox,12
z80,viewers/zxbox,12