From 19d95d7b56aa8a6a3a7cdcd2ee80161de9598225 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Sun, 6 Mar 2022 02:53:06 -0500 Subject: keyremap add plain text import and export allows users to edit and view keymaps in a text editor (parser requires one entry per line (LF or CRLF work as line endings) allow naming of files on save Change-Id: Id6627171eb96210b2deeef8d15bab3f8d1e0ab3f --- apps/plugins/keyremap.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 507 insertions(+), 15 deletions(-) diff --git a/apps/plugins/keyremap.c b/apps/plugins/keyremap.c index 0a6b705e9d..a4ce1c48e6 100644 --- a/apps/plugins/keyremap.c +++ b/apps/plugins/keyremap.c @@ -21,12 +21,12 @@ #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" +#include "lib/kbd_helper.h" #ifdef ROCKBOX_HAS_LOGF #define logf rb->logf @@ -102,6 +102,8 @@ enum { M_SETKEYS, M_TESTKEYS, M_RESETKEYS, + M_EXPORTKEYS, + M_IMPORTKEYS, M_SAVEKEYS, M_LOADKEYS, M_DELKEYS, @@ -125,8 +127,10 @@ 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_EXPORTKEYS, "Export Text Keymap", 1), +MENU_ITEM(M_IMPORTKEYS, "Import Text Keymap", 1), +MENU_ITEM(M_SAVEKEYS, "Save Native Keymap", 1), +MENU_ITEM(M_LOADKEYS, "Load Native Keymaps", 1), MENU_ITEM(M_DELKEYS, "Delete Keymaps", 1), MENU_ITEM(M_TMPCORE, "Temp Core Remap", 1), MENU_ITEM(M_SETCORE, "Set Core Remap", 1), @@ -172,8 +176,24 @@ static struct mainmenu *mainitem(int selected_item) else return ∅ } - +/* Forward Declarations */ +static const char *edit_keymap_name_cb(int selected_item, void* data,char* buf, size_t buf_len); +static int keyremap_import_file(char *filenamebuf, size_t bufsz); static void synclist_set(int id, int selected_item, int items, int sel_size); + +static int prompt_filename(char *buf, size_t bufsz) +{ +#define KBD_LAYOUT "abcdefghijklmnop\nqrstuvwxyz |()[]\n1234567890 /._-+\n\n" \ + "\nABCDEFGHIJKLMNOP\nQRSTUVWXYZ |()[]\n1234567890 /._-+" + unsigned short kbd[sizeof(KBD_LAYOUT) + 10]; + unsigned short *kbd_p = kbd; + if (!kbd_create_layout(KBD_LAYOUT, kbd, sizeof(kbd))) + kbd_p = NULL; + +#undef KBD_LAYOUT + return rb->kbd_input(buf, bufsz, kbd_p) + 1; +} + static void synclist_set_update(int id, int selected_item, int items, int sel_size) { SET_MENU_ITEM(lists.selected_item + 1); /* update selected for previous menu*/ @@ -471,13 +491,18 @@ static int keyremap_save_current(const char *filename) static void keyremap_save_user_keys(bool notify) { char buf[MAX_PATH]; - int i = 1; + int i = 0; do { - rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", KMFDIR, KMFUSER, i, KMFEXT1); i++; + rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", KMFDIR, KMFUSER, i, KMFEXT1); } while (i < 100 && rb->file_exists(buf)); + if (notify && prompt_filename(buf, sizeof(buf)) <= 0) + { + return; + } + if (keyremap_save_current(buf) == 0) { if(notify) @@ -487,6 +512,128 @@ static void keyremap_save_user_keys(bool notify) rb->splashf(HZ *2, "Saved %s", buf); } +static int keyremap_export_current(char *filenamebuf, size_t bufsz) +{ + filenamebuf[bufsz - 1] = '\0'; + int i, j; + int ctx_count = 0; + size_t entrylen; + + int entry_count = ctx_data.ctx_count + ctx_data.act_count + 1;;/* (ctx_count + ctx_count + act_count + 1) */ + + if (entry_count <= 3) + return 0; + + int fd = rb->open(filenamebuf, O_WRONLY | O_CREAT | O_TRUNC, 0666); + + if (fd < 0) + return -1; + rb->fdprintf(fd, "# Key Remap\n# Device: %s\n" \ + "# Entries: %d\n\n", MODEL_NAME, entry_count - 1); + rb->fdprintf(fd, "# Each entry should be PROPER_CASE and on its own line\n" \ + "# Comments run to end of line \n"); + + for (i = 0; i <= entry_count; i++) + { + entrylen = 0; + rb->memset(filenamebuf, 0, bufsz); + edit_keymap_name_cb(i, MENU_ID(M_EXPORTKEYS), filenamebuf, bufsz); + if (i == 0) + { + ctx_menu_data.act_fmt = " {%s%s, %s, %s},\n\n"; + continue; + } + else if (i == entry_count) + { + rb->strlcpy(filenamebuf, "}\n\n", bufsz); + } + char last = '\0'; + for (j = 0; j < (int)bufsz ;j++) + { + char ch = filenamebuf[j]; + if (ch == '$' && last == '\0') /*CONTEXT*/ + { + if (ctx_count > 0) + rb->fdprintf(fd, "}\n"); + ctx_count++; + filenamebuf[j] = '\n'; + } + if (ch == '\n' && last == '\n') + { + entrylen = j; + break; + } + else if (ch == '\0') + filenamebuf[j] = ','; + last = ch; + } + + size_t bytes = rb->write(fd, filenamebuf, entrylen); + if (bytes != entrylen) + { + entry_count = -2; + goto fail; + } + } + +fail: + rb->close(fd); + + return entry_count; +} + +static void keyremap_export_user_keys(void) +{ + const bool notify = true; + char buf[MAX_PATH]; + int i = 0; + do + { + i++; + rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", "", KMFUSER, i, ".txt"); + } while (i < 100 && rb->file_exists(buf)); + + if (notify && prompt_filename(buf, sizeof(buf)) <= 0) + { + return; + } + + if (keyremap_export_current(buf, sizeof(buf)) <= 0) + { + if(notify) + rb->splash(HZ *2, "Error Saving"); + } + else if (notify) + { + rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", "", KMFUSER, i, ".txt"); + rb->splashf(HZ *2, "Saved %s", buf); + } +} + +static void keyremap_import_user_keys(void) +{ + char buf[MAX_PATH]; + struct browse_context browse; + + rb->browse_context_init(&browse, SHOW_ALL, BROWSE_SELECTONLY, "Select Keymap", + Icon_Plugin, "/", NULL); + + browse.buf = buf; + browse.bufsize = sizeof(buf); + + if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS) + { + int ret = keyremap_import_file(buf, sizeof(buf)); + if (ret <= 0) + { + keyremap_reset_buffer(); + rb->splash(HZ *2, "Error Opening"); + } + else + rb->splashf(HZ * 2, "Loaded Text Keymap "); + } +} + static int keymap_add_context_entry(int context) { int remap_context = CORE_CONTEXT_REMAP(context); @@ -538,6 +685,328 @@ fail: return 0; } +static int get_action_from_str(char *pfield, size_t bufsz) +{ + int act = -1; + for (int i=0;i < LAST_ACTION_PLACEHOLDER; i++) + { + if (rb->strncasecmp(pfield, action_name(i), bufsz) == 0) + { + logf("Action Found: %s (%d)", pfield, i); + act = i; + break; + } + } + return act; +} + +static int get_button_from_str(char *pfield, size_t bufsz) +{ + int btn = -1; + for (int i=0;i < available_button_count; i++) + { + const struct available_button* abtn = &available_buttons[i]; + if (rb->strncasecmp(pfield, abtn->name, bufsz) == 0) + { + logf("Button Found: %s (%lx)", abtn->name, abtn->value); + btn = abtn->value; + break; + } + } + if (btn < 0) /* Not Fatal */ + { + logf("Invalid Button %s", pfield); + rb->splashf(HZ, "Invalid Button %s", pfield); + } + return btn; +} + +static int parse_action_import_entry(int context, char * pbuf, size_t bufsz) +{ + size_t bufleft; + int count = 0; + char ch; + char *pfirst = NULL; + char *pfield; + int field = -1; + int act = -1; + int button = BUTTON_NONE; + int prebtn = BUTTON_NONE; + while ((ch = *(pbuf)) != '\0') + { + pfield = NULL; /* Valid names */ + if ((ch >= 'A' && ch <= 'Z') || ch == '_') + { + if (pfirst == NULL) + pfirst = pbuf; + } + else if (ch == ',') + { + if (pfirst != NULL) + { + field++; + pfield = pfirst; + pfirst = NULL; + *pbuf = '\0'; + } + } + else if (ch == ' ' || ch == '|'){;} + else + pfirst = NULL; + + if (field == 1 && pfirst != NULL && pbuf[1] == '\0') + { + field++; + pfield = pfirst; + pfirst = NULL; + } + + if (pfield != NULL) + { + char *pf; + + if (field == 0) /* action */ + { + char *pact = pfield; + pf = pfield; + while ((ch = *(pf)) != '\0') + { + if(ch == ' ') + *pf = '\0'; + else + pf++; + } + bufleft = bufsz - (pact - pbuf); + act = get_action_from_str(pact, bufleft); + + if (act < 0) + { + logf("Error Action Expected: %s", pact); + return -1; + } + } + else if (field == 1 || field == 2) /* button / pre_btn */ + { + char *pbtn = pfield; + pf = pfield; + while ((ch = *(pf)) != '\0') + { + if (pf[1] == '\0') /* last item? */ + { + pf++; + ch = '|'; + pfield = NULL; + } + else if (ch == ' ' || ch == '|') + { + *pf = '\0'; + } + + if(ch == '|') + { + bufleft = bufsz - (pbtn - pbuf); + int btn = get_button_from_str(pbtn, bufleft); + + if (btn > BUTTON_NONE) + { + if (field == 1) + button |= btn; + else if (field == 2) + prebtn |= btn; + } + + if (pfield != NULL) + { + pf++; + while ((ch = *(pf)) != '\0') + { + if (*pf == ' ') + pf++; + else + break; + } + pbtn = pf; + } + } + else + pf++; + } + + if (act < 0) + { + logf("Error Action Expected: %s", pfield); + return -1; + } + } + + pfield = NULL; + } + + pbuf++; + } + if (field == 2) + { + count = keymap_add_button_entry(context, act, button, prebtn); + if (count > 0) + { + logf("Added: Ctx: %d, Act: %d, Btn: %d PBtn: %d", + context, act, button, prebtn); + } + } + return count; +} + +static int keyremap_import_file(char *filenamebuf, size_t bufsz) +{ + size_t bufleft; + int count = 0; + int line = 0; + filenamebuf[bufsz - 1] = '\0'; + logf("keyremap: import %s", filenamebuf); + int fd = rb->open(filenamebuf, O_RDONLY); + if (fd < 0) + return -1; + + char ch; + char *pbuf; + char *pfirst; + + char *pctx = NULL; + char *pact; + int ctx = -1; + + keyremap_reset_buffer(); +next_line: + while (rb->read_line(fd, filenamebuf, (int) bufsz) > 0) + { + line++; + + + pbuf = filenamebuf; + pfirst = NULL; + pact = NULL; + char *pcomment = rb->strchr(pbuf, '#'); + if (pcomment != NULL) + { + logf("ln: %d: Skipped Comment: %s", line, pcomment); + *pcomment = '\0'; + } + + while ((ch = *(pbuf)) != '\0') + { + /* PARSE CONTEXT = { */ + if ((ch >= 'A' && ch <= 'Z') || ch == '_') + { + if (pfirst == NULL) + pfirst = pbuf; + } + else if (ch == ' ') + { + if (ctx < 0 && pfirst != NULL) + { + *pbuf = '\0'; + pctx = pfirst; + pfirst = NULL; + } + } + else if (ch == '=') + { + if (ctx < 0 && pfirst != NULL) + { + *pbuf = '\0'; + pbuf++; + pctx = pfirst; + pfirst = NULL; + } + while ((ch = *(pbuf)) != '\0') + { + if (ch == '{') + break; + pbuf++; + } + if (ch == '{' && pctx != NULL) + { + bufleft = bufsz - (pctx - filenamebuf); + ctx = -1; + for (int i=0;i < LAST_CONTEXT_PLACEHOLDER;i++) + { + if (rb->strncasecmp(pctx, context_name(i), bufleft) == 0) + { + logf("ln: %d: Context Found: %s (%d)", line, pctx, i); + if (keymap_add_context_entry(i) <= 0) + logf("ln: %d: Context Exists: %s (%d)", line, pctx, i); + ctx = i; + goto next_line; + + } + } + logf("ln: %d: ERROR { Context Expected got: %s", line, pctx); + goto fail; + } + } + else if (ch == '}') + { + if (ctx >= 0) + ctx = -1; + else + { + logf("ln: %d: ERROR no context, unexpected close {", line); + goto fail; + } + } + else if (ch == '{') /* PARSE FIELDS { ACTION, BUTTON, PREBTN } */ + { + int res = 0; + if (ctx >= 0) + { + pfirst = pbuf; + + while ((ch = *(pbuf)) != '\0') + { + if (ch == '}') + { + pact = pfirst + 1; + pfirst = NULL; + *pbuf = '\0'; + pbuf = ""; + continue; + } + pbuf++; + } + if (pact != NULL) + { + bufleft = bufsz - (pact - filenamebuf); + logf("ln: %d: Entry Found: {%s} (%d)", line, pact, 0); + res = parse_action_import_entry(ctx, pact, bufleft); + } + } + if (res <= 0) + { + logf("ln: %d: ERROR action entry expected", line); + goto fail; + } + else + { + pbuf = ""; + continue; + } + } + else + pfirst = NULL; + pbuf++; + } + + } + rb->close(fd); + count = ctx_data.ctx_count + ctx_data.act_count; + return count; + +fail: + rb->close(fd); + rb->splashf(HZ * 2, "Error @ line %d", line); + return 0; +} + static int keyremap_load_file(const char *filename) { logf("keyremap: load %s", filename); @@ -709,7 +1178,18 @@ static const char *menu_useract_items_cb(int selected_item, void* data, static char buf_button[MAX_BUTTON_NAME * MAX_BUTTON_COMBO]; static char buf_prebtn[MAX_BUTTON_NAME * MAX_BUTTON_COMBO]; int i; - (void)data; + int szbtn = sizeof("BUTTON"); + int szctx = sizeof("CONTEXT"); + int szact = sizeof("ACTION"); + const char* ctxfmt = "%s"; + + if (data == MENU_ID(M_EXPORTKEYS)) + { + szbtn = 0; + szctx = 0; + szact = 0; + ctxfmt = "$%s = {\n\n"; + } buf[0] = '\0'; for(i = 0; i < ctx_data.ctx_count; i ++) { @@ -720,7 +1200,7 @@ static const char *menu_useract_items_cb(int selected_item, void* data, 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)); + rb->snprintf(buf, buf_len, ctxfmt, context_name(ctx_data.ctx_map[i].context)); return buf; } } @@ -730,19 +1210,23 @@ static const char *menu_useract_items_cb(int selected_item, void* data, { int context = ctx_data.act_map[i].context; char ctxbuf[action_helper_maxbuffer]; - char *pctxbuf = ctxbuf; + char *pctxbuf = "\0"; char *pactname; - rb->snprintf(ctxbuf, sizeof(ctxbuf), "%s", context_name(context)); - pctxbuf += sizeof("CONTEXT"); + if (data != MENU_ID(M_EXPORTKEYS)) + { + pctxbuf = ctxbuf; + rb->snprintf(ctxbuf, sizeof(ctxbuf), ctxfmt, context_name(context)); + pctxbuf += szctx;//sizeof("CONTEXT") + } struct button_mapping * bm = &ctx_data.act_map[i].map; pactname = action_name(bm->action_code); - pactname += sizeof("ACTION"); + pactname += szact;//sizeof("ACTION") /* BUTTON & PRE_BUTTON */ btnval_to_name(buf_button, sizeof(buf_button), bm->button_code); btnval_to_name(buf_prebtn, sizeof(buf_prebtn), bm->pre_button_code); rb->snprintf(buf, buf_len, ctx_menu_data.act_fmt, pctxbuf, - pactname, buf_button + sizeof("BUTTON"), buf_prebtn + sizeof("BUTTON")); + pactname, buf_button + szbtn, buf_prebtn + szbtn); return buf; } } @@ -752,7 +1236,6 @@ static const char *menu_useract_items_cb(int selected_item, void* data, 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) { @@ -930,6 +1413,16 @@ int menu_action_root(int *action, int selected_item, bool* exit, struct gui_sync keyremap_save_user_keys(true); goto default_handler; } + else if (cur->menuid == MENU_ID(M_EXPORTKEYS)) + { + keyremap_export_user_keys(); + goto default_handler; + } + else if (cur->menuid == MENU_ID(M_IMPORTKEYS)) + { + keyremap_import_user_keys(); + goto default_handler; + } else if (cur->menuid == MENU_ID(M_DELKEYS) || cur->menuid == MENU_ID(M_LOADKEYS)) { @@ -1118,7 +1611,6 @@ int menu_action_testkeys(int *action, int selected_item, bool* exit, struct gui_ goto default_handler; } - if (*action == ACTION_STD_CANCEL && selected_item == 0 && keytest.keymap != NULL) { keytest.index -= 2; -- cgit v1.2.3