diff --git a/apps/SOURCES b/apps/SOURCES index 1628524805..f440104a19 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -7,6 +7,7 @@ alarm_menu.c #endif abrepeat.c bookmark.c +core_keymap.c debug_menu.c filetypes.c language.c diff --git a/apps/action.c b/apps/action.c index b31c4fa927..3a4cc2ff64 100644 --- a/apps/action.c +++ b/apps/action.c @@ -69,6 +69,11 @@ static action_last_t action_last = .tick = 0, .wait_for_release = false, +#ifndef DISABLE_ACTION_REMAP + .check_remap = false, + .core_keymap = NULL, +#endif + #ifdef HAVE_TOUCHSCREEN .ts_data = 0, .ts_short_press = false, @@ -499,7 +504,7 @@ static inline int action_code_worker(action_last_t *last, int *end ) { int ret = ACTION_UNKNOWN; - int i = 0; + int i = *end; unsigned int found = 0; while (cur->items[i].button_code != BUTTON_NONE) { @@ -588,7 +593,9 @@ static inline void action_code_lookup(action_last_t *last, action_cur_t *cur) int action = ACTION_NONE; int context = cur->context; int i = 0; - +#ifndef DISABLE_ACTION_REMAP + last->check_remap = (last->core_keymap != NULL); +#endif cur->is_prebutton = false; #ifdef HAVE_LOCKED_ACTIONS @@ -609,9 +616,42 @@ static inline void action_code_lookup(action_last_t *last, action_cur_t *cur) #endif if ((context & CONTEXT_PLUGIN) && cur->get_context_map) + { cur->items = cur->get_context_map(context); + } +#ifndef DISABLE_ACTION_REMAP + else if(last->check_remap) /* attempt to look up the button in user supplied remap */ + { + cur->items = last->core_keymap; + i = 0; + action = ACTION_UNKNOWN; + /* check the lut at the beginning for the desired context */ + while (cur->items[i].action_code != (int) CONTEXT_STOPSEARCHING) + { + if (cur->items[i].action_code == CORE_CONTEXT_REMAP(context)) + { + i = cur->items[i].button_code; + action = action_code_worker(last, cur, &i); + break; + } + i++; + } + + if (action != ACTION_UNKNOWN) + break; + else + { + /* Not found -- fall through to inbuilt keymaps */ + i = 0; + last->check_remap = false; + cur->items = get_context_mapping(context); + } + } +#endif else + { cur->items = get_context_mapping(context); + } if (cur->items != NULL) { @@ -1150,6 +1190,66 @@ int get_action(int context, int timeout) return action; } +int action_set_keymap(struct button_mapping* core_keymap, int count) +{ + +#ifdef DISABLE_ACTION_REMAP + count = -1; +#else + if (count > 0 && core_keymap != NULL) /* saf-tey checks :) */ + { + int i = 0; + if (core_keymap[count - 1].action_code != (int) CONTEXT_STOPSEARCHING || + core_keymap[count - 1].button_code != BUTTON_NONE) /* check for sentinel at end*/ + count = -1; + + /* check the lut at the beginning for invalid offsets */ + while (count > 0 && core_keymap[i].action_code != (int) CONTEXT_STOPSEARCHING) + { + if ((core_keymap[i].action_code & CONTEXT_REMAPPED) == CONTEXT_REMAPPED) + { + int firstbtn = core_keymap[i].button_code; + int endpos = firstbtn + core_keymap[i].pre_button_code; + if (firstbtn > count || firstbtn < i || endpos > count) + { + /* offset out of bounds */ + count = -2; + break; + } + + if (core_keymap[endpos].action_code != (int) CONTEXT_STOPSEARCHING) + { + /* stop sentinel is not at end of action lut*/ + count = -3; + } + } + else /* something other than a context remap in the lut */ + { + count = -4; + break; + } + + i++; + + if (i >= count) /* no sentinel in the lut */ + { + count = -5; + break; + } + } + + if (count <= 0) + core_keymap = NULL; + } + else +#endif + { + core_keymap = NULL; + } + action_last.core_keymap = core_keymap; + return count; +} + int get_custom_action(int context,int timeout, const struct button_mapping* (*get_context_map)(int)) { diff --git a/apps/action.h b/apps/action.h index 3217ce4d6f..7fadc015c8 100644 --- a/apps/action.h +++ b/apps/action.h @@ -28,10 +28,13 @@ #define TIMEOUT_NOBLOCK 0 #define CONTEXT_STOPSEARCHING 0xFFFFFFFF + #define CONTEXT_REMOTE 0x80000000 /* | this against another context to get remote buttons for that context */ #define CONTEXT_CUSTOM 0x40000000 /* | this against anything to get your context number */ #define CONTEXT_CUSTOM2 0x20000000 /* as above */ #define CONTEXT_PLUGIN 0x10000000 /* for plugins using get_custom_action */ +#define CONTEXT_REMAPPED 0x08000000 /* marker for key remap context table */ +#define CORE_CONTEXT_REMAP(context) (CONTEXT_REMAPPED | context) #ifdef HAVE_LOCKED_ACTIONS #define CONTEXT_LOCKED 0x04000000 /* flag to use alternate keymap when screen is locked */ #endif @@ -415,6 +418,11 @@ typedef struct bool repeated; bool wait_for_release; +#ifndef DISABLE_ACTION_REMAP + bool check_remap; + struct button_mapping* core_keymap; +#endif + #ifdef HAVE_TOUCHSCREEN bool ts_short_press; int ts_data; @@ -441,6 +449,9 @@ bool action_userabort(int timeout); /* no other code should need this apart from action.c */ const struct button_mapping* get_context_mapping(int context); +/* load a key map to allow buttons for actions to be remapped see: core_keymap */ +int action_set_keymap(struct button_mapping* core_button_map, int count); + /* returns the status code variable from action.c for the button just pressed If button != NULL it will be set to the actual button code */ #define ACTION_REMOTE 0x1 /* remote was pressed */ diff --git a/apps/core_keymap.c b/apps/core_keymap.c new file mode 100644 index 0000000000..0a7241a9e0 --- /dev/null +++ b/apps/core_keymap.c @@ -0,0 +1,106 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2020 by 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 "action.h" +#include "core_alloc.h" +#include "core_keymap.h" + +#if !defined(__PCTOOL__) || defined(CHECKWPS) +int core_load_key_remap(const char *filename) +{ + static int keymap_handle = -1; + char *buf; + int fd = -1; + int count = 0; + size_t fsize = 0; + if (keymap_handle > 0) /* free old buffer */ + { + action_set_keymap(NULL, -1); + keymap_handle = core_free(keymap_handle); + } + if (filename != NULL) + count = open_key_remap(filename, &fd, &fsize); + while (count > 0) + { + + keymap_handle = core_alloc_ex("key remap", fsize, &buflib_ops_locked); + if (keymap_handle <= 0) + { + count = -30; + break; + } + buf = core_get_data(keymap_handle); + if (read(fd, buf, fsize) == (ssize_t) fsize) + { + count = action_set_keymap((struct button_mapping *) buf, count); + } + else + count = -40; + break; + } + close(fd); + return count; +} + +int open_key_remap(const char *filename, int *fd, size_t *fsize) +{ + int count = 0; + + while (filename && fd && fsize) + { + *fsize = 0; + *fd = open(filename, O_RDONLY); + if (*fd) + { + *fsize = 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}; + 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; +} + +#endif /* !defined(__PCTOOL__) */ diff --git a/apps/core_keymap.h b/apps/core_keymap.h new file mode 100644 index 0000000000..39d35e9cd9 --- /dev/null +++ b/apps/core_keymap.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2020 by 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. + * + ****************************************************************************/ +#ifndef CORE_KEYMAP_H +#define CORE_KEYMAP_H + +#include +#include +#include "config.h" +#define KEYREMAP_VERSION 1 +#define KEYREMAP_HEADERID (LAST_ACTION_PLACEHOLDER | (TARGET_ID << 8)) + +/* If exists remap file will be loaded at startup */ +#define CORE_KEYREMAP_FILE ROCKBOX_DIR "/keyremap.kmf" + +/* open_key_remap(filename , *fd (you must close file_descriptor), *fsize) + * checks/strips header and returns remaining count + * fd is opened and set to first record + * filesize contains the size of the remaining records +*/ +int open_key_remap(const char *filename, int *fd, size_t *filesize); + +/* load a remap file to allow buttons for actions to be remapped */ +int core_load_key_remap(const char *filename); + +/* + * entries consist of 3 int [action, button, prebtn] + * the header (VERSION, LAST_DEFINED_ACTION, count) is stripped by open_key_remap + * + * context look up table is at the beginning + * action_code contains (context | CONTEXT_REMAPPED) + * button_code contains index of first remapped action for the matched context + * prebtn_code contains count of actions in this remapped context + * [-1] REMAP_VERSION, REMAP_HEADERID, entry count(9) / DISCARDED AFTER LOAD + * [0] CORE_CONTEXT_REMAP(ctx1), offset1=(3), count=(1) + * [1] CORE_CONTEXT_REMAP(ctx2, offset2=(5), count=(2) + * [2] sentinel, 0, 0 + * [3] act0, btn, 0 + * [4] sentinel 0, 0 + * [5] act1, btn, 0 + * [6] act2, btn1 + * [7] sentinel, 0, 0 + * + * Note: + * last entry of each group is always the sentinel [CONTEXT_STOPSEARCHING, BUTTON_NONE, BUTTON_NONE] + * contexts must match exactly -- re-mapped contexts run before the built in w/ fall through contexts + * ie. you can't remap std_context and expect it to match std_context actions from the WPS context. + */ + +#endif /* CORE_KEYMAP_H */ + diff --git a/apps/main.c b/apps/main.c index a88cd73ef7..2f3b246210 100644 --- a/apps/main.c +++ b/apps/main.c @@ -31,6 +31,7 @@ #include "led.h" #include "../kernel-internal.h" #include "button.h" +#include "core_keymap.h" #include "tree.h" #include "filetypes.h" #include "panic.h" @@ -175,6 +176,15 @@ int main(void) usb_start_monitoring(); #endif +#if !defined(DISABLE_ACTION_REMAP) && defined(CORE_KEYREMAP_FILE) + if (file_exists(CORE_KEYREMAP_FILE)) + { + int mapct = core_load_key_remap(CORE_KEYREMAP_FILE); + if (mapct <= 0) + splashf(HZ, "key remap failed: %d, %s", mapct, CORE_KEYREMAP_FILE); + } +#endif + #ifdef AUTOROCK { char filename[MAX_PATH]; diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index bb0960f501..89aba0e32f 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -47,6 +47,7 @@ jackpot,games jewels,games jpeg,viewers keybox,apps +keyremap,apps lamp,apps logo,demos lrcplayer,apps diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index ab77dcde58..d2f3c39d54 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -11,6 +11,7 @@ cube.c dict.c jackpot.c keybox.c +keyremap.c logo.c lrcplayer.c mosaique.c diff --git a/apps/plugins/keyremap.c b/apps/plugins/keyremap.c new file mode 100644 index 0000000000..acd23172f0 --- /dev/null +++ b/apps/plugins/keyremap.c @@ -0,0 +1,1616 @@ +/*************************************************************************** + * __________ __ ___. + * 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 "../open_plugin.h" + +#include "lib/action_helper.h" +#include "lib/button_helper.h" +#include "lib/pluginlib_actions.h" +#include "lib/printcell_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 TEST_COUNTDOWN_MS 1590 + +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_SAVEKEYS, + M_LOADKEYS, + M_DELKEYS, + M_SETCORE, + 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_SAVEKEYS, "Save Keymap", 1), +MENU_ITEM(M_LOADKEYS, "Load Keymaps", 1), +MENU_ITEM(M_DELKEYS, "Delete Keymaps", 1), +MENU_ITEM(M_SETCORE, "Set 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 ), +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 ∅ +} + +static void synclist_set(int id, int selected_item, int items, int sel_size); +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); +} + +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) +{ + 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("keyremap create temp entry count: %d", entry_count); + logf("keyremap bytes: %ld, avail: %ld", 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 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; } + + 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) + 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 = 1; + do + { + rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", KMFDIR, KMFUSER, i, KMFEXT1); + i++; + } while (i < 100 && rb->file_exists(buf)); + + if (keyremap_save_current(buf) == 0) + { + if(notify) + rb->splash(HZ *2, "Error Saving"); + } + else if (notify) + rb->splashf(HZ *2, "Saved %s", buf); +} + +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 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); + int offset = entry.button_code; + int entries = entry.pre_button_code; + if (offset == 0 || entries <= 0) + { + logf("keyremap: error reading offset"); + } + 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 action = entry.action_code; + int button = entry.button_code; + int prebtn = entry.pre_button_code; + + if (action == (int)CONTEXT_STOPSEARCHING || button == BUTTON_NONE) + { + logf("keyremap: entry invalid"); + goto fail; + } + logf("keyremap: found ctx: %d, act: %d btn: %d pbtn: %d", + context, action, button, prebtn); + keymap_add_button_entry(context, action, 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; + (void)data; + 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", + context_name(ctx_data.ctx_map[i].context), + "Select$to add$actions"); + else + rb->snprintf(buf, buf_len, "%s", context_name(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 = ctxbuf; + char *pactname; + rb->snprintf(ctxbuf, sizeof(ctxbuf), "%s", context_name(context)); + pctxbuf += sizeof("CONTEXT"); + struct button_mapping * bm = &ctx_data.act_map[i].map; + pactname = action_name(bm->action_code); + pactname += 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 + sizeof("BUTTON"), buf_prebtn + sizeof("BUTTON")); + return buf; + } + } + return "Error!"; +} + +static const char *edit_keymap_name_cb(int selected_item, void* data, + char* buf, size_t buf_len) +{ + (void)data; + 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 >", context_name(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 context_name(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 + { + char buf[64]; + 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_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, Restart Device"); + 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_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)) + { + rb->splashf(HZ / 2, "Delete Current?"); + int usract = ACTION_NONE; + while (usract <= ACTION_UNKNOWN) + { + usract = rb->get_action(CONTEXT_STD, HZ / 10); + } + if (usract == ACTION_STD_OK) + { + 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(lists, 1, true); + *action = ACTION_NONE; + } + } + } + else if (*action == ACTION_STD_CANCEL) + { + keyset.view_lastcol = printcell_increment_column(lists, -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 action = 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, action, 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)) + { + rb->splashf(HZ / 2, "Delete %s ?", buf); + int action = ACTION_NONE; + while (action <= ACTION_UNKNOWN) + { + action = rb->get_action(CONTEXT_STD, HZ / 10); + } + if (action == ACTION_STD_OK) + { + rb->remove(buf); + cur->items--; + lists->nb_items--; + } + } + else if (lists->data == MENU_ID(M_LOADKEYS)) + { + keyremap_load_file(buf); + rb->splashf(HZ * 2, "Loaded %s ", buf); + } + } + } + } + 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)) + { + rb->splashf(HZ / 2, "Save?"); + int action = ACTION_NONE; + while (action <= ACTION_UNKNOWN) + { + action = rb->get_action(CONTEXT_STD, HZ / 10); + } + if (action == ACTION_STD_OK) + { + 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_limit_scroll(&lists,true); + rb->gui_synclist_select_item(&lists, selected_item); + printcell_enable(&lists, false, 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)) + { + printcell_enable(&lists, true, true); + keyset.view_columns = printcell_set_columns(&lists, ACTVIEW_HEADER, Icon_Rockbox); + int curcol = printcell_increment_column(&lists, 0, true); + 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(&lists, 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(&lists, true, 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,LIST_WRAP_UNLESS_HELD)) + 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 diff --git a/apps/plugins/lib/action_helper.h b/apps/plugins/lib/action_helper.h index 58d9c6c303..53f5c840f8 100644 --- a/apps/plugins/lib/action_helper.h +++ b/apps/plugins/lib/action_helper.h @@ -27,7 +27,7 @@ */ #ifndef _ACTION_HELPER_H_ #define _ACTION_HELPER_H_ - +extern const size_t action_helper_maxbuffer; char* action_name(int action); char* context_name(int context); diff --git a/apps/plugins/lib/action_helper.pl b/apps/plugins/lib/action_helper.pl index 1dfdcfd070..742419e23b 100755 --- a/apps/plugins/lib/action_helper.pl +++ b/apps/plugins/lib/action_helper.pl @@ -140,10 +140,12 @@ printf "#define CONTEXTBUFSZ %d\n\n", $len_max_context; if ($len_max_action > $len_max_context) { + print "const size_t action_helper_maxbuffer = ACTIONBUFSZ;\n"; print "static char name_buf[ACTIONBUFSZ];\n"; } else { + print "const size_t action_helper_maxbuffer = CONTEXTBUFSZ;\n"; print "static char name_buf[CONTEXTBUFSZ];\n"; } print <) { chomp($line); if($line =~ /^#define (BUTTON_[^\s]+) (.+)$/) { $def = "{\"$1\", $2},\n"; + my $slen = length($1) + 1; # NULL terminator + if ($slen > $len_max_button) { $len_max_button = $slen; } $val = $2; if($val =~ /^0/) { @@ -53,6 +56,8 @@ print <