Skip to content

Debounce rapid QMK settings writes#90

Merged
kissetfall merged 6 commits into
ergohaven:mainfrom
IgorArkhipov:igor/qmk-settings-write-debounce-fix
Jul 20, 2026
Merged

Debounce rapid QMK settings writes#90
kissetfall merged 6 commits into
ergohaven:mainfrom
IgorArkhipov:igor/qmk-settings-write-debounce-fix

Conversation

@IgorArkhipov

@IgorArkhipov IgorArkhipov commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

EN

Problem

Touchpad sensitivity sliders sent a QMK HID command for every UI change while dragging. Slow USB or Bluetooth transports could receive a burst of stale writes for the same QSID, increasing latency and making the final firmware value less predictable.

Fix

  • Add a per-QSID settings scheduler that replaces pending writes with the newest requested value.
  • Debounce touchpad sensitivity sliders for 180 ms, while keeping module fields, dropdowns, and toggles immediate through the same queue.
  • Let immediate writes for another QSID bypass a delayed slider write.
  • Cancel a pending write when the control returns to its original value.
  • Flush pending settings before device switches and app close, clear them on disconnect, and pause automatic device scans while a write is pending.
  • Preserve existing verified module-setting readback behavior.

This PR complements #89; if #89 merges first, this scheduler will be rebased onto its background HID worker so debounce remains separate from transport threading.

Verification

  • cargo test --locked: 194 passed.
  • cargo test qmk_settings_write_queue --locked: 3 passed.
  • cargo clippy --locked --all-targets: passed with existing repository warnings.
  • python3 scripts/check_i18n.py: passed.
  • Scoped rustfmt --check and git diff --check: passed.
  • TARGET=aarch64-apple-darwin scripts/build_macos_app.sh: arm64 app, DMG, and ZIP built; plist and code-signature validation passed.
  • Built executable verified as Mach-O 64-bit arm64.

RU

Проблема

Слайдеры чувствительности тачпада отправляли QMK HID-команду при каждом изменении во время перетаскивания. Медленный USB- или Bluetooth-транспорт мог получить очередь устаревших записей одного QSID, что увеличивало задержку и делало итоговое значение в прошивке менее предсказуемым.

Исправление

  • Добавлен планировщик настроек по QSID, который заменяет ожидающую запись последним запрошенным значением.
  • Для слайдеров чувствительности тачпада добавлен debounce 180 мс; поля модулей, выпадающие списки и переключатели остаются немедленными, но проходят через ту же очередь.
  • Немедленная запись другого QSID не блокируется отложенной записью слайдера.
  • Если значение возвращается к исходному, ожидающая запись отменяется.
  • Ожидающие настройки сохраняются перед переключением устройства и закрытием приложения, очищаются при отключении; автоматическое сканирование устройства приостанавливается до завершения записи.
  • Существующая проверка настроек модулей контрольным чтением сохранена.

PR намеренно дополняет #89; если #89 будет смержен первым, планировщик будет изменен на его фоновый HID-worker, чтобы debounce оставался отдельным от transport threading.

Проверка

  • cargo test --locked: прошли 194 теста.
  • cargo test qmk_settings_write_queue --locked: прошли 3 теста.
  • cargo clippy --locked --all-targets: прошел с существующими предупреждениями репозитория.
  • python3 scripts/check_i18n.py: прошел.
  • Scoped rustfmt --check и git diff --check: прошли.
  • TARGET=aarch64-apple-darwin scripts/build_macos_app.sh: собраны arm64-приложение, DMG и ZIP; plist и подпись прошли проверку.
  • Исполняемый файл проверен как Mach-O 64-bit arm64.

@IgorArkhipov
IgorArkhipov force-pushed the igor/qmk-settings-write-debounce-fix branch from d9c98cc to c961c7c Compare July 17, 2026 21:05

@kissetfall kissetfall left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The debounce scheduler itself looks sound, but the surrounding write lifecycle is not safe to merge yet.

Required changes:

  1. Rebase/integrate this scheduler after #89 is corrected and route debounced writes through the single background HID worker. This PR currently keeps synchronous HID set/readback operations in the UI lifecycle and substantially overlaps with #89.
  2. Do not silently drop a pending value when HID is temporarily unavailable. A layer write can take the HID handle before the 180 ms deadline; the pending request is then removed, the write is skipped, and the UI can diverge from firmware. Pending work must be preserved/retried or explicitly cancelled with the UI restored to the confirmed firmware value. The same rule must cover disconnect.
  3. Add lifecycle/regression coverage for a delayed setting colliding with a layer write, disconnect/reconnect, device switch, and application shutdown. The current unit tests validate scheduler bookkeeping but not HID ownership or handoff.

Please also verify the integrated path over both USB and Bluetooth.

@IgorArkhipov
IgorArkhipov force-pushed the igor/qmk-settings-write-debounce-fix branch from c961c7c to 11ca324 Compare July 19, 2026 02:14
@IgorArkhipov

Copy link
Copy Markdown
Contributor Author

Addressed in commit 11ca324 after rebasing.

  1. Integrated corrected Make HID settings writes nonblocking #89 worker lifecycle: debounce now only coalesces slider changes; every HID set/readback runs through the serialized background settings worker.
  2. Pending values hand off to that worker while a layer write owns HID. On unavailable transport or teardown, pending optimistic touchpad state is restored to its confirmed value.
  3. Added lifecycle coverage for worker ownership handoff, device switch deferral, disconnect reset, stale worker results, stale readback versus a newer debounced value, and close-time worker draining.

@kissetfall kissetfall left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — the 11ca324 update addresses the previous debounce-specific blockers on its old stack: delayed slider values are handed to the background Settings worker, unavailable transport restores the confirmed value, device switching is deferred, and stale worker/readback generations are guarded.

