feat: add managed-repeat for kanata-managed key repeat with per-key rates#2070
feat: add managed-repeat for kanata-managed key repeat with per-key rates#2070malpern wants to merge 8 commits into
Conversation
Add macOS equivalent of linux-continue-if-no-devs-found. When enabled, kanata keeps running if no matching devices are found at startup and automatically captures them when they connect. Two capture paths depending on how devices are specified: - Hash-based IDs: registered unconditionally via register_device_hash, grab() starts the listener thread, and device_connected_callback captures the device on connection. - Name-based IDs: polls every 2s until the device appears, then registers and grabs. Relates to jtroo#1982 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Make Drop unconditional to clean up listener thread in deferred state - Handle recovery loop gracefully when no devices are grabbed - Add periodic log message to poll_for_devices (every 30s) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a new repeat engine that lets kanata generate key repeats instead of relying on the OS. This solves the macOS duplicate key bug where tap-hold processing delays cause unintended OS autorepeat through the HID report model. - defcfg options: managed-repeat, managed-repeat-delay, managed-repeat-interval - defrepeat block for per-key delay/interval overrides - Automatically suppresses OS hardware repeat when enabled - Modifiers are exempt from repeat - Validated on real macOS hardware through Karabiner DriverKit virtual HID - 9 simulation tests covering basic repeat, per-key overrides, modifiers, layer-while-held, and disabled-by-default behavior Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous approach re-posted the same HID report, which left macOS free to run its own repeat timer alongside kanata's managed repeat. Now the repeat timer has three phases: 1. HeldBeforeRelease (5ms) — key in report just long enough for initial char 2. ReleasedWaiting — key removed from report, OS repeat can never fire 3. Repeating — release+re-press cycles produce fresh keydown events Validated on macOS hardware under CPU load (KeyPath compile loop). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
09541e8 to
f8bbc6f
Compare
|
The macOS CI failure is a runner infrastructure issue — |
The reload path was missing the managed_repeat_state and allow_hardware_repeat updates, so TCP Reload didn't pick up changes to defrepeat or managed-repeat-delay/interval. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
I'm using the ability to customize the repeat speed to offer "Fast Navigation" in KeyPath, where alpha's remain with a normal repeat delay and repeat rate but arrow keys and delete can be much faster. It makes the computer feel much snappier without compromising alpha key typing. |
|
Recently switched from Karabiner Elements and running into the issue of repeating key-strokes basically daily - not sure how it's solved there - the fix maybe in an updated VHIDD driver (or in Karabiner itself)? Would be great to get this fix. |
|
@oschrenk have you had an opportunity to test if this PR solves your repeat issues? |
No. Need to find a way to compile/build this and then find a way to re-produce the issue (create some load on the system) |
|
Was trying to build and use master before trying this. But now my external bluetooth keyboard is not working (was working with 1.11). I'm unaware of any device include/exclude procedure I think at the very least for today I revert to 1.11 Edit: yes w/ 1.11 my bluetooth keyboard works. |
|
This looks like a separate issue from managed-repeat — your Bluetooth keyboard problem is likely related to device grabbing on master, not this PR. We recently landed once we resolve your bluetooth issues in (#2065), or if you can test with a non-bluetooth keyboard, let us know if you get a clean read on if this PRs managed repeat feature solves your duplicate key presses. To your question on how to generate load on your mac to see if the repeat issue is fixed, I usually compile a large program from source like KeyPath. |
|
Anyhting I need to configure? Current config |
|
Do I need to set ? The description says something about 5ms but the pasted config snippet doesn't make that clear if how that is set |
|
Misread the description. Added this config (and also optional faster key repeat for backspace - I do enjoy to b able to set different key repeats per key). But I'm sad to say that key-repeats still happen though. Any other configuration/advice? |
|
Should also mention that I am not holding down any key. I'm just normally typing. |


Summary
defrepeatblock for per-key repeat delay/interval overridesProblem
Users on macOS experience unintended repeated characters when typing under CPU load — holding a key produces
stilllllllloraaaaaaaaaandinstead of a single character followed by controlled repeat. This is especially problematic with tap-hold (homerow mods), where the processing delay keeps the previous key held just long enough to trigger OS autorepeat (#1441, #422).USB HID reports model current switch state, not repeat intent. Linux exposes repeat as a higher-level subsystem feature, so
allow-hardware-repeat nofully suppresses it. macOS derives repeat heuristically from sustained key presence in the HID report, and there is no API to disable this. Thef24interrupt workaround from #422 doesn't help — f24 enters the report but the original key stays held.Solution
When enabled, kanata releases each non-modifier key from the HID report after 5ms (well below OS repeat thresholds of 300-500ms), then manages all repeat via release+re-press cycles on its own timer. Modifiers are exempt. Repeat cancels immediately on physical key release.
This is opt-in and doesn't change default behavior. Kanata has historically treated repeat as the OS's responsibility (#422, #450), and on Linux that works. On macOS there is no alternative — the only way to prevent OS repeat is to remove the key from the report before the threshold fires.
Repeat is implemented as HID press/release cycles (not higher-level character injection) to preserve app compatibility, navigation semantics, and uniform behavior across terminals, editors, and games.
How it works
Three-phase timer per held key:
Runs in the existing 1ms tick loop via
tick_states().is_idle()returns false when timers are active so the processing loop keeps ticking.Cross-platform
The primary motivation is macOS, but per-key repeat rates (faster arrows/delete, slower alphas) are useful on all platforms and not available from any OS natively. The implementation uses existing
press_key/release_keyprimitives and works cross-platform. The early-release phase is primarily needed on macOS; on Linuxallow-hardware-repeat noalready fully suppresses OS repeat.This is opt-in on all platforms. If community feedback confirms stability on macOS, a future change could default to
yeson macOS where OS repeat cannot be suppressed any other way.Compatibility
Validated with standard Latin text input on macOS. IME workflows, dead key sequences, and accessibility software may warrant additional testing since repeat events become synthetic press cycles rather than sustained holds.
Validation
Test plan
Related: #1441, #422, #1794, #2042