summaryrefslogtreecommitdiffstats
path: root/apps/plugins/keyremap.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/keyremap.c')
-rw-r--r--apps/plugins/keyremap.c1616
1 files changed, 1616 insertions, 0 deletions
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 &empty;
+}
+
+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