Skip to content

Stuck managed repeat after output-backend recovery #22

Description

@malpern

Summary

After a transient DriverKit output-backend failure (asio.system:55), managed repeat continues firing indefinitely for keys that were physically held at the moment of disconnection. The key repeats at ~30ms intervals and cannot be stopped by pressing other keys — requires killing kanata.

Root Cause

The recovery path in kanata/macos.rs:288-293 clears PRESSED_KEYS (input thread tracking) but does not clear the keyberon layout state. The processing thread's tick_managed_repeat() checks self.cur_keys (populated from self.layout.keycodes()), which still contains the stuck key because no Release event was ever delivered — the physical key-up happened while input was ungrabbed and went directly to macOS.

After re-grab, no release event arrives (key is no longer physically held), so the keyberon layout permanently retains the key as "pressed" and managed repeat loops forever.

Observed Behavior (2026-05-31)

14:29:05.392  — e key pressed during normal typing
14:29:05.424  — error_occurred asio.system:55
14:29:05.472  — "output backend unavailable during write — releasing input devices"
14:29:05.891  — managed repeat E starts firing (~30ms interval)
14:29:06.587  — output backend recovers, input re-grabbed
14:29:06.611+ — managed repeat E continues for 21+ seconds (928 total repeats)
              — user pressed Esc multiple times, no effect
              — had to emergency-stop kanata

Proposed Fix

After recovery, before re-grabbing input, also clear keyberon layout state and managed repeat timers — matching what the latency-soft-reset path already does (mod.rs:2612-2616):

// In macos.rs recovery path, after line 293:
{
    let mut kanata = kanata.lock();
    kanata.kbd_out.release_tracked_output_keys("output-backend-recovery");
    // ADD: clear keyberon layout state so no keys appear "stuck"
    release_normalkey_states(kanata.layout.bm());
    // ADD: clear managed repeat timers
    if let Some(ref mut mrs) = kanata.managed_repeat_state {
        mrs.timers.clear();
    }
}
PRESSED_KEYS.lock().clear();

Relevant Code

  • src/kanata/macos.rs:247-293 — recovery path (missing layout clear)
  • src/kanata/managed_repeat.rs:59-143tick_managed_repeat (checks cur_keys from layout)
  • src/kanata/mod.rs:2612-2616 — latency-soft-reset (has the correct pattern)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions