dfe12252bb
a plugin to view lastfm scrobbler logs uses print cell to give a spreadsheet view of scrobbler logs buffers the whole file if possible otherwise it reads entries from disk rudimentary text searching for columns include / exclude; all/any and case sensitive Change-Id: Id9616e5796658952fba4ea747f596cb77d6f34c0
2228 lines
70 KiB
C
2228 lines
70 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// __ \_/ ___\| |/ /| __ \ / __ \ \/ /
|
|
* Jukebox | | ( (__) ) \___| ( | \_\ ( (__) ) (
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2022 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.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "plugin.h"
|
|
#include "lang_enum.h"
|
|
|
|
#include "lib/action_helper.h"
|
|
#include "lib/button_helper.h"
|
|
#include "lib/pluginlib_actions.h"
|
|
#include "lib/printcell_helper.h"
|
|
#include "lib/kbd_helper.h"
|
|
|
|
#ifdef ROCKBOX_HAS_LOGF
|
|
#define logf rb->logf
|
|
#else
|
|
#define logf(...) do { } while(0)
|
|
#endif
|
|
|
|
/* CORE_KEYREMAP_FILE */
|
|
#include "../core_keymap.h"
|
|
#define KMFDIR ROCKBOX_DIR
|
|
#define KMFEXT1 ".kmf"
|
|
#define KMFEXT2 ".kmf.old"
|
|
#define KMFUSER "user_keyremap"
|
|
#define MAX_BUTTON_COMBO 5
|
|
#define MAX_BUTTON_NAME 32
|
|
#define MAX_MENU_NAME 64
|
|
#define TEST_COUNTDOWN_MS 1590
|
|
|
|
struct context_flags {
|
|
char * name;
|
|
int flag;
|
|
};
|
|
|
|
/* flags added to context_name[] */
|
|
static struct context_flags context_flags[] = {
|
|
{"UNKNOWN", 0},/* index 0 is an Error */
|
|
#ifndef HAS_BUTTON_HOLD
|
|
{"LOCKED", CONTEXT_LOCKED},
|
|
#endif
|
|
/*{"PLUGIN", CONTEXT_PLUGIN}, need a custom action list and a way to supply */
|
|
#if BUTTON_REMOTE != 0
|
|
{"REMOTE", CONTEXT_REMOTE},
|
|
#ifndef HAS_BUTTON_HOLD
|
|
{"REMOTE_LOCKED", CONTEXT_REMOTE | CONTEXT_LOCKED},
|
|
#endif
|
|
#endif /* BUTTON_REMOTE != 0 */
|
|
};
|
|
|
|
static struct keyremap_buffer_t {
|
|
char * buffer;
|
|
size_t buf_size;
|
|
char *front;
|
|
char *end;
|
|
} keyremap_buffer;
|
|
|
|
|
|
struct action_mapping_t {
|
|
int context;
|
|
int display_pos;
|
|
struct button_mapping map;
|
|
};
|
|
|
|
static struct user_context_data_t {
|
|
struct action_mapping_t *ctx_map;
|
|
int ctx_count;
|
|
struct action_mapping_t *act_map;
|
|
int act_count;
|
|
} ctx_data;
|
|
|
|
/* set keys keymap */
|
|
static struct setkeys_data_t {
|
|
/* save state in the set keys action view list */
|
|
int view_columns;
|
|
int view_lastcol;
|
|
uint32_t crc32;
|
|
} keyset;
|
|
|
|
/* test keys data */
|
|
static struct testkey_data_t {
|
|
struct button_mapping *keymap;
|
|
int action;
|
|
int context;
|
|
int index;
|
|
int countdown;
|
|
} keytest;
|
|
|
|
static struct context_menu_data_t {
|
|
char *menuid;
|
|
int ctx_index;
|
|
int act_index;
|
|
int act_edit_index;
|
|
//const char * ctx_fmt;
|
|
const char * act_fmt;
|
|
} ctx_menu_data;
|
|
#define ACTIONFMT_LV0 "$%s$%s$%s$%s"
|
|
#define ACTVIEW_HEADER "$Context$Action$Button$PreBtn"
|
|
|
|
#define GOTO_ACTION_DEFAULT_HANDLER (PLUGIN_OK + 1)
|
|
#define MENU_ID(x) (((void*)&mainmenu[x]))
|
|
#define MENU_MAX_DEPTH 4
|
|
/* this enum sets menu order */
|
|
enum {
|
|
M_ROOT = 0,
|
|
M_SETKEYS,
|
|
M_TESTKEYS,
|
|
M_RESETKEYS,
|
|
M_EXPORTKEYS,
|
|
M_IMPORTKEYS,
|
|
M_SAVEKEYS,
|
|
M_LOADKEYS,
|
|
M_DELKEYS,
|
|
M_TMPCORE,
|
|
M_SETCORE,
|
|
M_DELCORE,
|
|
M_EXIT,
|
|
M_LAST_MAINITEM, //MAIN MENU ITEM COUNT
|
|
/*Menus not directly accessible from main menu*/
|
|
M_ACTIONS = M_LAST_MAINITEM,
|
|
M_BUTTONS,
|
|
M_CONTEXTS,
|
|
M_CONTEXT_EDIT,
|
|
M_LAST_ITEM, //ITEM COUNT
|
|
};
|
|
|
|
struct mainmenu { const char *name; void *menuid; int index; int items;};
|
|
static struct mainmenu mainmenu[M_LAST_ITEM] = {
|
|
#define MENU_ITEM(ID, NAME, COUNT) [ID]{NAME, MENU_ID(ID), (int)ID, (int)COUNT}
|
|
MENU_ITEM(M_ROOT, "Key Remap Plugin", M_LAST_MAINITEM - 1),
|
|
MENU_ITEM(M_SETKEYS, "Edit Keymap", 1),
|
|
MENU_ITEM(M_TESTKEYS, "Test Keymap", 4),
|
|
MENU_ITEM(M_RESETKEYS, "Reset Keymap", 1),
|
|
MENU_ITEM(M_EXPORTKEYS, "Export Text Keymap", 1),
|
|
MENU_ITEM(M_IMPORTKEYS, "Import Text Keymap", 1),
|
|
MENU_ITEM(M_SAVEKEYS, "Save Native Keymap", 1),
|
|
MENU_ITEM(M_LOADKEYS, "Load Native Keymaps", 1),
|
|
MENU_ITEM(M_DELKEYS, "Delete Keymaps", 1),
|
|
MENU_ITEM(M_TMPCORE, "Temp Core Remap", 1),
|
|
MENU_ITEM(M_SETCORE, "Set Core Remap", 1),
|
|
MENU_ITEM(M_DELCORE, "Remove Core Remap", 1),
|
|
MENU_ITEM(M_EXIT, ID2P(LANG_MENU_QUIT), 0),
|
|
MENU_ITEM(M_ACTIONS, "Actions", LAST_ACTION_PLACEHOLDER),
|
|
MENU_ITEM(M_BUTTONS, "Buttons", -1), /* Set at runtime in plugin_start: */
|
|
MENU_ITEM(M_CONTEXTS, "Contexts", LAST_CONTEXT_PLACEHOLDER * ARRAYLEN(context_flags)),
|
|
MENU_ITEM(M_CONTEXT_EDIT, "", 5),
|
|
#undef MENU_ITEM
|
|
};
|
|
|
|
DIR* kmffiles_dirp = NULL;
|
|
static int core_savecount = 0;/* so we don't overwrite the last .old file with revisions */
|
|
|
|
/* global lists, for everything */
|
|
static struct gui_synclist lists;
|
|
|
|
static void menu_useract_set_positions(void);
|
|
static size_t lang_strlcpy(char * dst, const char *src, size_t len)
|
|
{
|
|
unsigned char **language_strings = rb->language_strings;
|
|
return rb->strlcpy(dst, P2STR((unsigned char*)src), len);
|
|
}
|
|
|
|
/* Menu stack macros */
|
|
static int mlastsel[MENU_MAX_DEPTH * 2 + 1] = {0};
|
|
#define PUSH_MENU(id, item) \
|
|
if(mlastsel[0] < MENU_MAX_DEPTH * 2) {mlastsel[++mlastsel[0]] = id;mlastsel[++mlastsel[0]] = item;}
|
|
#define POP_MENU(id, item) { item = (mlastsel[0] > 0 ? mlastsel[mlastsel[0]--]:0); \
|
|
id = (mlastsel[0] > 0 ? mlastsel[mlastsel[0]--]:0); }
|
|
#define PEEK_MENU_ITEM(item) { item = (mlastsel[0] > 0 ? mlastsel[mlastsel[0]]:0);}
|
|
#define PEEK_MENU_ID(id) {id = (mlastsel[0] > 1 ? mlastsel[mlastsel[0]-1]:0);}
|
|
#define PEEK_MENU(id, item) PEEK_MENU_ITEM(item) PEEK_MENU_ID(id)
|
|
#define SET_MENU_ITEM(item) \
|
|
if(mlastsel[0] > 1) {mlastsel[mlastsel[0]] = item;}
|
|
|
|
static struct mainmenu *mainitem(int selected_item)
|
|
{
|
|
static struct mainmenu empty = {0};
|
|
if (selected_item >= 0 && selected_item < (int) ARRAYLEN(mainmenu))
|
|
return &mainmenu[selected_item];
|
|
else
|
|
return ∅
|
|
}
|
|
/* Forward Declarations */
|
|
static const char *edit_keymap_name_cb(int selected_item, void* data,char* buf, size_t buf_len);
|
|
static int keyremap_import_file(char *filenamebuf, size_t bufsz);
|
|
static void synclist_set(int id, int selected_item, int items, int sel_size);
|
|
|
|
static int prompt_filename(char *buf, size_t bufsz)
|
|
{
|
|
#define KBD_LAYOUT "abcdefghijklmnop\nqrstuvwxyz |()[]\n1234567890 /._-+\n\n" \
|
|
"\nABCDEFGHIJKLMNOP\nQRSTUVWXYZ |()[]\n1234567890 /._-+"
|
|
unsigned short kbd[sizeof(KBD_LAYOUT) + 10];
|
|
unsigned short *kbd_p = kbd;
|
|
if (!kbd_create_layout(KBD_LAYOUT, kbd, sizeof(kbd)))
|
|
kbd_p = NULL;
|
|
|
|
#undef KBD_LAYOUT
|
|
return rb->kbd_input(buf, bufsz, kbd_p) + 1;
|
|
}
|
|
|
|
static void synclist_set_update(int id, int selected_item, int items, int sel_size)
|
|
{
|
|
SET_MENU_ITEM(lists.selected_item + 1); /* update selected for previous menu*/
|
|
synclist_set(id, selected_item, items, sel_size);
|
|
}
|
|
|
|
/* returns the actual context & index of the flag is passed in *flagidx */
|
|
static int ctx_strip_flagidx(int ctx, int *flagidx)
|
|
{
|
|
int ctx_out = ctx % (LAST_CONTEXT_PLACEHOLDER);
|
|
*flagidx = 0;
|
|
if (ctx_out != ctx)
|
|
{
|
|
while (ctx >= LAST_CONTEXT_PLACEHOLDER)
|
|
{
|
|
(*flagidx)++;
|
|
ctx -= LAST_CONTEXT_PLACEHOLDER;
|
|
}
|
|
if (*flagidx >= (int)ARRAYLEN(context_flags))
|
|
*flagidx = 0; /* unknown flag */
|
|
|
|
logf("%s ctx: (%d) %s flag idx: (%d) %s\n", __func__,
|
|
ctx, context_name(ctx), *flagidx, context_flags[*flagidx].name);
|
|
}
|
|
return ctx_out;
|
|
}
|
|
|
|
/* combines context name and flag name using '_' to join them */
|
|
static char *ctx_name_and_flag(int index)
|
|
{
|
|
static char ctx_namebuf[MAX_MENU_NAME];
|
|
char *ctx_name = "?";
|
|
int flagidx;
|
|
int ctx = ctx_strip_flagidx(index, &flagidx);
|
|
if (flagidx == 0)
|
|
{
|
|
ctx_name = context_name(ctx);
|
|
}
|
|
else if (flagidx >= 0 && flagidx < (int)ARRAYLEN(context_flags))
|
|
{
|
|
rb->snprintf(ctx_namebuf, sizeof(ctx_namebuf), "%s_%s",
|
|
context_name(ctx), context_flags[flagidx].name);
|
|
ctx_name = ctx_namebuf;
|
|
}
|
|
return ctx_name;
|
|
}
|
|
|
|
static int btnval_to_index(unsigned int btnvalue)
|
|
{
|
|
int index = -1;
|
|
for (int i = 0; i < available_button_count; i++)
|
|
{
|
|
const struct available_button *btn = &available_buttons[i];
|
|
if (btnvalue == btn->value)
|
|
{
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
static int btnval_to_name(char *buf, size_t bufsz, unsigned int btnvalue)
|
|
{
|
|
int index = btnval_to_index(btnvalue);
|
|
if (index >= 0)
|
|
{
|
|
return rb->snprintf(buf, bufsz, "%s", available_buttons[index].name);
|
|
}
|
|
else /* this provides names for button combos e.g.(BUTTON_UP|BUTTON_REPEAT) */
|
|
{
|
|
int res = get_button_names(buf, bufsz, btnvalue);
|
|
if (res > 0 && res <= (int)bufsz)
|
|
return res;
|
|
}
|
|
return rb->snprintf(buf, bufsz, "%s", "BUTTON_UNKNOWN");
|
|
}
|
|
|
|
static int keyremap_check_extension(const char* filename)
|
|
{
|
|
int found = 0;
|
|
unsigned int len = rb->strlen(filename);
|
|
if (len > sizeof(KMFEXT1) &&
|
|
rb->strcmp(&filename[len - sizeof(KMFEXT1) + 1], KMFEXT1) == 0)
|
|
{
|
|
found = 1;
|
|
}
|
|
else if (len > sizeof(KMFEXT2) &&
|
|
rb->strcmp(&filename[len - sizeof(KMFEXT2) + 1], KMFEXT2) == 0)
|
|
{
|
|
found = 2;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
static int keyremap_count_files(const char *directory)
|
|
{
|
|
int nfiles = 0;
|
|
DIR* kmfdirp = NULL;
|
|
kmfdirp = rb->opendir(directory);
|
|
if (kmfdirp != NULL)
|
|
{
|
|
struct dirent *entry;
|
|
while ((entry = rb->readdir(kmfdirp)))
|
|
{
|
|
/* skip directories */
|
|
if ((rb->dir_get_info(kmfdirp, entry).attribute & ATTR_DIRECTORY) != 0)
|
|
continue;
|
|
if (keyremap_check_extension(entry->d_name) > 0)
|
|
nfiles++;
|
|
}
|
|
rb->closedir(kmfdirp);
|
|
}
|
|
return nfiles;
|
|
}
|
|
|
|
static void keyremap_reset_buffer(void)
|
|
{
|
|
keyremap_buffer.front = keyremap_buffer.buffer;
|
|
keyremap_buffer.end = keyremap_buffer.front + keyremap_buffer.buf_size;
|
|
rb->memset(keyremap_buffer.front, 0, keyremap_buffer.buf_size);
|
|
ctx_data.ctx_map = (struct action_mapping_t*)keyremap_buffer.front;
|
|
ctx_data.ctx_count = 0;
|
|
ctx_data.act_map = (struct action_mapping_t*) keyremap_buffer.end;
|
|
ctx_data.act_count = 0;
|
|
|
|
}
|
|
|
|
static size_t keyremap_write_entries(int fd, struct button_mapping *data, int entry_count)
|
|
{
|
|
if (fd < 0 || entry_count <= 0 || !data)
|
|
goto fail;
|
|
size_t bytes_req = sizeof(struct button_mapping) * entry_count;
|
|
size_t bytes = rb->write(fd, data, bytes_req);
|
|
if (bytes == bytes_req)
|
|
return bytes_req;
|
|
fail:
|
|
return 0;
|
|
}
|
|
|
|
static size_t keyremap_write_header(int fd, int entry_count)
|
|
{
|
|
if (fd < 0 || entry_count < 0)
|
|
goto fail;
|
|
struct button_mapping header = {KEYREMAP_VERSION, KEYREMAP_HEADERID, entry_count};
|
|
return keyremap_write_entries(fd, &header, 1);
|
|
fail:
|
|
return 0;
|
|
}
|
|
|
|
static int keyremap_open_file(const char *filename, int *fd, size_t *fsize)
|
|
{
|
|
int count = 0;
|
|
|
|
while (filename && fd && fsize)
|
|
{
|
|
*fsize = 0;
|
|
*fd = rb->open(filename, O_RDONLY);
|
|
if (*fd)
|
|
{
|
|
*fsize = rb->filesize(*fd);
|
|
|
|
count = *fsize / sizeof(struct button_mapping);
|
|
|
|
if (count * sizeof(struct button_mapping) != *fsize)
|
|
{
|
|
count = -10;
|
|
break;
|
|
}
|
|
|
|
if (count > 1)
|
|
{
|
|
struct button_mapping header = {0};
|
|
rb->read(*fd, &header, sizeof(struct button_mapping));
|
|
if (KEYREMAP_VERSION == header.action_code &&
|
|
KEYREMAP_HEADERID == header.button_code &&
|
|
header.pre_button_code == count)
|
|
{
|
|
count--;
|
|
*fsize -= sizeof(struct button_mapping);
|
|
}
|
|
else /* Header mismatch */
|
|
{
|
|
count = -20;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static int keyremap_map_is_valid(struct action_mapping_t *amap, int context)
|
|
{
|
|
int ret = (amap->context == context ? 1: 0);
|
|
struct button_mapping entry = amap->map;
|
|
if (entry.action_code == (int)CONTEXT_STOPSEARCHING ||
|
|
entry.button_code == BUTTON_NONE)
|
|
{
|
|
ret = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct button_mapping *keyremap_create_temp(int *entries)
|
|
{
|
|
logf("%s()", __func__);
|
|
struct button_mapping *tempkeymap;
|
|
int action_offset = 1; /* start of action entries (ctx_count + 1)*/
|
|
int entry_count = 1;/* (ctx_count + ctx_count + act_count) */
|
|
int index = 0;
|
|
int i, j;
|
|
|
|
/* count includes a single stop sentinel for the list of contexts */
|
|
/* and a stop sentinel for each group of action remaps as well */
|
|
for (i = 0; i < ctx_data.ctx_count; i++)
|
|
{
|
|
int actions_this_ctx = 0;
|
|
int context = ctx_data.ctx_map[i].context;
|
|
/* how many actions are contained in each context? */
|
|
for (j = 0; j < ctx_data.act_count; j++)
|
|
{
|
|
if (keyremap_map_is_valid(&ctx_data.act_map[j], context) > 0)
|
|
{
|
|
actions_this_ctx++;
|
|
}
|
|
}
|
|
/* only count contexts with remapped actions */
|
|
if (actions_this_ctx != 0)
|
|
{
|
|
action_offset++;
|
|
entry_count += actions_this_ctx + 2;
|
|
}
|
|
}
|
|
size_t keymap_bytes = (entry_count) * sizeof(struct button_mapping);
|
|
*entries = entry_count;
|
|
logf("%s() entry count: %d", __func__, entry_count);
|
|
logf("keyremap bytes: %zu, avail: %zu", keymap_bytes,
|
|
(keyremap_buffer.end - keyremap_buffer.front));
|
|
if (keyremap_buffer.front + keymap_bytes < keyremap_buffer.end)
|
|
{
|
|
tempkeymap = (struct button_mapping *) keyremap_buffer.front;
|
|
rb->memset(tempkeymap, 0, keymap_bytes);
|
|
struct button_mapping *entry = &tempkeymap[index++];
|
|
for (i = 0; i < ctx_data.ctx_count; i++)
|
|
{
|
|
int actions_this_ctx = 0;
|
|
int flagidx;
|
|
int context = ctx_data.ctx_map[i].context;
|
|
/* how many actions are contained in each context? */
|
|
for (j = 0; j < ctx_data.act_count; j++)
|
|
{
|
|
if (keyremap_map_is_valid(&ctx_data.act_map[j], context) > 0)
|
|
{
|
|
actions_this_ctx += 1;
|
|
}
|
|
}
|
|
/*Don't save contexts with no actions */
|
|
if (actions_this_ctx == 0){ continue; }
|
|
|
|
/* convert context x flag to context | flag */
|
|
context = ctx_strip_flagidx(ctx_data.ctx_map[i].context, &flagidx);
|
|
context |= context_flags[flagidx].flag;
|
|
|
|
entry->action_code = CORE_CONTEXT_REMAP(context);
|
|
entry->button_code = action_offset; /* offset of first action entry */
|
|
entry->pre_button_code = actions_this_ctx; /* entries (excluding sentinel) */
|
|
entry = &tempkeymap[index++];
|
|
logf("keyremap found context: %d index: %d entries: %d",
|
|
context, action_offset, actions_this_ctx);
|
|
action_offset += actions_this_ctx + 1; /* including sentinel */
|
|
}
|
|
/* context sentinel */
|
|
entry->action_code = CONTEXT_STOPSEARCHING;;
|
|
entry->button_code = BUTTON_NONE;
|
|
entry->pre_button_code = BUTTON_NONE;
|
|
entry = &tempkeymap[index++];
|
|
|
|
for (i = 0; i < ctx_data.ctx_count; i++)
|
|
{
|
|
int actions_this_ctx = 0;
|
|
int context = ctx_data.ctx_map[i].context;
|
|
|
|
for (int j = 0; j < ctx_data.act_count; j++)
|
|
{
|
|
if (keyremap_map_is_valid(&ctx_data.act_map[j], context) > 0)
|
|
{
|
|
struct button_mapping map = ctx_data.act_map[j].map;
|
|
entry->action_code = map.action_code;
|
|
entry->button_code = map.button_code;
|
|
entry->pre_button_code = map.pre_button_code;
|
|
entry = &tempkeymap[index++];
|
|
actions_this_ctx++;
|
|
logf("keyremap: found ctx: %d, act: %d btn: %d pbtn: %d",
|
|
context, map.action_code, map.button_code, map.pre_button_code);
|
|
}
|
|
}
|
|
/*Don't save sentinel for contexts with no actions */
|
|
if (actions_this_ctx == 0){ continue; }
|
|
|
|
/* action sentinel */
|
|
entry->action_code = CONTEXT_STOPSEARCHING;;
|
|
entry->button_code = BUTTON_NONE;
|
|
entry->pre_button_code = BUTTON_NONE;
|
|
entry = &tempkeymap[index++];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rb->splashf(HZ *2, "Out of Memory");
|
|
logf("keyremap: create temp OOM");
|
|
*entries = 0;
|
|
tempkeymap = NULL;
|
|
}
|
|
|
|
return tempkeymap;
|
|
}
|
|
|
|
static int keyremap_save_current(const char *filename)
|
|
{
|
|
int status = 0;
|
|
int entry_count;/* (ctx_count + ctx_count + act_count + 1) */
|
|
|
|
struct button_mapping *keymap = keyremap_create_temp(&entry_count);
|
|
|
|
if (keymap == NULL || entry_count <= 3) /* there should be atleast 4 entries */
|
|
return status;
|
|
keyset.crc32 =
|
|
rb->crc_32(keymap, entry_count * sizeof(struct button_mapping), 0xFFFFFFFF);
|
|
int keyfd = rb->open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
if (keyremap_write_header(keyfd, entry_count + 1) > 0)
|
|
{
|
|
if (keyremap_write_entries(keyfd, keymap, entry_count) > 0)
|
|
status = 1;
|
|
}
|
|
rb->close(keyfd);
|
|
|
|
return status;
|
|
}
|
|
|
|
static void keyremap_save_user_keys(bool notify)
|
|
{
|
|
char buf[MAX_PATH];
|
|
int i = 0;
|
|
do
|
|
{
|
|
i++;
|
|
rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", KMFDIR, KMFUSER, i, KMFEXT1);
|
|
} while (i < 100 && rb->file_exists(buf));
|
|
|
|
if (notify && prompt_filename(buf, sizeof(buf)) <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (keyremap_save_current(buf) == 0)
|
|
{
|
|
logf("Error Saving");
|
|
if(notify)
|
|
rb->splash(HZ *2, "Error Saving");
|
|
}
|
|
else if (notify)
|
|
rb->splashf(HZ *2, "Saved %s", buf);
|
|
}
|
|
|
|
static int keyremap_export_current(char *filenamebuf, size_t bufsz)
|
|
{
|
|
filenamebuf[bufsz - 1] = '\0';
|
|
int i, j;
|
|
int ctx_count = 0;
|
|
size_t entrylen;
|
|
|
|
int entry_count = ctx_data.ctx_count + ctx_data.act_count + 1;
|
|
|
|
if (entry_count < 3) /* the header is not counted should be at least 3 entries */
|
|
{
|
|
logf("%s: Not enough entries", __func__);
|
|
return 0;
|
|
}
|
|
int fd = rb->open(filenamebuf, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
|
|
if (fd < 0)
|
|
return -1;
|
|
rb->fdprintf(fd, "# Key Remap\n# Device: %s\n" \
|
|
"# Entries: %d\n\n", MODEL_NAME, entry_count - 1);
|
|
rb->fdprintf(fd, "# Each entry should be PROPER_CASE and on its own line\n" \
|
|
"# Comments run to end of line \n");
|
|
|
|
for (i = 0; i <= entry_count; i++)
|
|
{
|
|
entrylen = 0;
|
|
rb->memset(filenamebuf, 0, bufsz);
|
|
edit_keymap_name_cb(i, MENU_ID(M_EXPORTKEYS), filenamebuf, bufsz);
|
|
if (i == 0)
|
|
{
|
|
ctx_menu_data.act_fmt = " {%s%s, %s, %s},\n\n";
|
|
continue;
|
|
}
|
|
else if (i == entry_count)
|
|
{
|
|
rb->strlcpy(filenamebuf, "}\n\n", bufsz);
|
|
}
|
|
char last = '\0';
|
|
for (j = 0; j < (int)bufsz ;j++)
|
|
{
|
|
char ch = filenamebuf[j];
|
|
if (ch == '$' && last == '\0') /*CONTEXT*/
|
|
{
|
|
if (ctx_count > 0)
|
|
rb->fdprintf(fd, "}\n");
|
|
ctx_count++;
|
|
filenamebuf[j] = '\n';
|
|
}
|
|
if (ch == '\n' && last == '\n')
|
|
{
|
|
entrylen = j;
|
|
break;
|
|
}
|
|
else if (ch == '\0')
|
|
filenamebuf[j] = ',';
|
|
last = ch;
|
|
}
|
|
|
|
size_t bytes = rb->write(fd, filenamebuf, entrylen);
|
|
if (bytes != entrylen)
|
|
{
|
|
entry_count = -2;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
fail:
|
|
rb->close(fd);
|
|
|
|
return entry_count;
|
|
}
|
|
|
|
static void keyremap_export_user_keys(void)
|
|
{
|
|
const bool notify = true;
|
|
char buf[MAX_PATH];
|
|
int i = 0;
|
|
do
|
|
{
|
|
i++;
|
|
rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", "", KMFUSER, i, ".txt");
|
|
} while (i < 100 && rb->file_exists(buf));
|
|
|
|
if (notify && prompt_filename(buf, sizeof(buf)) <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (keyremap_export_current(buf, sizeof(buf)) <= 0)
|
|
{
|
|
if(notify)
|
|
rb->splash(HZ *2, "Error Saving");
|
|
}
|
|
else if (notify)
|
|
{
|
|
rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", "", KMFUSER, i, ".txt");
|
|
rb->splashf(HZ *2, "Saved %s", buf);
|
|
}
|
|
}
|
|
|
|
static void keyremap_import_user_keys(void)
|
|
{
|
|
char buf[MAX_PATH];
|
|
struct browse_context browse = {
|
|
.dirfilter = SHOW_ALL,
|
|
.flags = BROWSE_SELECTONLY,
|
|
.title = "Select Keymap",
|
|
.icon = Icon_Plugin,
|
|
.buf = buf,
|
|
.bufsize = sizeof(buf),
|
|
.root = "/",
|
|
};
|
|
|
|
if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS)
|
|
{
|
|
int ret = keyremap_import_file(buf, sizeof(buf));
|
|
if (ret <= 0)
|
|
{
|
|
keyremap_reset_buffer();
|
|
rb->splash(HZ *2, "Error Opening");
|
|
}
|
|
else
|
|
rb->splashf(HZ * 2, "Loaded Text Keymap ");
|
|
}
|
|
}
|
|
|
|
static int keymap_add_context_entry(int context)
|
|
{
|
|
int remap_context = CORE_CONTEXT_REMAP(context);
|
|
for (int i = 0; i < ctx_data.ctx_count; i++)
|
|
{
|
|
if (ctx_data.ctx_map[i].map.action_code == remap_context)
|
|
goto fail; /* context exists */
|
|
}
|
|
if (keyremap_buffer.front + sizeof(struct action_mapping_t) > keyremap_buffer.end)
|
|
goto fail;
|
|
keyremap_buffer.front += sizeof(struct action_mapping_t);
|
|
ctx_data.ctx_map[ctx_data.ctx_count].context = context;
|
|
ctx_data.ctx_map[ctx_data.ctx_count].display_pos = -1;
|
|
ctx_data.ctx_map[ctx_data.ctx_count].map.action_code = remap_context;
|
|
ctx_data.ctx_map[ctx_data.ctx_count].map.button_code = 0;
|
|
ctx_data.ctx_map[ctx_data.ctx_count].map.pre_button_code = 0;
|
|
ctx_data.ctx_count++;
|
|
menu_useract_set_positions();
|
|
return ctx_data.ctx_count;
|
|
fail:
|
|
return 0;
|
|
}
|
|
|
|
static int keymap_add_button_entry(int context, int action_code,
|
|
int button_code, int pre_button_code)
|
|
{
|
|
bool hasctx = false;
|
|
for (int i = 0; i < ctx_data.ctx_count; i++)
|
|
{
|
|
if (ctx_data.ctx_map[i].context == context)
|
|
{
|
|
hasctx = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasctx || keyremap_buffer.end - sizeof(struct action_mapping_t) < keyremap_buffer.front)
|
|
goto fail;
|
|
keyremap_buffer.end -= sizeof(struct action_mapping_t);
|
|
ctx_data.act_map = (struct action_mapping_t*) keyremap_buffer.end;
|
|
ctx_data.act_map[0].context = context;
|
|
ctx_data.act_map[0].display_pos = -1;
|
|
ctx_data.act_map[0].map.action_code = action_code;
|
|
ctx_data.act_map[0].map.button_code = button_code;
|
|
ctx_data.act_map[0].map.pre_button_code = pre_button_code;
|
|
ctx_data.act_count++;
|
|
menu_useract_set_positions();
|
|
return ctx_data.act_count;
|
|
fail:
|
|
return 0;
|
|
}
|
|
|
|
static int get_action_from_str(char *pfield, size_t bufsz)
|
|
{
|
|
int act = -1;
|
|
for (int i=0;i < LAST_ACTION_PLACEHOLDER; i++)
|
|
{
|
|
if (rb->strncasecmp(pfield, action_name(i), bufsz) == 0)
|
|
{
|
|
logf("Action Found: %s (%d)", pfield, i);
|
|
act = i;
|
|
break;
|
|
}
|
|
}
|
|
return act;
|
|
}
|
|
|
|
static int get_button_from_str(char *pfield, size_t bufsz)
|
|
{
|
|
int btn = -1;
|
|
for (int i=0;i < available_button_count; i++)
|
|
{
|
|
const struct available_button* abtn = &available_buttons[i];
|
|
if (rb->strncasecmp(pfield, abtn->name, bufsz) == 0)
|
|
{
|
|
logf("Button Found: %s (%lx)", abtn->name, abtn->value);
|
|
btn = abtn->value;
|
|
break;
|
|
}
|
|
}
|
|
if (btn < 0) /* Not Fatal */
|
|
{
|
|
logf("Invalid Button %s", pfield);
|
|
rb->splashf(HZ, "Invalid Button %s", pfield);
|
|
}
|
|
return btn;
|
|
}
|
|
|
|
static int parse_action_import_entry(int context, char * pbuf, size_t bufsz)
|
|
{
|
|
size_t bufleft;
|
|
int count = 0;
|
|
char ch;
|
|
char *pfirst = NULL;
|
|
char *pfield;
|
|
int field = -1;
|
|
int act = -1;
|
|
int button = BUTTON_NONE;
|
|
int prebtn = BUTTON_NONE;
|
|
while ((ch = *(pbuf)) != '\0')
|
|
{
|
|
pfield = NULL; /* Valid names */
|
|
if ((ch >= 'A' && ch <= 'Z') || ch == '_')
|
|
{
|
|
if (pfirst == NULL)
|
|
pfirst = pbuf;
|
|
}
|
|
else if (ch == ',')
|
|
{
|
|
if (pfirst != NULL)
|
|
{
|
|
field++;
|
|
pfield = pfirst;
|
|
pfirst = NULL;
|
|
*pbuf = '\0';
|
|
}
|
|
}
|
|
else if (ch == ' ' || ch == '|'){;}
|
|
else
|
|
pfirst = NULL;
|
|
|
|
if (field == 1 && pfirst != NULL && pbuf[1] == '\0')
|
|
{
|
|
field++;
|
|
pfield = pfirst;
|
|
pfirst = NULL;
|
|
}
|
|
|
|
if (pfield != NULL)
|
|
{
|
|
char *pf;
|
|
|
|
if (field == 0) /* action */
|
|
{
|
|
char *pact = pfield;
|
|
pf = pfield;
|
|
while ((ch = *(pf)) != '\0')
|
|
{
|
|
if(ch == ' ')
|
|
*pf = '\0';
|
|
else
|
|
pf++;
|
|
}
|
|
bufleft = bufsz - (pact - pbuf);
|
|
act = get_action_from_str(pact, bufleft);
|
|
|
|
if (act < 0)
|
|
{
|
|
logf("Error Action Expected: %s", pact);
|
|
return -1;
|
|
}
|
|
}
|
|
else if (field == 1 || field == 2) /* button / pre_btn */
|
|
{
|
|
char *pbtn = pfield;
|
|
pf = pfield;
|
|
while ((ch = *(pf)) != '\0')
|
|
{
|
|
if (pf[1] == '\0') /* last item? */
|
|
{
|
|
pf++;
|
|
ch = '|';
|
|
pfield = NULL;
|
|
}
|
|
else if (ch == ' ' || ch == '|')
|
|
{
|
|
*pf = '\0';
|
|
}
|
|
|
|
if(ch == '|')
|
|
{
|
|
bufleft = bufsz - (pbtn - pbuf);
|
|
int btn = get_button_from_str(pbtn, bufleft);
|
|
|
|
if (btn > BUTTON_NONE)
|
|
{
|
|
if (field == 1)
|
|
button |= btn;
|
|
else if (field == 2)
|
|
prebtn |= btn;
|
|
}
|
|
|
|
if (pfield != NULL)
|
|
{
|
|
pf++;
|
|
while ((ch = *(pf)) != '\0')
|
|
{
|
|
if (*pf == ' ')
|
|
pf++;
|
|
else
|
|
break;
|
|
}
|
|
pbtn = pf;
|
|
}
|
|
}
|
|
else
|
|
pf++;
|
|
}
|
|
|
|
if (act < 0)
|
|
{
|
|
logf("Error Action Expected: %s", pfield);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
pfield = NULL;
|
|
}
|
|
|
|
pbuf++;
|
|
}
|
|
if (field == 2)
|
|
{
|
|
count = keymap_add_button_entry(context, act, button, prebtn);
|
|
if (count > 0)
|
|
{
|
|
logf("Added: Ctx: %d, Act: %d, Btn: %d PBtn: %d",
|
|
context, act, button, prebtn);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static int keyremap_import_file(char *filenamebuf, size_t bufsz)
|
|
{
|
|
size_t bufleft;
|
|
int count = 0;
|
|
int line = 0;
|
|
filenamebuf[bufsz - 1] = '\0';
|
|
logf("keyremap: import %s", filenamebuf);
|
|
int fd = rb->open(filenamebuf, O_RDONLY);
|
|
if (fd < 0)
|
|
return -1;
|
|
|
|
char ch;
|
|
char *pbuf;
|
|
char *pfirst;
|
|
|
|
char *pctx = NULL;
|
|
char *pact;
|
|
int ctx = -1;
|
|
|
|
keyremap_reset_buffer();
|
|
next_line:
|
|
while (rb->read_line(fd, filenamebuf, (int) bufsz) > 0)
|
|
{
|
|
line++;
|
|
|
|
|
|
pbuf = filenamebuf;
|
|
pfirst = NULL;
|
|
pact = NULL;
|
|
char *pcomment = rb->strchr(pbuf, '#');
|
|
if (pcomment != NULL)
|
|
{
|
|
logf("ln: %d: Skipped Comment: %s", line, pcomment);
|
|
*pcomment = '\0';
|
|
}
|
|
|
|
while ((ch = *(pbuf)) != '\0')
|
|
{
|
|
/* PARSE CONTEXT = { */
|
|
if ((ch >= 'A' && ch <= 'Z') || ch == '_')
|
|
{
|
|
if (pfirst == NULL)
|
|
pfirst = pbuf;
|
|
}
|
|
else if (ch == ' ')
|
|
{
|
|
if (ctx < 0 && pfirst != NULL)
|
|
{
|
|
*pbuf = '\0';
|
|
pctx = pfirst;
|
|
pfirst = NULL;
|
|
}
|
|
}
|
|
else if (ch == '=')
|
|
{
|
|
if (ctx < 0 && pfirst != NULL)
|
|
{
|
|
*pbuf = '\0';
|
|
pbuf++;
|
|
pctx = pfirst;
|
|
pfirst = NULL;
|
|
}
|
|
while ((ch = *(pbuf)) != '\0')
|
|
{
|
|
if (ch == '{')
|
|
break;
|
|
pbuf++;
|
|
}
|
|
if (ch == '{' && pctx != NULL)
|
|
{
|
|
bufleft = bufsz - (pctx - filenamebuf);
|
|
ctx = -1;
|
|
int ctx_x_flag_count = (LAST_CONTEXT_PLACEHOLDER
|
|
* ARRAYLEN(context_flags));
|
|
|
|
for (int i=0;i < ctx_x_flag_count ;i++)
|
|
{
|
|
/* context x flag */
|
|
if (rb->strncasecmp(pctx, ctx_name_and_flag(i), bufleft) == 0)
|
|
{
|
|
logf("ln: %d: Context Found: %s (%d)", line, pctx, i);
|
|
if (keymap_add_context_entry(i) <= 0)
|
|
logf("ln: %d: Context Exists: %s (%d)", line, pctx, i);
|
|
ctx = i;
|
|
goto next_line;
|
|
|
|
}
|
|
}
|
|
logf("ln: %d: ERROR { Context Expected got: %s", line, pctx);
|
|
goto fail;
|
|
}
|
|
}
|
|
else if (ch == '}')
|
|
{
|
|
if (ctx >= 0)
|
|
ctx = -1;
|
|
else
|
|
{
|
|
logf("ln: %d: ERROR no context, unexpected close {", line);
|
|
goto fail;
|
|
}
|
|
}
|
|
else if (ch == '{') /* PARSE FIELDS { ACTION, BUTTON, PREBTN } */
|
|
{
|
|
int res = 0;
|
|
if (ctx >= 0)
|
|
{
|
|
pfirst = pbuf;
|
|
|
|
while ((ch = *(pbuf)) != '\0')
|
|
{
|
|
if (ch == '}')
|
|
{
|
|
pact = pfirst + 1;
|
|
pfirst = NULL;
|
|
*pbuf = '\0';
|
|
pbuf = "";
|
|
continue;
|
|
}
|
|
pbuf++;
|
|
}
|
|
if (pact != NULL)
|
|
{
|
|
bufleft = bufsz - (pact - filenamebuf);
|
|
logf("ln: %d: Entry Found: {%s} (%d)", line, pact, 0);
|
|
res = parse_action_import_entry(ctx, pact, bufleft);
|
|
}
|
|
}
|
|
if (res <= 0)
|
|
{
|
|
logf("ln: %d: ERROR action entry expected", line);
|
|
goto fail;
|
|
}
|
|
else
|
|
{
|
|
pbuf = "";
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
pfirst = NULL;
|
|
pbuf++;
|
|
}
|
|
|
|
}
|
|
rb->close(fd);
|
|
count = ctx_data.ctx_count + ctx_data.act_count;
|
|
return count;
|
|
|
|
fail:
|
|
rb->close(fd);
|
|
rb->splashf(HZ * 2, "Error @ line %d", line);
|
|
return 0;
|
|
}
|
|
|
|
static int keyremap_load_file(const char *filename)
|
|
{
|
|
logf("keyremap: load %s", filename);
|
|
int fd = -1;
|
|
size_t fsize = 0;
|
|
size_t bytes;
|
|
struct button_mapping entry;
|
|
int count = keyremap_open_file(filename, &fd, &fsize);
|
|
logf("keyremap: entries %d", count);
|
|
/* actions are indexed from the first entry after the header save this pos */
|
|
off_t firstpos = rb->lseek(fd, 0, SEEK_CUR);
|
|
off_t ctxpos = firstpos;
|
|
|
|
if (count > 0)
|
|
{
|
|
keyremap_reset_buffer();
|
|
while(--count > 0)
|
|
{
|
|
rb->lseek(fd, ctxpos, SEEK_SET); /* next context remap entry */
|
|
bytes = rb->read(fd, &entry, sizeof(struct button_mapping));
|
|
ctxpos = rb->lseek(fd, 0, SEEK_CUR);
|
|
if (bytes != sizeof(struct button_mapping))
|
|
{
|
|
count = -10;
|
|
goto fail;
|
|
}
|
|
if (entry.action_code == (int)CONTEXT_STOPSEARCHING)
|
|
{
|
|
logf("keyremap: end of context entries ");
|
|
break;
|
|
}
|
|
if ((entry.action_code & CONTEXT_REMAPPED) == CONTEXT_REMAPPED)
|
|
{
|
|
int context = (entry.action_code & ~CONTEXT_REMAPPED);
|
|
for (int i = ARRAYLEN(context_flags) - 1; i > 0; i--) /* don't check idx 0*/
|
|
{
|
|
/* convert context | flag to context x flag */
|
|
if ((context & context_flags[i].flag) == context_flags[i].flag)
|
|
{
|
|
logf("found ctx flag %s", context_flags[i].name);
|
|
context &= ~context_flags[i].flag;
|
|
context += i * LAST_CONTEXT_PLACEHOLDER;
|
|
}
|
|
}
|
|
int offset = entry.button_code;
|
|
int entries = entry.pre_button_code;
|
|
if (offset == 0 || entries <= 0)
|
|
{
|
|
logf("keyremap: error reading offset");
|
|
count = -15;
|
|
goto fail;
|
|
}
|
|
logf("keyremap found context: %d file offset: %d entries: %d",
|
|
context, offset, entries);
|
|
|
|
keymap_add_context_entry(context);
|
|
|
|
off_t entrypos = firstpos + (offset * sizeof(struct button_mapping));
|
|
rb->lseek(fd, entrypos, SEEK_SET);
|
|
for (int i = 0; i < entries; i++)
|
|
{
|
|
bytes = rb->read(fd, &entry, sizeof(struct button_mapping));
|
|
if (bytes == sizeof(struct button_mapping))
|
|
{
|
|
int act = entry.action_code;
|
|
int button = entry.button_code;
|
|
int prebtn = entry.pre_button_code;
|
|
|
|
if (act == (int)CONTEXT_STOPSEARCHING || button == BUTTON_NONE)
|
|
{
|
|
logf("keyremap: entry invalid");
|
|
goto fail;
|
|
}
|
|
logf("keyremap: found ctx: %d, act: %d btn: %d pbtn: %d",
|
|
context, act, button, prebtn);
|
|
keymap_add_button_entry(context, act, button, prebtn);
|
|
}
|
|
else
|
|
goto fail;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logf("keyremap: Invalid context entry");
|
|
keyremap_reset_buffer();
|
|
count = -20;
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
int entries = 0;
|
|
struct button_mapping *keymap = keyremap_create_temp(&entries);
|
|
if (keymap != NULL)
|
|
{
|
|
keyset.crc32 =
|
|
rb->crc_32(keymap, entries * sizeof(struct button_mapping), 0xFFFFFFFF);
|
|
}
|
|
fail:
|
|
rb->close(fd);
|
|
if (count <= 0)
|
|
rb->splashf(HZ * 2, "Error Loading %sz", filename);
|
|
return count;
|
|
}
|
|
|
|
static const struct button_mapping* test_get_context_map(int context)
|
|
{
|
|
(void)context;
|
|
|
|
if (keytest.keymap != NULL && keytest.context >= 0)
|
|
{
|
|
int mapidx = keytest.keymap[keytest.index].button_code;
|
|
int mapcnt = keytest.keymap[keytest.index].pre_button_code;
|
|
/* make fallthrough to the test context*/
|
|
keytest.keymap[mapidx + mapcnt].action_code = keytest.context;
|
|
static const struct button_mapping *testctx[] = { NULL };
|
|
testctx[0] = &keytest.keymap[mapidx];
|
|
return testctx[0];
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
static const char *kmffiles_name_cb(int selected_item, void* data,
|
|
char* buf, size_t buf_len)
|
|
{
|
|
/* found kmf filenames returned by each call kmffiles_dirp keeps state
|
|
* selected_item = 0 resets state */
|
|
|
|
(void)data;
|
|
buf[0] = '\0';
|
|
if (selected_item == 0)
|
|
{
|
|
rb->closedir(kmffiles_dirp);
|
|
kmffiles_dirp = rb->opendir(KMFDIR);
|
|
}
|
|
if (kmffiles_dirp != NULL)
|
|
{
|
|
struct dirent *entry;
|
|
while ((entry = rb->readdir(kmffiles_dirp)))
|
|
{
|
|
/* skip directories */
|
|
if ((rb->dir_get_info(kmffiles_dirp, entry).attribute & ATTR_DIRECTORY) != 0)
|
|
continue;
|
|
if (keyremap_check_extension(entry->d_name) > 0)
|
|
{
|
|
rb->snprintf(buf, buf_len, "%s", entry->d_name);
|
|
return buf;
|
|
}
|
|
}
|
|
}
|
|
return "Error!";
|
|
}
|
|
|
|
static void menu_useract_set_positions(void)
|
|
{
|
|
/* responsible for item ordering to display action edit interface */
|
|
int display_pos = 0; /* start at item 0*/
|
|
int i, j;
|
|
for (i = 0; i < ctx_data.ctx_count; i++)
|
|
{
|
|
int context = ctx_data.ctx_map[i].context;
|
|
ctx_data.ctx_map[i].display_pos = display_pos++;
|
|
/* how many actions are contained in this context? */
|
|
for (j = 0; j < ctx_data.act_count; j++)
|
|
{
|
|
if (ctx_data.act_map[j].context == context)
|
|
{
|
|
ctx_data.act_map[j].display_pos = display_pos++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *menu_useract_items_cb(int selected_item, void* data,
|
|
char* buf, size_t buf_len)
|
|
{
|
|
static char buf_button[MAX_BUTTON_NAME * MAX_BUTTON_COMBO];
|
|
static char buf_prebtn[MAX_BUTTON_NAME * MAX_BUTTON_COMBO];
|
|
int i;
|
|
int szbtn = sizeof("BUTTON");
|
|
int szctx = sizeof("CONTEXT");
|
|
int szact = sizeof("ACTION");
|
|
const char* ctxfmt = "%s";
|
|
|
|
if (data == MENU_ID(M_EXPORTKEYS))
|
|
{
|
|
szbtn = 0;
|
|
szctx = 0;
|
|
szact = 0;
|
|
ctxfmt = "$%s = {\n\n";
|
|
}
|
|
buf[0] = '\0';
|
|
for(i = 0; i < ctx_data.ctx_count; i ++)
|
|
{
|
|
if (ctx_data.ctx_map[i].display_pos == selected_item)
|
|
{
|
|
if (ctx_data.act_count == 0)
|
|
rb->snprintf(buf, buf_len, "%s$%s",
|
|
ctx_name_and_flag(ctx_data.ctx_map[i].context),
|
|
"Select$to add$actions");
|
|
else
|
|
rb->snprintf(buf, buf_len, ctxfmt, ctx_name_and_flag(ctx_data.ctx_map[i].context));
|
|
return buf;
|
|
}
|
|
}
|
|
for(i = 0; i < ctx_data.act_count; i ++)
|
|
{
|
|
if (ctx_data.act_map[i].display_pos == selected_item)
|
|
{
|
|
int context = ctx_data.act_map[i].context;
|
|
char ctxbuf[action_helper_maxbuffer];
|
|
char *pctxbuf = "\0";
|
|
char *pactname;
|
|
if (data != MENU_ID(M_EXPORTKEYS))
|
|
{
|
|
pctxbuf = ctxbuf;
|
|
rb->snprintf(ctxbuf, sizeof(ctxbuf), ctxfmt, ctx_name_and_flag(context));
|
|
pctxbuf += szctx;//sizeof("CONTEXT")
|
|
}
|
|
struct button_mapping * bm = &ctx_data.act_map[i].map;
|
|
pactname = action_name(bm->action_code);
|
|
pactname += szact;//sizeof("ACTION")
|
|
/* BUTTON & PRE_BUTTON */
|
|
btnval_to_name(buf_button, sizeof(buf_button), bm->button_code);
|
|
btnval_to_name(buf_prebtn, sizeof(buf_prebtn), bm->pre_button_code);
|
|
|
|
rb->snprintf(buf, buf_len, ctx_menu_data.act_fmt, pctxbuf,
|
|
pactname, buf_button + szbtn, buf_prebtn + szbtn);
|
|
return buf;
|
|
}
|
|
}
|
|
return "Error!";
|
|
}
|
|
|
|
static const char *edit_keymap_name_cb(int selected_item, void* data,
|
|
char* buf, size_t buf_len)
|
|
{
|
|
buf[0] = '\0';
|
|
if (selected_item == 0)
|
|
{
|
|
rb->snprintf(buf, buf_len, "Add Context");
|
|
ctx_menu_data.act_index = -1;
|
|
ctx_menu_data.ctx_index = -1;
|
|
ctx_menu_data.act_fmt = ACTIONFMT_LV0;
|
|
}
|
|
else if (ctx_data.ctx_count > 0)
|
|
{
|
|
return menu_useract_items_cb(selected_item - 1, data, buf, buf_len);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
static const char *test_keymap_name_cb(int selected_item, void* data,
|
|
char* buf, size_t buf_len)
|
|
{
|
|
(void)data;
|
|
buf[0] = '\0';
|
|
if (keytest.context >= 0)
|
|
{
|
|
if (selected_item == 0)
|
|
rb->snprintf(buf, buf_len, "< %s >", ctx_name_and_flag(keytest.context));
|
|
else if (selected_item == 1)
|
|
{
|
|
if (keytest.countdown >= 10)
|
|
rb->snprintf(buf, buf_len, "Testing %d", keytest.countdown / 10);
|
|
else
|
|
rb->snprintf(buf, buf_len, "Start test");
|
|
}
|
|
else if (selected_item == 2)
|
|
rb->snprintf(buf, buf_len, "%s", action_name(keytest.action));
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
static const char* list_get_name_cb(int selected_item, void* data,
|
|
char* buf, size_t buf_len)
|
|
{
|
|
buf[0] = '\0';
|
|
const struct mainmenu *cur = (struct mainmenu *) data;
|
|
if (data == MENU_ID(M_ROOT))
|
|
return mainitem(selected_item + 1)->name;
|
|
else if (selected_item >= cur->items - 1)
|
|
{
|
|
return ID2P(LANG_BACK);
|
|
}
|
|
if (data == MENU_ID(M_SETKEYS))
|
|
{
|
|
return edit_keymap_name_cb(selected_item, data, buf, buf_len);
|
|
}
|
|
else if (data == MENU_ID(M_BUTTONS))
|
|
{
|
|
const struct available_button *btn = &available_buttons[selected_item];
|
|
rb->snprintf(buf, buf_len, "%s: [0x%X] ", btn->name, (unsigned int) btn->value);
|
|
return buf;
|
|
}
|
|
else if (data == MENU_ID(M_ACTIONS))
|
|
{
|
|
return action_name(selected_item);
|
|
}
|
|
else if (data == MENU_ID(M_CONTEXTS))
|
|
{
|
|
return ctx_name_and_flag(selected_item);
|
|
}
|
|
else if (data == MENU_ID(M_DELKEYS) || data == MENU_ID(M_LOADKEYS))
|
|
{
|
|
/* need to iterate the callback for the items off screen to
|
|
* keep ordering, this limits the menu to only the main screen :( */
|
|
int start_item = lists.start_item[SCREEN_MAIN];
|
|
if (start_item != 0 && start_item == selected_item)
|
|
{
|
|
for (int i = 0; i < start_item; i++)
|
|
kmffiles_name_cb(i, data, buf, buf_len);
|
|
}
|
|
return kmffiles_name_cb(selected_item, data, buf, buf_len);
|
|
}
|
|
else if (data == MENU_ID(M_TESTKEYS))
|
|
{
|
|
return test_keymap_name_cb(selected_item, data, buf, buf_len);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
static int list_voice_cb(int list_index, void* data)
|
|
{
|
|
if (!rb->global_settings->talk_menu)
|
|
return -1;
|
|
|
|
if (data == MENU_ID(M_ROOT))
|
|
{
|
|
const char * name = mainitem(list_index)->name;
|
|
long id = P2ID((const unsigned char *)name);
|
|
if(id>=0)
|
|
rb->talk_id(id, true);
|
|
else
|
|
rb->talk_spell(name, true);
|
|
}
|
|
else if(data == MENU_ID(M_SETKEYS))
|
|
{
|
|
char buf[MAX_MENU_NAME];
|
|
int selcol = printcell_get_column_selected();
|
|
const char* name = printcell_get_column_text(selcol, buf, sizeof(buf));
|
|
long id = P2ID((const unsigned char *)name);
|
|
if(id>=0)
|
|
rb->talk_id(id, true);
|
|
else
|
|
rb->talk_spell(name, true);
|
|
}
|
|
else
|
|
{
|
|
char buf[MAX_MENU_NAME];
|
|
const char* name = list_get_name_cb(list_index, data, buf, sizeof(buf));
|
|
long id = P2ID((const unsigned char *)name);
|
|
if(id>=0)
|
|
rb->talk_id(id, true);
|
|
else
|
|
rb->talk_spell(name, true);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int menu_action_root(int *action, int selected_item, bool* exit, struct gui_synclist *lists)
|
|
{
|
|
#ifdef ROCKBOX_HAS_LOGF
|
|
char logfnamebuf[64];
|
|
#endif
|
|
|
|
if (*action == ACTION_STD_OK)
|
|
{
|
|
struct mainmenu *cur = mainitem(selected_item + 1);
|
|
if (cur != NULL)
|
|
{
|
|
#ifdef ROCKBOX_HAS_LOGF
|
|
lang_strlcpy(logfnamebuf, cur->name, sizeof(logfnamebuf));
|
|
logf("Root select menu -> %s(%d) ", logfnamebuf, cur->index);
|
|
#endif
|
|
if (cur->menuid == MENU_ID(M_SETKEYS))
|
|
{
|
|
keyset.view_lastcol = -1;
|
|
}
|
|
else if (cur->menuid == MENU_ID(M_TMPCORE))
|
|
{
|
|
int entry_count;/* (ctx_count + ctx_count + act_count + 1) */
|
|
struct button_mapping *keymap = keyremap_create_temp(&entry_count);
|
|
if (rb->core_set_keyremap(keymap, entry_count) >= 0)
|
|
rb->splash(HZ *2, "Keymap Applied");
|
|
else
|
|
rb->splash(HZ *2, "Error Applying");
|
|
|
|
goto default_handler;
|
|
}
|
|
else if (cur->menuid == MENU_ID(M_SETCORE))
|
|
{
|
|
if (rb->file_exists(CORE_KEYREMAP_FILE) && 0 == core_savecount++)
|
|
{
|
|
rb->rename(CORE_KEYREMAP_FILE, CORE_KEYREMAP_FILE".old"); /* make a backup */
|
|
}
|
|
if (keyremap_save_current(CORE_KEYREMAP_FILE) == 0)
|
|
rb->splash(HZ *2, "Error Saving");
|
|
else
|
|
{
|
|
rb->splash(HZ *2, "Saved");
|
|
int entry_count;/* (ctx_count + ctx_count + act_count + 1) */
|
|
struct button_mapping *keymap = keyremap_create_temp(&entry_count);
|
|
rb->core_set_keyremap(keymap, entry_count);
|
|
}
|
|
goto default_handler;
|
|
}
|
|
else if (cur->menuid == MENU_ID(M_DELCORE))
|
|
{
|
|
rb->core_set_keyremap(NULL, -1);
|
|
if (rb->file_exists(CORE_KEYREMAP_FILE))
|
|
{
|
|
rb->rename(CORE_KEYREMAP_FILE, KMFDIR "/core_deleted" KMFEXT2);
|
|
rb->splash(HZ *2, "Removed");
|
|
}
|
|
else
|
|
rb->splash(HZ *2, "Error Removing");
|
|
|
|
goto default_handler;
|
|
}
|
|
else if (cur->menuid == MENU_ID(M_SAVEKEYS))
|
|
{
|
|
keyremap_save_user_keys(true);
|
|
goto default_handler;
|
|
}
|
|
else if (cur->menuid == MENU_ID(M_EXPORTKEYS))
|
|
{
|
|
keyremap_export_user_keys();
|
|
goto default_handler;
|
|
}
|
|
else if (cur->menuid == MENU_ID(M_IMPORTKEYS))
|
|
{
|
|
keyremap_import_user_keys();
|
|
goto default_handler;
|
|
}
|
|
else if (cur->menuid == MENU_ID(M_DELKEYS) ||
|
|
cur->menuid == MENU_ID(M_LOADKEYS))
|
|
{
|
|
cur->items = keyremap_count_files(KMFDIR) + 1;
|
|
}
|
|
else if (cur->menuid == MENU_ID(M_TESTKEYS))
|
|
{
|
|
int entries = 0;
|
|
keytest.keymap = keyremap_create_temp(&entries);
|
|
if (entries > 0)
|
|
{
|
|
struct button_mapping *entry = &keytest.keymap[0];
|
|
if (entry->action_code != (int)CONTEXT_STOPSEARCHING
|
|
&& (entry->action_code & CONTEXT_REMAPPED) == CONTEXT_REMAPPED)
|
|
{
|
|
keytest.context = (entry->action_code & ~CONTEXT_REMAPPED);
|
|
}
|
|
else
|
|
keytest.context = -1;
|
|
}
|
|
else
|
|
{
|
|
keytest.keymap = NULL;
|
|
keytest.context = -1;
|
|
}
|
|
keytest.action = ACTION_NONE;
|
|
keytest.countdown = 0;
|
|
}
|
|
else if (cur->menuid == MENU_ID(M_RESETKEYS))
|
|
{
|
|
if (rb->yesno_pop("Delete Current Entries?") == true)
|
|
{
|
|
keyremap_reset_buffer();
|
|
}
|
|
goto default_handler;
|
|
}
|
|
}
|
|
|
|
if (cur->menuid == NULL || cur->menuid == MENU_ID(M_EXIT))
|
|
{
|
|
logf("Root menu %s", (cur->menuid) == NULL ? "NULL":"Exit");
|
|
*action = ACTION_STD_CANCEL;
|
|
*exit = true;
|
|
}
|
|
else
|
|
{
|
|
#ifdef ROCKBOX_HAS_LOGF
|
|
lang_strlcpy(logfnamebuf, cur->name, sizeof(logfnamebuf));
|
|
logf("Root load menu -> %s(%d) ", logfnamebuf, cur->index);
|
|
#endif
|
|
synclist_set_update(cur->index, 0, cur->items, 1);
|
|
rb->gui_synclist_draw(lists);
|
|
*action = ACTION_NONE;
|
|
}
|
|
}
|
|
|
|
return PLUGIN_OK;
|
|
default_handler:
|
|
return GOTO_ACTION_DEFAULT_HANDLER;
|
|
}
|
|
|
|
int menu_action_setkeys(int *action, int selected_item, bool* exit, struct gui_synclist *lists)
|
|
{
|
|
(void) exit;
|
|
int i;
|
|
struct mainmenu *cur = (struct mainmenu *)lists->data;
|
|
if (*action == ACTION_STD_OK)
|
|
{
|
|
if (selected_item == 0) /*add_context*/
|
|
{
|
|
const struct mainmenu *mainm = &mainmenu[M_CONTEXTS];
|
|
synclist_set_update(mainm->index, 0, mainm->items, 1);
|
|
rb->gui_synclist_draw(lists);
|
|
goto default_handler;
|
|
}
|
|
else if (selected_item < lists->nb_items - 1)/* not back*/
|
|
{
|
|
bool add_action = false;
|
|
for (i = 0; i < ctx_data.ctx_count; i++)
|
|
{
|
|
if (ctx_data.ctx_map[i].display_pos == selected_item - 1)
|
|
{
|
|
add_action = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (add_action)
|
|
{
|
|
keymap_add_button_entry(ctx_data.ctx_map[i].context, ACTION_NONE, BUTTON_NONE, BUTTON_NONE);
|
|
cur->items++;
|
|
lists->nb_items++;
|
|
goto default_handler;
|
|
}
|
|
else
|
|
{
|
|
keyset.view_lastcol = printcell_increment_column(1, true);
|
|
*action = ACTION_NONE;
|
|
}
|
|
}
|
|
}
|
|
else if (*action == ACTION_STD_CANCEL)
|
|
{
|
|
keyset.view_lastcol = printcell_increment_column(-1, true);
|
|
if (keyset.view_lastcol != keyset.view_columns - 1)
|
|
{
|
|
*action = ACTION_NONE;
|
|
}
|
|
}
|
|
else if (*action == ACTION_STD_CONTEXT)
|
|
{
|
|
int col = keyset.view_lastcol;
|
|
for (i = 0; i < ctx_data.act_count; i++)
|
|
{
|
|
if (ctx_data.act_map[i].display_pos == selected_item - 1)
|
|
{
|
|
int context = ctx_data.act_map[i].context;
|
|
int act = ctx_data.act_map[i].map.action_code;
|
|
int button = ctx_data.act_map[i].map.button_code;
|
|
int prebtn = ctx_data.act_map[i].map.pre_button_code;
|
|
|
|
if (col < 0)
|
|
{
|
|
rb->splashf(HZ, "short press increments columns");
|
|
|
|
}
|
|
else if (col == 0) /* Context */
|
|
{
|
|
const struct mainmenu *mainm = &mainmenu[M_CONTEXTS];
|
|
synclist_set_update(mainm->index, context, mainm->items, 1);
|
|
rb->gui_synclist_draw(lists);
|
|
goto default_handler;
|
|
|
|
}
|
|
else if (col == 1) /* Action */
|
|
{
|
|
const struct mainmenu *mainm = &mainmenu[M_ACTIONS];
|
|
synclist_set_update(mainm->index, act, mainm->items, 1);
|
|
rb->gui_synclist_draw(lists);
|
|
goto default_handler;
|
|
}
|
|
else if (col == 2) /* Button */
|
|
{
|
|
const struct mainmenu *mainm = &mainmenu[M_BUTTONS];
|
|
int btnidx = btnval_to_index(button);
|
|
synclist_set_update(mainm->index, btnidx, mainm->items, 1);
|
|
rb->gui_synclist_draw(lists);
|
|
goto default_handler;
|
|
}
|
|
else if (col == 3) /* PreBtn */
|
|
{
|
|
const struct mainmenu *mainm = &mainmenu[M_BUTTONS];
|
|
int pbtnidx = btnval_to_index(prebtn);
|
|
synclist_set_update(mainm->index, pbtnidx, mainm->items, 1);
|
|
rb->gui_synclist_draw(lists);
|
|
goto default_handler;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return PLUGIN_OK;
|
|
default_handler:
|
|
return GOTO_ACTION_DEFAULT_HANDLER;
|
|
}
|
|
|
|
int menu_action_testkeys(int *action, int selected_item, bool* exit, struct gui_synclist *lists)
|
|
{
|
|
(void) exit;
|
|
(void) lists;
|
|
static int last_count = 0;
|
|
if (keytest.keymap != NULL && keytest.countdown >= 10 && keytest.context >= 0)
|
|
{
|
|
int newact = rb->get_custom_action(CONTEXT_PLUGIN, HZ / 10, test_get_context_map);
|
|
if (newact == ACTION_NONE)
|
|
{
|
|
keytest.countdown--;
|
|
if (last_count - keytest.countdown > 10)
|
|
keytest.action = newact;
|
|
}
|
|
else
|
|
{
|
|
last_count = keytest.countdown;
|
|
keytest.action = newact;
|
|
}
|
|
*action = ACTION_REDRAW;
|
|
goto default_handler;
|
|
}
|
|
|
|
if (*action == ACTION_STD_CANCEL && selected_item == 0 && keytest.keymap != NULL)
|
|
{
|
|
keytest.index -= 2;
|
|
if (keytest.index < 0)
|
|
keytest.index = 0;
|
|
*action = ACTION_STD_OK;
|
|
}
|
|
|
|
if (*action == ACTION_STD_OK)
|
|
{
|
|
if (selected_item == 0)
|
|
{
|
|
if (keytest.keymap != NULL)
|
|
{
|
|
struct button_mapping *entry = &keytest.keymap[++keytest.index];
|
|
if (entry->action_code != (int) CONTEXT_STOPSEARCHING
|
|
&& (entry->action_code & CONTEXT_REMAPPED) == CONTEXT_REMAPPED)
|
|
{
|
|
keytest.context = (entry->action_code & ~CONTEXT_REMAPPED);
|
|
}
|
|
else
|
|
{
|
|
entry = &keytest.keymap[0];
|
|
if (entry->action_code != (int)CONTEXT_STOPSEARCHING
|
|
&& (entry->action_code & CONTEXT_REMAPPED) == CONTEXT_REMAPPED)
|
|
{
|
|
keytest.context = (entry->action_code & ~CONTEXT_REMAPPED);
|
|
keytest.index = 0;
|
|
}
|
|
else
|
|
{
|
|
keytest.keymap = NULL;
|
|
keytest.context = -1;
|
|
keytest.index = -1;
|
|
keytest.action = ACTION_NONE;
|
|
keytest.countdown = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (selected_item == 1)
|
|
{
|
|
keytest.countdown = TEST_COUNTDOWN_MS / 10;
|
|
keytest.action = ACTION_NONE;
|
|
}
|
|
}
|
|
|
|
return PLUGIN_OK;
|
|
default_handler:
|
|
return GOTO_ACTION_DEFAULT_HANDLER;
|
|
}
|
|
|
|
int menu_action_listfiles(int *action, int selected_item, bool* exit, struct gui_synclist *lists)
|
|
{
|
|
(void) exit;
|
|
int i;
|
|
if (*action == ACTION_STD_OK)
|
|
{
|
|
struct mainmenu *cur = (struct mainmenu *)lists->data;
|
|
if (selected_item >= (cur->items) - 1)/*back*/
|
|
{
|
|
*action = ACTION_STD_CANCEL;
|
|
}
|
|
else
|
|
{
|
|
/* iterate to the desired file */
|
|
char buf[MAX_PATH];
|
|
int len = rb->snprintf(buf, sizeof(buf), "%s/", KMFDIR);
|
|
if (len < (int) sizeof(buf))
|
|
{
|
|
char *name = &buf[len];
|
|
size_t bufleft = sizeof(buf) - len;
|
|
list_get_name_cb(0, lists->data, name, bufleft);
|
|
for (i = 1; i <= selected_item; i++)
|
|
{
|
|
list_get_name_cb(i, lists->data, name, bufleft);
|
|
}
|
|
|
|
if (lists->data == MENU_ID(M_DELKEYS))
|
|
{
|
|
const char *lines[] = {ID2P(LANG_DELETE), buf};
|
|
const struct text_message message={lines, 2};
|
|
if (rb->gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES)
|
|
{
|
|
rb->remove(buf);
|
|
cur->items--;
|
|
lists->nb_items--;
|
|
*action = ACTION_NONE;
|
|
}
|
|
}
|
|
else if (lists->data == MENU_ID(M_LOADKEYS))
|
|
{
|
|
if (keyremap_load_file(buf) > 0)
|
|
{
|
|
rb->splashf(HZ * 2, "Loaded %s ", buf);
|
|
*action = ACTION_STD_CANCEL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return PLUGIN_OK;
|
|
}
|
|
|
|
int menu_action_submenus(int *action, int selected_item, bool* exit, struct gui_synclist *lists)
|
|
{
|
|
#ifdef ROCKBOX_HAS_LOGF
|
|
char logfnamebuf[64];
|
|
#endif
|
|
(void) exit;
|
|
if (*action == ACTION_STD_OK)
|
|
{
|
|
struct mainmenu *cur = (struct mainmenu *)lists->data;
|
|
if (selected_item >= (cur->items) - 1)/*back*/
|
|
{
|
|
*action = ACTION_STD_CANCEL;
|
|
}
|
|
else if (lists->data == MENU_ID(M_ACTIONS))
|
|
{
|
|
#ifdef ROCKBOX_HAS_LOGF
|
|
const char *name = list_get_name_cb(selected_item, lists->data, logfnamebuf, sizeof(logfnamebuf));
|
|
logf("ACT %s %d (0x%X)", name, selected_item, selected_item);
|
|
#endif
|
|
int id, item;
|
|
POP_MENU(id, item);
|
|
POP_MENU(id, item);
|
|
const struct mainmenu *lastm = &mainmenu[id];
|
|
|
|
if (id == M_SETKEYS)
|
|
{
|
|
for (int i = 0; i < ctx_data.act_count; i++)
|
|
{
|
|
if (ctx_data.act_map[i].display_pos == item - 2)
|
|
{
|
|
int col = keyset.view_lastcol;
|
|
struct button_mapping * bm = &ctx_data.act_map[i].map;
|
|
if (col == 1) /* Action */
|
|
{
|
|
bm->action_code = selected_item;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
synclist_set(lastm->index, item-1, lastm->items, 1);
|
|
rb->gui_synclist_draw(lists);
|
|
}
|
|
else if (lists->data == MENU_ID(M_CONTEXTS))
|
|
{
|
|
#ifdef ROCKBOX_HAS_LOGF
|
|
const char *name = list_get_name_cb(selected_item, lists->data, logfnamebuf, sizeof(logfnamebuf));
|
|
logf("CTX %s %d (0x%X)", name, selected_item, selected_item);
|
|
#endif
|
|
int id, item;
|
|
POP_MENU(id, item);
|
|
POP_MENU(id, item);
|
|
struct mainmenu *lastm = &mainmenu[id];
|
|
if (id == M_SETKEYS)
|
|
{
|
|
bool found = false;
|
|
int newctx = selected_item;
|
|
|
|
for (int i = 0; i < ctx_data.act_count; i++)
|
|
{
|
|
if (ctx_data.act_map[i].display_pos == item - 2)
|
|
{
|
|
int col = keyset.view_lastcol;
|
|
if (col == 0) /* Context */
|
|
{
|
|
ctx_data.act_map[i].context = newctx;
|
|
/* check if this context exists (if not create it) */
|
|
for (int j = 0; j < ctx_data.ctx_count; j++)
|
|
{
|
|
if (ctx_data.ctx_map[j].context == newctx)
|
|
{
|
|
found = true;;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (found == false)
|
|
{
|
|
keymap_add_context_entry(newctx);
|
|
lastm->items++;
|
|
}
|
|
}
|
|
synclist_set(lastm->index, item-1, lastm->items, 1);
|
|
rb->gui_synclist_draw(lists);
|
|
}
|
|
else if (lists->data == MENU_ID(M_BUTTONS))
|
|
{
|
|
#ifdef ROCKBOX_HAS_LOGF
|
|
const char *name = list_get_name_cb(selected_item, lists->data, logfnamebuf, sizeof(logfnamebuf));
|
|
logf("BTN %s", name);
|
|
#endif
|
|
int id, item;
|
|
POP_MENU(id, item);
|
|
POP_MENU(id, item);
|
|
const struct mainmenu *lastm = &mainmenu[id];
|
|
|
|
if (id == M_SETKEYS)
|
|
{
|
|
for (int i = 0; i < ctx_data.act_count; i++)
|
|
{
|
|
if (ctx_data.act_map[i].display_pos == item - 2)
|
|
{
|
|
int col = keyset.view_lastcol;
|
|
struct button_mapping * bm = &ctx_data.act_map[i].map;
|
|
const struct available_button *btn = &available_buttons[selected_item];
|
|
if (col == 2) /* BUTTON*/
|
|
{
|
|
if (btn->value == BUTTON_NONE)
|
|
bm->button_code = btn->value;
|
|
else
|
|
bm->button_code |= btn->value;
|
|
}
|
|
else if (col == 3) /* PREBTN */
|
|
{
|
|
if (btn->value == BUTTON_NONE)
|
|
bm->pre_button_code = btn->value;
|
|
else
|
|
bm->pre_button_code |= btn->value;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
synclist_set(lastm->index, item-1, lastm->items, 1);
|
|
rb->gui_synclist_draw(lists);
|
|
}
|
|
}
|
|
return PLUGIN_OK;
|
|
}
|
|
|
|
static void cleanup(void *parameter)
|
|
{
|
|
(void)parameter;
|
|
keyremap_save_user_keys(false);
|
|
}
|
|
|
|
int menu_action_cb(int *action, int selected_item, bool* exit, struct gui_synclist *lists)
|
|
{
|
|
int status = PLUGIN_OK;
|
|
/* Top Level Menu Actions */
|
|
if (lists->data == MENU_ID(M_ROOT))
|
|
{
|
|
status = menu_action_root(action, selected_item, exit, lists);
|
|
}
|
|
else if (lists->data == MENU_ID(M_SETKEYS))
|
|
{
|
|
status = menu_action_setkeys(action, selected_item, exit, lists);
|
|
}
|
|
else if (lists->data == MENU_ID(M_TESTKEYS))
|
|
{
|
|
status = menu_action_testkeys(action, selected_item, exit, lists);
|
|
}
|
|
else if (lists->data == MENU_ID(M_DELKEYS))
|
|
{
|
|
status = menu_action_listfiles(action, selected_item, exit, lists);
|
|
}
|
|
else if (lists->data == MENU_ID(M_LOADKEYS))
|
|
{
|
|
status = menu_action_listfiles(action, selected_item, exit, lists);
|
|
}
|
|
|
|
if (status == GOTO_ACTION_DEFAULT_HANDLER)
|
|
goto default_handler;
|
|
/* Submenu Actions */
|
|
menu_action_submenus(action, selected_item, exit, lists);
|
|
|
|
/* Global cancel */
|
|
if (*action == ACTION_STD_CANCEL)
|
|
{
|
|
logf("CANCEL BUTTON");
|
|
if (lists->data == MENU_ID(M_TESTKEYS))
|
|
{
|
|
keytest.keymap = NULL;
|
|
keytest.context = -1;
|
|
}
|
|
|
|
if (lists->data != MENU_ID(M_ROOT))
|
|
{
|
|
int id, item;
|
|
POP_MENU(id, item);
|
|
POP_MENU(id, item);
|
|
const struct mainmenu *lastm = &mainmenu[id];
|
|
synclist_set(lastm->index, item-1, lastm->items, 1);
|
|
rb->gui_synclist_draw(lists);
|
|
}
|
|
else
|
|
{
|
|
/* save changes? */
|
|
if (ctx_data.act_count + ctx_data.ctx_count >= 2)
|
|
{
|
|
int entries = 0;
|
|
struct button_mapping *keymap = keyremap_create_temp(&entries);
|
|
if (keymap != NULL && keyset.crc32 != rb->crc_32(keymap,
|
|
entries * sizeof(struct button_mapping), 0xFFFFFFFF))
|
|
{
|
|
if (rb->yesno_pop("Save Keymap?") == true)
|
|
{
|
|
keyremap_save_user_keys(true);
|
|
goto default_handler;
|
|
}
|
|
}
|
|
}
|
|
*exit = true;
|
|
}
|
|
}
|
|
default_handler:
|
|
if (rb->default_event_handler_ex(*action, cleanup, NULL) == SYS_USB_CONNECTED)
|
|
{
|
|
*exit = true;
|
|
return PLUGIN_USB_CONNECTED;
|
|
}
|
|
return PLUGIN_OK;
|
|
}
|
|
|
|
static void synclist_set(int id, int selected_item, int items, int sel_size)
|
|
{
|
|
void* menu_id = MENU_ID(id);
|
|
static char menu_title[64]; /* title is used by every menu */
|
|
PUSH_MENU(id, selected_item);
|
|
|
|
if (items <= 0)
|
|
return;
|
|
if (selected_item < 0)
|
|
selected_item = 0;
|
|
|
|
list_voice_cb(0, menu_id);
|
|
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_select_item(&lists, selected_item);
|
|
printcell_enable(false);
|
|
|
|
if (menu_id == MENU_ID(M_ROOT))
|
|
{
|
|
lang_strlcpy(menu_title, mainmenu[M_ROOT].name, sizeof(menu_title));
|
|
rb->gui_synclist_set_title(&lists, menu_title, Icon_Plugin);
|
|
}
|
|
else if (menu_id == MENU_ID(M_SETKEYS))
|
|
{
|
|
keyset.view_columns = printcell_set_columns(&lists, NULL,
|
|
ACTVIEW_HEADER, Icon_Rockbox);
|
|
printcell_enable(true);
|
|
int curcol = printcell_get_column_selected();
|
|
if (keyset.view_lastcol >= keyset.view_columns)
|
|
keyset.view_lastcol = -1;
|
|
/* restore column position */
|
|
while (keyset.view_lastcol > -1 && curcol != keyset.view_lastcol)
|
|
{
|
|
curcol = printcell_increment_column(1, true);
|
|
}
|
|
keyset.view_lastcol = curcol;
|
|
}
|
|
else
|
|
{
|
|
int id;
|
|
PEEK_MENU_ID(id);
|
|
lang_strlcpy(menu_title, mainitem(id)->name, sizeof(menu_title));
|
|
rb->gui_synclist_set_title(&lists, menu_title, Icon_Submenu_Entered);
|
|
/* if (menu_title[0] == '$'){ printcell_enable(true); } */
|
|
}
|
|
}
|
|
|
|
static void keyremap_set_buffer(void* buffer, size_t buf_size)
|
|
{
|
|
/* set up the keyremap action buffer
|
|
* contexts start at the front and work towards the back
|
|
* actions start at the back and work towards front
|
|
* if they meet buffer is out of space (checked by ctx & btn add entry fns)
|
|
*/
|
|
keyremap_buffer.buffer = buffer;
|
|
keyremap_buffer.buf_size = buf_size;
|
|
keyremap_reset_buffer();
|
|
}
|
|
|
|
enum plugin_status plugin_start(const void* parameter)
|
|
{
|
|
int ret = PLUGIN_OK;
|
|
int selected_item = -1;
|
|
int action;
|
|
bool redraw = true;
|
|
bool exit = false;
|
|
if (parameter)
|
|
{
|
|
//
|
|
}
|
|
|
|
size_t buf_size;
|
|
void* buffer = rb->plugin_get_buffer(&buf_size);
|
|
keyremap_set_buffer(buffer, buf_size);
|
|
|
|
mainmenu[M_BUTTONS].items = available_button_count;
|
|
/* add back item to each submenu */
|
|
for (int i = 1; i < M_LAST_ITEM; i++)
|
|
mainmenu[i].items += 1;
|
|
|
|
#if 0
|
|
keymap_add_context_entry(CONTEXT_WPS);
|
|
keymap_add_button_entry(CONTEXT_WPS, ACTION_WPS_PLAY, BUTTON_VOL_UP, BUTTON_NONE);
|
|
keymap_add_button_entry(CONTEXT_WPS, ACTION_WPS_STOP, BUTTON_VOL_DOWN | BUTTON_REPEAT, BUTTON_NONE);
|
|
|
|
keymap_add_context_entry(CONTEXT_MAINMENU);
|
|
keymap_add_button_entry(CONTEXT_MAINMENU, ACTION_STD_PREV, BUTTON_VOL_UP, BUTTON_NONE);
|
|
keymap_add_button_entry(CONTEXT_MAINMENU, ACTION_STD_NEXT, BUTTON_VOL_DOWN, BUTTON_NONE);
|
|
|
|
keymap_add_context_entry(CONTEXT_STD);
|
|
keymap_add_button_entry(CONTEXT_STD, ACTION_STD_OK, BUTTON_VOL_UP, BUTTON_NONE);
|
|
keymap_add_button_entry(CONTEXT_STD, ACTION_STD_OK, BUTTON_VOL_DOWN, BUTTON_NONE);
|
|
#endif
|
|
|
|
keyset.crc32 = 0;
|
|
keyset.view_lastcol = -1;
|
|
if (!exit)
|
|
{
|
|
const struct mainmenu *mainm = &mainmenu[M_ROOT];
|
|
synclist_set(mainm->index, 0, mainm->items, 1);
|
|
rb->gui_synclist_draw(&lists);
|
|
|
|
while (!exit)
|
|
{
|
|
action = rb->get_action(CONTEXT_LIST, HZ / 10);
|
|
|
|
if (action == ACTION_NONE)
|
|
{
|
|
if (redraw)
|
|
{
|
|
action = ACTION_REDRAW;
|
|
redraw = false;
|
|
}
|
|
}
|
|
else
|
|
redraw = true;
|
|
|
|
ret = menu_action_cb(&action, selected_item, &exit, &lists);
|
|
if (rb->gui_synclist_do_button(&lists, &action))
|
|
continue;
|
|
selected_item = rb->gui_synclist_get_sel_pos(&lists);
|
|
|
|
mainmenu[M_SETKEYS].items = ctx_data.ctx_count + ctx_data.act_count + 2;
|
|
}
|
|
}
|
|
rb->closedir(kmffiles_dirp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
#if 0 /* Alt Example */
|
|
static int write_keyremap(const char*filename, struct button_mapping *remap_data, int count)
|
|
{
|
|
size_t res = 0;
|
|
if (!filename || !remap_data || count <= 0)
|
|
goto fail;
|
|
int fd = rb->open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
if (fd < 0)
|
|
goto fail;
|
|
if (keyremap_write_header(fd, count + 1) > 0)
|
|
{
|
|
res = keyremap_write_entries(fd, remap_data, count);
|
|
res += sizeof(struct button_mapping); /*for header */
|
|
}
|
|
fail:
|
|
rb->close(fd);
|
|
return res;
|
|
}
|
|
|
|
void alt_example(void)
|
|
{
|
|
struct button_mapping kmap[8];
|
|
|
|
kmap[0].action_code = CORE_CONTEXT_REMAP(CONTEXT_WPS);
|
|
kmap[0].button_code = 3; /*OFFSET*/
|
|
kmap[0].pre_button_code = 1; /*COUNT*/
|
|
|
|
kmap[1].action_code = CORE_CONTEXT_REMAP(CONTEXT_MAINMENU);
|
|
kmap[1].button_code = 5; /*OFFSET*/
|
|
kmap[1].pre_button_code = 2; /*COUNT*/
|
|
|
|
kmap[2].action_code = CONTEXT_STOPSEARCHING;
|
|
kmap[2].button_code = BUTTON_NONE;
|
|
kmap[2].pre_button_code = BUTTON_NONE;
|
|
|
|
kmap[3].action_code = ACTION_WPS_PLAY;
|
|
kmap[3].button_code = BUTTON_VOL_UP;
|
|
kmap[3].pre_button_code = BUTTON_NONE;
|
|
|
|
kmap[4].action_code = CONTEXT_STOPSEARCHING;
|
|
kmap[4].button_code = BUTTON_NONE;
|
|
kmap[4].pre_button_code = BUTTON_NONE;
|
|
|
|
kmap[5].action_code = ACTION_STD_NEXT;
|
|
kmap[5].button_code = BUTTON_VOL_DOWN;
|
|
kmap[5].pre_button_code = BUTTON_NONE;
|
|
|
|
kmap[6].action_code = ACTION_STD_PREV;
|
|
kmap[6].button_code = BUTTON_VOL_UP;
|
|
kmap[6].pre_button_code = BUTTON_NONE;
|
|
|
|
kmap[7].action_code = CONTEXT_STOPSEARCHING;
|
|
kmap[7].button_code = BUTTON_NONE;
|
|
kmap[7].pre_button_code = BUTTON_NONE;
|
|
|
|
write_keyremap(CORE_KEYREMAP_FILE, kmap, 8);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|