rockbox/apps/plugins/keyremap.c
William Wilgus dfe12252bb [Feature, Plugin] lastfm_scrobbler_viewer
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
2023-04-16 23:50:24 -04:00

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 &empty;
}
/* 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