[16.3.0] CGEventTap fallback + ISO keyboard type: grave_accent_and_tilde / non_us_backslash remap types the wrong character

Author: deyanpCreated Sep 16, 2026Updated Sep 16, 2026

Summary

With enable_cgeventtap_fallback enabled and the virtual keyboard set to iso, a remap between grave_accent_and_tilde and non_us_backslash produces the wrong character. The key types § instead of `. Disabling the fallback fixes it immediately.

The cause is that macOS swaps the CGEvent key codes of these two keys on an ISO keyboard type, but keyboard_suppression matches the events coming back through the event tap by key code, so Karabiner-Elements does not recognise its own output and manipulates it a second time.

Environment

  • Karabiner-Elements 16.3.0
  • macOS 26.6.2 (25G83), MacBook Pro Mac16,5
  • Devices: built-in ISO keyboard; external ANSI keyboard (Alice 98, vendor 14000 / product 12299), grabbed normally via HID — the fallback is not needed for it
  • virtual_hid_keyboard.keyboard_type_v2: iso (correct for the built-in keyboard)

Configuration

The external keyboard is ANSI while the virtual keyboard must stay ISO for the built-in one, so the ANSI board carries one device-level modification:

json
{
  "identifiers": { "is_keyboard": true, "vendor_id": 14000, "product_id": 12299 },
  "simple_modifications": [
    { "from": { "key_code": "grave_accent_and_tilde" },
      "to": [ { "key_code": "non_us_backslash" } ] }
  ]
}

This worked correctly for two weeks. It broke the moment "global": { "enable_cgeventtap_fallback": true } was set.

The device setting Swap ISO layout-specific keys added in 16.2.0 behaves the same way, because it also emits through the virtual keyboard.

Steps to reproduce

  1. Set virtual_hid_keyboard.keyboard_type_v2 to iso.
  2. Remap grave_accent_and_tilde to non_us_backslash on any keyboard (device-level or global).
  3. Press that key. It types `, as expected.
  4. Enable Enable CGEventTap fallback in the Expert tab.
  5. Press the key again. It now types §.

The built-in ISO keyboard shows the mirror image of the same fault: with the fallback on, its § key types `.

Log evidence

enable_cgeventtap_fallback was switched on at 14:57:12 on Sep 8. The first

[warning] [core_service (daemon)] keyboard suppression expired before matching event (1 entries)

arrived 9 seconds later. There are 366 of them in the logs since, and none at all before that moment — including 5.5 days of logs on 16.1.0 and 16.3.0 with the fallback off. Typing a backtick produces one or two of these warnings.

Analysis

The key codes for these two keys are swapped by macOS when the keyboard type is ISO, but pqrs::osx::cg_event::key_code maps them statically:

  • vendor/vendor/include/pqrs/osx/cg_event/key_code.hpp: keyboard_grave_accent_and_tilde(0x32), keyboard_non_us_backslash(0xa)

event_tap_utility::make_momentary_switch_event converts a tapped CGEvent through that table, so on an ISO virtual keyboard the usage that comes back is the other one of the pair. What appears to follow, for one press of the remapped key:

  1. The physical grave_accent_and_tilde is manipulated, non_us_backslash is posted to the virtual keyboard, and keyboard_suppression::enqueue registers non_us_backslash.
  2. macOS delivers that as key code 0x32 because the keyboard type is ISO. event_tap_monitor::should_skip_keyboard_event looks up grave_accent_and_tilde, finds no matching entry, and does not skip it. The callback returns nullptr and pushes the event into merged_input_event_queue_ as device_id(0).
  3. device_id(0) matches no device-level modification, so grave_accent_and_tilde is posted, and a second suppression entry is registered for it.
  4. macOS delivers that as key code 0x0a, which converts to non_us_backslash and consumes the entry from step 1, so it passes through to the application — as §.
  5. The entry from step 3 is never matched and expires, which is the warning above.

The net effect is that the remap is applied an odd number of times and the wrong character reaches the application, plus one expired entry per press.

Suggested fix

Take the keyboard type into account when converting a tapped CGEvent key code back to a HID usage, so grave_accent_and_tilde and non_us_backslash are exchanged when the virtual keyboard type is ISO. Matching the suppression entries on something other than the key code, if the CGEvent carries enough information, would avoid the round-trip conversion entirely.

Workaround

Disable Enable CGEventTap fallback. Users who need the fallback for a keyboard that cannot be opened via HID, and who have a mixed ANSI/ISO setup, currently have to choose between the two.

Source: pqrs-org/Karabiner-Elements