{"record":{"id":"c8cb60e8e8eb537b","repo":"termux/termux-app","slug":"all-keys-have-to-specify-either-key-or-macro","errorCode":null,"errorMessage":"All keys have to specify either key or macro","messagePattern":"All keys have to specify either key or macro","errorType":"validation","errorClass":"JSONException","httpStatus":null,"severity":"error","filePath":"termux-shared/src/main/java/com/termux/shared/termux/extrakeys/ExtraKeyButton.java","lineNumber":94,"sourceCode":"     * @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) {\n            this.display = displayFromConfig;\n        } else {\n            this.display = Arrays.stream(keys)\n                .map(key -> extraKeyDisplayMap.get(key, key))\n                .collect(Collectors.joining(\" \"));\n        }\n\n        this.popup = popup;","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/termux/termux-app/blob/3df69d1da197dd9bd71a3bafd902dffd720576b4/termux-shared/src/main/java/com/termux/shared/termux/extrakeys/ExtraKeyButton.java#L76-L112","documentation":"Thrown by the same ExtraKeyButton constructor when a button config object defines NEITHER 'key' nor 'macro'. At least one is required so the button knows what to emit. This usually means the JSON object is empty or only contains display/auxiliary fields.","triggerScenarios":"An extra-keys button object like {} or {\"display\":\"foo\"} with no key/macro field; a malformed config where the key name was misspelled (e.g. \"keys\" instead of \"key\").","commonSituations":"Typo in field name ('keys', 'Key', 'MACRO'); empty button object left as a placeholder; display-only object mistakenly used as a button.","solutions":["Add either a \"key\":\"...\" or \"macro\":\"...\" field to the button object.","Check for field-name typos against ExtraKeyButton.KEY_KEY_NAME and KEY_MACRO constants.","Remove placeholder/empty objects from the matrix."],"exampleFix":"// before (invalid: no key or macro)\n{\"display\": \"Tab\"}\n\n// after (key specified)\n{\"key\": \"TAB\", \"display\": \"Tab\"}","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 define 'key' or 'macro'\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    new ExtraKeyButton(config, popup, displayMap, aliasMap);\n} catch (JSONException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"specify either key or macro\")) {\n        // prompt user to fix the empty/typo'd button\n        reportConfigError(e.getMessage());\n    } else throw e;\n}","preventionTips":["Every button object must contain a 'key' or 'macro' field.","Watch for field-name typos ('keys', 'Key') that silently make a button empty.","Remove empty placeholder objects from the matrix."],"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"}