termux/termux-app · error · JSONException

Both key and macro can't be set for the same key. key: "%s",

Error message

Both key and macro can't be set for the same key. key: "%s", macro: "%s"

What it means

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.

Source

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

    /**
     * Initialize a {@link ExtraKeyButton}.
     *
     * @param config The {@link JSONObject} containing the info to create the {@link ExtraKeyButton}.
     * @param popup The {@link ExtraKeyButton} optional {@link #popup} button.
     * @param extraKeyDisplayMap The {@link ExtraKeysConstants.ExtraKeyDisplayMap} that defines the
     *                           display text mapping for the keys if a custom value is not defined
     *                           by {@link #KEY_DISPLAY_NAME}.
     * @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) {

View on GitHub (pinned to 3df69d1da1)

Solutions

  1. Edit the offending button in the extra-keys config: keep either 'key' or 'macro', remove the other.
  2. Validate the JSON with a linter/schema that marks key+macro as mutually exclusive.
  3. If the intent is a macro, use "macro":"ctrl x" and delete the "key" entry.

Example fix

// before (invalid)
{"key": "CTRL", "macro": "CTRL x"}

// after (macro only)
{"macro": "CTRL x"}
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 not define both 'key' and 'macro'");
}

Try / catch

try {
    new ExtraKeyButton(config, popup, displayMap, aliasMap);
} catch (JSONException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Both key and macro")) {
        // remove one field from user config and reload
        config.remove(ExtraKeyButton.KEY_MACRO);
    } else throw e;
}

Prevention

When it happens

Trigger: An extra-keys JSON config object contains both "key":"..." and "macro":"..." for the same button, e.g. {"key":"CTRL","macro":"ctrl x"}.

Common situations: 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.

Related errors


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