termux/termux-app · error · JSONException

All keys have to specify either key or macro

Error message

All keys have to specify either key or macro

What it means

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.

Source

Thrown at termux-shared/src/main/java/com/termux/shared/termux/extrakeys/ExtraKeyButton.java:94

     * @param extraKeyAliasMap The {@link ExtraKeysConstants.ExtraKeyDisplayMap} that defines the
     *                           aliases for the actual key names.
     */
    public ExtraKeyButton(@NonNull JSONObject config, @Nullable ExtraKeyButton popup,
                          @NonNull ExtraKeysConstants.ExtraKeyDisplayMap extraKeyDisplayMap,
                          @NonNull ExtraKeysConstants.ExtraKeyDisplayMap extraKeyAliasMap) throws JSONException {
        String keyFromConfig = getStringFromJson(config, KEY_KEY_NAME);
        String macroFromConfig = getStringFromJson(config, KEY_MACRO);
        String[] keys;
        if (keyFromConfig != null && macroFromConfig != null) {
            throw new JSONException("Both key and macro can't be set for the same key. key: \"" + keyFromConfig + "\", macro: \"" + macroFromConfig + "\"");
        } else if (keyFromConfig != null) {
            keys = new String[]{keyFromConfig};
            this.macro = false;
        } else if (macroFromConfig != null) {
            keys = macroFromConfig.split(" ");
            this.macro = true;
        } else {
            throw new JSONException("All keys have to specify either key or macro");
        }

        for (int i = 0; i < keys.length; i++) {
            keys[i] = replaceAlias(extraKeyAliasMap, keys[i]);
        }

        this.key = TextUtils.join(" ", keys);

        String displayFromConfig = getStringFromJson(config, KEY_DISPLAY_NAME);
        if (displayFromConfig != null) {
            this.display = displayFromConfig;
        } else {
            this.display = Arrays.stream(keys)
                .map(key -> extraKeyDisplayMap.get(key, key))
                .collect(Collectors.joining(" "));
        }

        this.popup = popup;

View on GitHub (pinned to 3df69d1da1)

Solutions

  1. Add either a "key":"..." or "macro":"..." field to the button object.
  2. Check for field-name typos against ExtraKeyButton.KEY_KEY_NAME and KEY_MACRO constants.
  3. Remove placeholder/empty objects from the matrix.

Example fix

// before (invalid: no key or macro)
{"display": "Tab"}

// after (key specified)
{"key": "TAB", "display": "Tab"}
Defensive patterns

Strategy: validation

Validate before calling

boolean hasKey = config.has(ExtraKeyButton.KEY_KEY_NAME);
boolean hasMacro = config.has(ExtraKeyButton.KEY_MACRO);
if (!hasKey && !hasMacro) {
    throw new JSONException("Button must define 'key' or 'macro'");
}

Try / catch

try {
    new ExtraKeyButton(config, popup, displayMap, aliasMap);
} catch (JSONException e) {
    if (e.getMessage() != null && e.getMessage().contains("specify either key or macro")) {
        // prompt user to fix the empty/typo'd button
        reportConfigError(e.getMessage());
    } else throw e;
}

Prevention

When it happens

Trigger: 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").

Common situations: Typo in field name ('keys', 'Key', 'MACRO'); empty button object left as a placeholder; display-only object mistakenly used as a button.

Related errors


AI-assisted analysis of termux/termux-app@3df69d1da1 (2026-08-13). Data as JSON: /api/errors/c8cb60e8e8eb537b. Report an issue: GitHub.