{"record":{"id":"b976be782d4f6bf2","repo":"termux/termux-app","slug":"both-key-and-macro-can-t-be-set-for-the-same-key","errorCode":null,"errorMessage":"Both key and macro can't be set for the same key. key: \"%s\", macro: \"%s\"","messagePattern":"Both key and macro can't be set for the same key\\. key: \"(.+?)\", macro: \"(.+?)\"","errorType":"validation","errorClass":"JSONException","httpStatus":null,"severity":"error","filePath":"termux-shared/src/main/java/com/termux/shared/termux/extrakeys/ExtraKeyButton.java","lineNumber":86,"sourceCode":"    /**\n     * Initialize a {@link ExtraKeyButton}.\n     *\n     * @param config The {@link JSONObject} containing the info to create the {@link ExtraKeyButton}.\n     * @param popup The {@link ExtraKeyButton} optional {@link #popup} button.\n     * @param extraKeyDisplayMap The {@link ExtraKeysConstants.ExtraKeyDisplayMap} that defines the\n     *                           display text mapping for the keys if a custom value is not defined\n     *                           by {@link #KEY_DISPLAY_NAME}.\n     * @param extraKeyAliasMap The {@link ExtraKeysConstants.ExtraKeyDisplayMap} that defines the\n     *                           aliases for the actual key names.\n     */\n    public ExtraKeyButton(@NonNull JSONObject config, @Nullable ExtraKeyButton popup,\n                          @NonNull ExtraKeysConstants.ExtraKeyDisplayMap extraKeyDisplayMap,\n                          @NonNull ExtraKeysConstants.ExtraKeyDisplayMap extraKeyAliasMap) throws JSONException {\n        String keyFromConfig = getStringFromJson(config, KEY_KEY_NAME);\n        String macroFromConfig = getStringFromJson(config, KEY_MACRO);\n        String[] keys;\n        if (keyFromConfig != null && macroFromConfig != null) {\n            throw new JSONException(\"Both key and macro can't be set for the same key. key: \\\"\" + keyFromConfig + \"\\\", macro: \\\"\" + macroFromConfig + \"\\\"\");\n        } else if (keyFromConfig != null) {\n            keys = new String[]{keyFromConfig};\n            this.macro = false;\n        } else if (macroFromConfig != null) {\n            keys = macroFromConfig.split(\" \");\n            this.macro = true;\n        } else {\n            throw new JSONException(\"All keys have to specify either key or macro\");\n        }\n\n        for (int i = 0; i < keys.length; i++) {\n            keys[i] = replaceAlias(extraKeyAliasMap, keys[i]);\n        }\n\n        this.key = TextUtils.join(\" \", keys);\n\n        String displayFromConfig = getStringFromJson(config, KEY_DISPLAY_NAME);\n        if (displayFromConfig != null) {","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/termux/termux-app/blob/3df69d1da197dd9bd71a3bafd902dffd720576b4/termux-shared/src/main/java/com/termux/shared/termux/extrakeys/ExtraKeyButton.java#L68-L104","documentation":"Thrown by the ExtraKeyButton JSON constructor when a single button config object defines BOTH the 'key' field and the 'macro' field. A button must be either a plain key (one key code) or a macro (space-separated sequence), never both. The offending key and macro values are included in the message.","triggerScenarios":"An extra-keys JSON config object contains both \"key\":\"...\" and \"macro\":\"...\" for the same button, e.g. {\"key\":\"CTRL\",\"macro\":\"ctrl x\"}.","commonSituations":"Hand-editing extra-keys.json and accidentally leaving both fields; migrating a config where a macro was added without removing the key; copy-paste error between button definitions.","solutions":["Edit the offending button in the extra-keys config: keep either 'key' or 'macro', remove the other.","Validate the JSON with a linter/schema that marks key+macro as mutually exclusive.","If the intent is a macro, use \"macro\":\"ctrl x\" and delete the \"key\" entry."],"exampleFix":"// before (invalid)\n{\"key\": \"CTRL\", \"macro\": \"CTRL x\"}\n\n// after (macro only)\n{\"macro\": \"CTRL x\"}","handlingStrategy":"validation","validationCode":"boolean hasKey = config.has(ExtraKeyButton.KEY_KEY_NAME);\nboolean hasMacro = config.has(ExtraKeyButton.KEY_MACRO);\nif (hasKey && hasMacro) {\n    throw new JSONException(\"Button must not define both 'key' and 'macro'\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    new ExtraKeyButton(config, popup, displayMap, aliasMap);\n} catch (JSONException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Both key and macro\")) {\n        // remove one field from user config and reload\n        config.remove(ExtraKeyButton.KEY_MACRO);\n    } else throw e;\n}","preventionTips":["Treat 'key' and 'macro' as mutually exclusive in any extra-keys config.","Validate button configs with a JSON schema enforcing exactly-one-of(key, macro).","When converting a key to a macro, delete the key field."],"tags":["extra-keys","json","configuration","validation","termux"],"backgroundTag":null,"analyzedSha":"3df69d1da197dd9bd71a3bafd902dffd720576b4","analyzedAt":"2026-08-13T22:46:28.294Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}