/*************************************************************************** * __________ __ ___. * 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_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_SAVEKEYS, "Save Keymap", 1), MENU_ITEM(M_LOADKEYS, "Load 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, "Delete 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_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_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