This cannot merge on current main. The branch still contains the blocked #89 stack, GitHub reports CONFLICTING, and the real merge conflicts in eight lifecycle files, including app_lifecycle, device_connect_task, device_connection, layer_operations, and window_lifecycle.

Blocking integration work:

  1. Rebase after #89 is corrected on current main (#91 + #82). The debounce scheduler must feed the single Layer/Combo/Settings HID owner. If Combo temporarily owns HID when a 180 ms value becomes due, keep the newest value pending; do not classify the missing handle as a disconnected device or restore the old UI value.
  2. Remove the parallel Settings-only close state/path. pending_close_after_settings_write and the rewritten handle_close_to_tray() conflict with #82's production exit chain and would bypass its Combo, Tap Hold, and display-fallback exactly-once guarantees. Pending debounce requests and active Settings writes must participate in the existing shared exit deferral, which drains every HID owner before the final close.
  3. Apply the same shared busy contract to device switch, Refresh Device Data, imports, undo, disconnect, and reconnect. A pending debounce value, a queued Settings request, and an active worker must all transition deterministically through handoff or terminal cancellation.
  4. Replace the synthetic start_settings_write_for_test() cases with the real test HID backend. The helper drops its sender immediately and the tests do not prove successful handle return or firmware requests. Add lifecycle coverage for Debounce → Combo/Layer collision, device switch, disconnect/reconnect, and normal close, asserting that only the newest value is written and read back exactly once.

Please keep the scheduler/coalescing logic, but integrate it only after #89 has one current-main HID lifecycle owner.

@IgorArkhipov
IgorArkhipov force-pushed the igor/qmk-settings-write-debounce-fix branch from 11ca324 to 1add7df Compare July 19, 2026 17:22
@IgorArkhipov

Copy link
Copy Markdown
Contributor Author

Updated in 1add7df:

  • Rebased debounce changes onto corrected PR 89 lifecycle, itself rebased on current main with Fix combo save lifecycle #82 and Stabilize module settings grouping #91. Removed obsolete parallel ownership/close code.
  • Debounced values now enter shared Settings queue and remain pending while Combo owns HID; newest value is retained rather than treated as disconnect.
  • Removed pending_close_after_settings_write; close flushes debounce work into existing shared deferred-exit chain.
  • Added lifecycle-busy gating for pending debounce, queued Settings, and active workers across scan/import, Refresh Device Data, switch controls, and undo.
  • Removed start_settings_write_for_test(). New real test-HID coverage verifies Combo handoff and newest-value write/readback exactly once, deferred device switch, and disconnect/reconnect cancellation/retry. Existing ViewportEvent::Close regression exercises shared close draining.

@kissetfall kissetfall left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Head 1add7df is rebased and the four release-build checks are green, but it is not mergeable yet:

  1. The local test target does not compile. cargo test --all-targets fails in src/ui/qmk_settings_write_queue.rs:309-310: the sibling test module calls private SettingsWriteQueueState::status() and refers to SettingsWriteStatus without importing/exposing it (E0624, E0433). CI currently runs release builds only, so it misses this. Please make the regression compile and add tests to CI.

  2. The debounce fallback is not guaranteed to be a confirmed firmware value. If 8 -> 10 is already in the Settings worker and the user moves 10 -> 12, the pending debounce records optimistic 10 as old_value. If the active worker then disconnects, finish_settings_write() suppresses reconciliation because a newer debounce exists; the no-transport path later restores 10 even though firmware may still contain 8. Track the last confirmed/read-back value across the active worker and add a mid-worker disconnect regression with a newer debounced value.

  3. This stack still inherits #89's missing Combo -> Settings completion handoff and non-terminal deferred-exit failure path. poll_combo_write() returns the handle but only resumes a pending device connect; it does not start the Settings request queued behind Combo. A Settings disconnect during close can also leave other dirty HID writes holding the cancelled close indefinitely. Please fix these in #89 first, then keep #90's scheduler on that corrected owner.

cargo clippy, i18n, and diff checks complete successfully. The hard local failure is the test compile; the lifecycle cases above remain functional blockers.

@IgorArkhipov
IgorArkhipov force-pushed the igor/qmk-settings-write-debounce-fix branch from 1add7df to 0569d51 Compare July 19, 2026 23:26
@IgorArkhipov

Copy link
Copy Markdown
Contributor Author

Addressed:

  • The QMK queue tests now use the app-level test status accessor and explicitly import SettingsWriteStatus. Linux CI now runs cargo test --all-targets.
  • Debounce rollback now tracks the last verified readback rather than an in-flight optimistic value. Successful readbacks and mismatch actual values update a pending debounce’s rollback baseline; a pending request equal to the confirmed value is removed. Added regressions for active-worker disconnect, successful readback, and mismatch rollback via no-transport flush.
  • Combo/layer completion now continues queued Settings writes before resuming a pending connect. Deferred exit force-flushes new debounce work and stays cancelled while the full HID lifecycle remains busy. Added coverage for a debounce queued after close was requested.

@kissetfall kissetfall left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed head 0569d51. The previous blockers are addressed: the test target compiles and Linux CI runs cargo test --all-targets; rollback now tracks the last verified readback across an active worker; Combo/Layer completion continues queued Settings work; and close flushes debounce requests through the shared HID lifecycle.

The new regressions cover a newer debounced value during worker disconnect, successful and mismatched readback baselines, Combo handoff with only the newest value, reconnect, and debounce arriving after close was requested. Local verification passed 265/265 tests, Clippy, scoped rustfmt, i18n, and diff-check. All four hosted builds are green.

Approved.

@kissetfall
kissetfall merged commit 2e3a917 into ergohaven:main Jul 20, 2026
4 checks passed
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.

2 participants