Skip to content

Fix: modifier keys + first character dropped on wireless after idle sleep#6

Open
jkquickstar wants to merge 2 commits intoEpomaker:mainfrom
jkquickstar:fix/wireless-modifier-drop-on-reconnect
Open

Fix: modifier keys + first character dropped on wireless after idle sleep#6
jkquickstar wants to merge 2 commits intoEpomaker:mainfrom
jkquickstar:fix/wireless-modifier-drop-on-reconnect

Conversation

@jkquickstar
Copy link
Copy Markdown

@jkquickstar jkquickstar commented Mar 3, 2026

Summary

Fixes modifier key drops (e.g. Cmd+Shift+4 registering as $ instead of triggering macOS screenshot) and first-character drops on 2.4GHz wireless after the keyboard wakes from 1-minute idle sleep.

Root cause: The default lpwr_wakeup_cb() in lowpower.c calls suspend_wakeup_init()clear_mods() approximately 200ms after the MCU wakes. By that point, the main loop has already scanned the wake-up keys and registered their modifiers. clear_mods() destroys that state, and because held keys aren't re-processed by the matrix debouncer (it sees them as already pressed), the modifiers are never re-registered.

Changes

Keyboard-specific (tide75/control/):

  • Override lpwr_wakeup_cb() (weak function) to skip suspend_wakeup_init() and call only suspend_wakeup_init_quantum() — preserves modifier state while restoring LED/RGB matrix suspend state
  • Override lpwr_presleep_cb() to track RGB enabled state independently
  • Add wireless_devs_change() in lpwr_stop_hook_post() to prime the radio module on wake (matching Lofree/P75 keyboard behavior)
  • Set debounce to 8ms and force_nkro to false

Wireless driver (tide75/linker/wireless/):

  • Post-sleep resync: 80×15ms = 1200ms of aggressive report re-delivery after wake, ensuring the first keypress is delivered once the radio warms up (~60-80ms)
  • 1000ms keep-alive to prevent radio link power-saving
  • Skip NKRO overflow (0xA2 message) during post-sleep resync to prevent modifier race between 6KRO and NKRO radio packets
  • Fix transport switching order (enable new transport before disabling old)
  • Fix USB suspend/resume state tracking

QMK core (quantum/action_util):

  • Add keyboard_report_dedup_invalidate() and nkro_report_dedup_invalidate() APIs for resync support — these mark the report dedup cache as stale, forcing the next send_keyboard_report() to transmit

Test plan

  • Wait for idle sleep (LEDs off), press Cmd+Shift+4 → macOS screenshot triggers
  • Wait for idle sleep, press Cmd+C → clipboard copy works
  • Wait for idle sleep, type "mar" → all characters appear (no first-char drop)
  • Normal typing — no regressions

How to apply

The quantum/action_util.c and quantum/action_util.h files are QMK core modifications (not keyboard-specific). To use this fix, copy the quantum/ files to your QMK tree's quantum/ directory alongside the keyboard-specific files.

🤖 Generated with Claude Code

@jkquickstar jkquickstar force-pushed the fix/wireless-modifier-drop-on-reconnect branch from 2a0974d to 9b07674 Compare March 5, 2026 15:40
@jkquickstar jkquickstar changed the title Fix modifier keys dropped on first press after wireless reconnect Fix modifier keys dropped on wireless after idle periods Mar 5, 2026
Root cause: Two independent bugs causing modifiers (Cmd, Ctrl, etc.) to be
lost when the first keystroke after an idle period is a modifier combo.

1. NKRO dual-message race (wireless.c)
   wireless_send_nkro() sends TWO UART messages per keystroke: a 6KRO report
   (0xA1) containing modifiers+keys, followed by an NKRO bitmap (0xA2)
   containing overflow keys. For normal typing (≤6 keys), the NKRO bitmap
   is all zeros. Both messages are forwarded by the dongle as separate USB
   HID reports. When they arrive within the same USB poll interval (~1ms),
   the host only sees the last state — the empty NKRO — causing modifiers
   to be dropped.

   Fix: Skip md_send_nkro() when the NKRO bitmap is all zeros.

2. Post-sleep radio cold start (wireless.c + companion changes)
   After the keyboard sleeps (1-minute idle timeout), the radio link to the
   dongle goes cold. The first keypress wakes the MCU, but the HID report
   is sent before the radio re-establishes — the report is lost.

   Fix: Three-layer defense:
   - Keep-alive: Re-send keyboard state every 1s to prevent radio/USB from
     entering power-saving modes during normal operation
   - Post-sleep resync: Aggressively re-send keyboard state (30×50ms=1500ms)
     after detecting wakeup from sleep, with dedup cache invalidation
   - Report-dropped recovery: When reports fail due to module disconnection,
     invalidate the dedup cache and force a resend on reconnection

Companion changes needed in keyboard-level code (tide75.c, control.c):
- lpwr_stop_hook_post(): Prime radio by re-issuing device-mode command
  (wireless_devs_change) immediately after MCU wakes, before main loop
  processes the wake-up keypress (~85ms warm-up window)
- lpwr_wakeup_hook(): Call matrix_init() to reset matrix/debounce state
- action_util.c/h: Add keyboard_report_dedup_invalidate() and
  nkro_report_dedup_invalidate() functions (move dedup cache from
  function-local statics to file-scoped statics)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jkquickstar jkquickstar force-pushed the fix/wireless-modifier-drop-on-reconnect branch from 9b07674 to c495c7e Compare March 5, 2026 18:17
@jkquickstar jkquickstar changed the title Fix modifier keys dropped on wireless after idle periods Fix: modifier keys dropped on wireless after idle/sleep Mar 5, 2026
Root cause: the default lpwr_wakeup_cb() calls suspend_wakeup_init()
which invokes clear_mods() ~200ms after MCU wake. By that time the
main loop has already scanned the wake-up keys and registered their
modifiers. clear_mods() destroys that state, and because held keys
are not re-processed by matrix debounce, modifiers are never
re-registered — causing e.g. Cmd+Shift+4 to register as '$'.

Changes:

Keyboard-specific (tide75/):
- Override lpwr_wakeup_cb() in control.c to skip suspend_wakeup_init()
  and call only suspend_wakeup_init_quantum() — preserves modifier
  state while still restoring LED/RGB matrix suspend state
- Override lpwr_presleep_cb() to track RGB state independently
  (rgb_enable_bak is static in lowpower.c, inaccessible externally)
- Add wireless_devs_change() in lpwr_stop_hook_post() to prime the
  radio module immediately on wake (like Lofree/P75 keyboards)
- Remove redundant wireless_devs_change() from suspend_wakeup_init_kb
- Set debounce to 8ms (from default) and force_nkro to false

Wireless driver (tide75/linker/wireless/):
- Add post-sleep resync mechanism (80x15ms = 1200ms) to re-deliver
  reports after radio warm-up
- Add keep-alive (1000ms) to prevent radio link power-saving
- Skip NKRO overflow (0xA2) during post-sleep resync to prevent
  modifier race between 6KRO and NKRO radio packets
- Fix transport switching order (enable new before disabling old)
- Fix USB suspend/resume tracking in transport.c

QMK core (quantum/):
- Add dedup cache invalidation API (keyboard_report_dedup_invalidate,
  nkro_report_dedup_invalidate) in action_util for resync support

Tested: Cmd+Shift+4, Cmd+C, first-character typing all work after
1-minute wireless idle sleep on 2.4GHz.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jkquickstar jkquickstar changed the title Fix: modifier keys dropped on wireless after idle/sleep Fix: modifier keys + first character dropped on wireless after idle sleep Mar 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant