e1553d860d
Adding a checksum over the struct offset will allow checking for compatibility across machines rather than using packed structs to ensure compability For any file created by the user from the device this isn't really a concern But for files between machines, across installs (sim v device), possibly even across compilers this at least will alert the user rather than returning junk data Change-Id: Id0531bbaa7013dce24dece270849f0a10ac99c20
909 lines
25 KiB
C
909 lines
25 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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 ROCK_LEN 5
|
|
|
|
#define OP_EXT "opx"
|
|
#define OP_LEN 4
|
|
|
|
#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;
|
|
static const uint32_t open_plugin_csum = OPEN_PLUGIN_CHECKSUM;
|
|
static 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, bool use_key);
|
|
|
|
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 int op_entry_checksum(void)
|
|
{
|
|
if (op_entry.checksum != open_plugin_csum)
|
|
{
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int op_entry_read_opx(const char *path)
|
|
{
|
|
int ret = -1;
|
|
off_t filesize;
|
|
int fd_opx;
|
|
int len;
|
|
|
|
len = rb->strlen(path);
|
|
if(len > OP_LEN && rb->strcasecmp(&((path)[len-OP_LEN]), "." OP_EXT) == 0)
|
|
{
|
|
fd_opx = rb->open(path, O_RDONLY);
|
|
if (fd_opx >= 0)
|
|
{
|
|
filesize = rb->filesize(fd_opx);
|
|
ret = filesize;
|
|
if (filesize == op_entry_sz && !op_entry_read(fd_opx, 0, op_entry_sz))
|
|
ret = 0;
|
|
else if (op_entry_checksum() <= 0)
|
|
ret = 0;
|
|
rb->close(fd_opx);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
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) || op_entry_checksum() <= 0)
|
|
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 > OP_LEN && filename[len] != PATH_SEPCH &&
|
|
rb->strcasecmp(&((filename)[len-OP_LEN]), "." 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_checksum(void)
|
|
{
|
|
op_entry.checksum = open_plugin_csum;
|
|
}
|
|
|
|
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, rb->str(LANG_ADD),
|
|
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 >= 0 && fd >= 0 && 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 && op_entry_checksum() > 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, bool use_key)
|
|
{
|
|
int len;
|
|
uint32_t hash;
|
|
uint32_t newhash;
|
|
char *pos = "";;
|
|
int fd_tmp = -1;
|
|
use_key = (use_key == true && key != NULL);
|
|
|
|
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, &newhash);
|
|
op_entry.hash = newhash;
|
|
}
|
|
else
|
|
hash = op_entry.hash;
|
|
|
|
if (plugin)
|
|
{
|
|
/* name */
|
|
if (use_key)
|
|
{
|
|
op_entry.lang_id = -1;
|
|
rb->strlcpy(op_entry.name, key, OPEN_PLUGIN_NAMESZ);
|
|
}
|
|
|
|
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 > ROCK_LEN && rb->strcasecmp(&(pos[len-ROCK_LEN]), "." 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 if (parameter != op_entry.param)
|
|
rb->strlcpy(op_entry.param, parameter, OPEN_PLUGIN_BUFSZ);
|
|
|
|
/* hash on the parameter path if it is a file */
|
|
if (op_entry.lang_id <0 && key == op_entry.path &&
|
|
rb->file_exists(op_entry.param))
|
|
{
|
|
open_plugin_get_hash(op_entry.path, &newhash);
|
|
op_entry.hash = newhash;
|
|
}
|
|
}
|
|
op_entry_set_checksum();
|
|
rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */
|
|
}
|
|
else if(op_entry_read_opx(plugin) == op_entry_sz)
|
|
{
|
|
fd_tmp = rb->open(OPEN_PLUGIN_DAT ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
if (fd_tmp < 0)
|
|
return 0;
|
|
|
|
if (op_entry.lang_id <0 && rb->file_exists(op_entry.param))
|
|
open_plugin_get_hash(op_entry.param, &hash);
|
|
else
|
|
open_plugin_get_hash(op_entry.path, &hash);
|
|
|
|
op_entry.hash = hash;
|
|
op_entry_set_checksum();
|
|
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, false);
|
|
}
|
|
}
|
|
|
|
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_user, NULL)
|
|
+ op_entry_transfer(fd_dat, fd_tmp, &op_et_exclude_builtin, 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 == (void*) &MENU_ID_MAIN) /* check address */
|
|
{
|
|
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 == (void*) &MENU_ID_MAIN) /* check address */
|
|
{
|
|
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, false);
|
|
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 res;
|
|
char *path;
|
|
bool exit = false;
|
|
|
|
const int creat_flags = O_RDWR | O_CREAT;
|
|
|
|
reopen_datfile:
|
|
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 (parameter)
|
|
{
|
|
path = (char*)parameter;
|
|
while (path[0] == ' ')
|
|
path++;
|
|
|
|
if (rb->strncasecmp(path, "-add", 4) == 0)
|
|
{
|
|
parameter = NULL;
|
|
op_entry_browse_add(-1);
|
|
rb->close(fd_dat);
|
|
goto reopen_datfile;
|
|
}
|
|
}
|
|
|
|
if (parameter)
|
|
{
|
|
path = (char*)parameter;
|
|
res = op_entry_read_opx(path);
|
|
if (res >= 0)
|
|
{
|
|
if (res == op_entry_sz)
|
|
{
|
|
exit = true;
|
|
ret = op_entry_run();
|
|
}
|
|
}
|
|
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))
|
|
{
|
|
ret = op_entry_run();
|
|
if (ret == PLUGIN_GOTO_PLUGIN)
|
|
exit = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
op_entry_read(fd_dat, selection, op_entry_sz);
|
|
if (op_entry_add_path(parameter, parameter, "\0", false) > 0)
|
|
{
|
|
selection = 0;
|
|
items++;
|
|
fd_dat = rb->open(OPEN_PLUGIN_DAT, creat_flags, 0666);
|
|
if (!fd_dat)
|
|
exit = true;
|
|
}
|
|
}
|
|
}/* OP_EXT */
|
|
}
|
|
|
|
if (items < 1 && !exit)
|
|
{
|
|
char* cur_filename = rb->plugin_get_current_filename();
|
|
|
|
if (op_entry_add_path(rb->str(LANG_ADD), cur_filename, "-add", true))
|
|
{
|
|
rb->close(fd_dat);
|
|
parameter = NULL;
|
|
goto reopen_datfile;
|
|
}
|
|
rb->close(fd_dat);
|
|
return PLUGIN_ERROR;
|
|
}
|
|
|
|
|
|
|
|
if (!exit)
|
|
{
|
|
synclist_set(MENU_ID_MAIN, selection, items, 1);
|
|
rb->gui_synclist_draw(&lists);
|
|
|
|
while (!exit && fd_dat >= 0)
|
|
{
|
|
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;
|
|
}
|