feat(k04): add auto layer deactivate on key, release v0.1.10 - #19
feat(k04): add auto layer deactivate on key, release v0.1.10#19iakunin wants to merge 2 commits into
Conversation
Adds a Vial QMK setting (qsid 335) that drops the pointing auto layer as soon as a non-mouse key is pressed, instead of waiting out the inactivity timeout. K:04 does not use `[[behavior.auto_mouse_layer]]`; its auto layer lives in QubePointingModeProcessor, so the option is implemented there. Classification mirrors auto_mouse_layer::keypress_step minus extra_mouse_keys, which a QMK setting cannot express: mouse keys, repeated keycodes, layer switches, macros and the Action::User pointer-mode keys leave the layer intact; ordinary keys, key-with-modifier and modifier-only actions drop it. The flag lives in the last free bit of the existing auto-flags byte, so the module settings storage layout and MODULE_SETTINGS_VERSION are unchanged and no migration is needed. Existing keyboards read the bit back as 0 and keep today's behaviour, including held keys suppressing the timeout. Vial only advertises a setting when get_setting returns Some, which needs a module_qsid_width arm as well as a module_qsid_value arm. A key present in SETTING_KEYS but missing from the width table is silently hidden from configurators, so add a const assertion that catches the mismatch at compile time. Both version lines move to 0.1.10 (BCD 0x0110): the standalone K:04 Series from v0.1.9 and the K:04 Qube profiles from v0.1.8, so every changed Vial definition ships under a new version string. Claude-Session: https://claude.ai/code/session_017y8Qh1NR26NxtP2wR1nPLu
kissetfall
left a comment
There was a problem hiding this comment.
Thanks for the implementation and K:04 Mini testing. I found one blocking P2 issue in the new keypress deactivation path: it can cancel a manually activated layer after pointer movement (details inline).
I reproduced this with a host state-transition test using the real KeyMap and QubePointingModeProcessor methods: manual_layer_must_survive_typing fails on this head and passes on base 9a54f4ca. This is not a hardware reproduction. The existing 122 focused pointing/auto-mouse tests and 207 keyboard interaction tests passed; separate settings/storage/split round-trip checks also passed.
Please fix the ownership check and add regression coverage for a manually active target layer, alongside successful deactivation of an auto-owned layer and the flag-disabled behavior. No additional classifier change is requested.
| && self.settings.deactivate_auto_layer_on_key() | ||
| && qube_action_deactivates_auto_layer(event.action) | ||
| { | ||
| self.deactivate_auto_layer(); |
There was a problem hiding this comment.
P2: do not deactivate a manually activated target layer.
active_auto_layer != QUBE_AUTO_LAYER_NONE does not currently prove ownership: sync_auto_layer_for_motion assigns active_auto_layer = layer even when activate_layer_if_inactive(layer) returns false because the layer was already active. This new callback then clears that layer unconditionally.
Reproduction with the new flag enabled:
- Configure the auto-layer target as layer 2 and enable it in Normal mode.
- Manually activate layer 2 with
MO(2)orTG(2). - Move the pointer while layer 2 is already active.
- Before the inactivity timeout, press a key resolving to an ordinary letter.
Layer 2 is cleared, so subsequent keys fall through to the lower layer even while MO(2) is still held, or despite the manual toggle.
The ownership bookkeeping problem already existed in the timeout path; the new regression is immediate cancellation on a keypress, including while held keys would suppress that old timeout. The same manual-layer state-transition assertion passes on the base and fails on this head.
Please make keypress deactivation respect actual auto-layer ownership (including an already-active manual target), without changing the documented flag-off behavior. The generic auto-mouse runner's handling of an unsuccessful activation is a useful reference.
There was a problem hiding this comment.
@kissetfall many thanks for your code review: I made the changes you asked for (including regression test for it).
Could you have a look one more time, please?
`QubePointingModeProcessor` used `active_auto_layer != QUBE_AUTO_LAYER_NONE` as a stand-in for auto-layer ownership, but `sync_auto_layer_for_motion` assigns that field even when `activate_layer_if_inactive` fails because the target layer is already held by `MO`/`TG`. With `Auto layer deactivate on key` enabled, the first ordinary keypress after pointer motion then cleared a manually held layer immediately, without even the held-key suppression that shields the timeout path. Track ownership explicitly in `auto_layer_self_activated`, set from the `activate_layer_if_inactive` return value, and gate keypress deactivation on it — mirroring `self_activated` in `AutoMouseLayerRunner`. The timeout path is untouched, so behaviour with the flag off is unchanged. Regression coverage drives the real `KeyMap` and processor: a manually active target survives typing, an auto-owned layer is still dropped, and the flag-off path still waits for the timeout. The first test fails without the ownership check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRtXFSSbZW1v1dDYS24RDQ
What
Adds a Vial QMK setting,
Auto layer deactivate on key(qsid 335), that drops the pointing auto layer as soon as a non-mouse key is pressed, instead of waiting out the inactivity timeout.K:04 does not use
[[behavior.auto_mouse_layer]]— nokeyboards/k04/*.tomldeclares one. Its auto layer is implemented inQubePointingModeProcessor(rmk/src/input_device/pointing.rs) with its own timer, soAutoMouseLayerRunner'sdeactivate_on_keynever runs on this board and the option is implemented there instead.Behaviour
Disabled by default. With the option off, the auto layer behaves exactly as in
v0.1.9, including held keys suppressing the timeout.With it on, classification mirrors
auto_mouse_layer::keypress_stepminusextra_mouse_keys(a keycode array no QMK setting can express):MouseUp…MouseAccel2),OneShotKeyof oneAgain/ repeated keycode (keycode unknown here)Action::Userpointer-mode keysKeyWithModifier, modifier-only actionsStorage
The flag occupies the last free bit (7) of the existing
IDX_AUTO_FLAGSbyte — bits 0–3 are the four modes, 4–5 touch gestures, 6 charge indicator. That byte already ships whole to the peripheral asdata[12], soMODULE_SETTINGS_VERSIONstays at 9, the layout is unchanged, and no migration is needed. Existing keyboards read the bit back as0.Compile-time guard
VialCommand::BehaviorSettingQueryonly advertises a key whenget_settingreturnsSome, which needs amodule_qsid_widtharm as well as amodule_qsid_valuearm. A key added toSETTING_KEYSbut missing from the width table is silently hidden from configurators — I hit exactly this during development, and it costs a flash cycle to notice.module_qsid_widthis now aconst fnwith an assertion that everySETTING_KEYSentry resolves to a width. Verified by re-introducing the bug: the build fails withevaluation panicked: every SETTING_KEYS entry needs a module_qsid_width arm.Versions
Both lines move to
0.1.10(BCD0x0110) so every changed Vial definition ships under a new version string — otherwise configurators reuse a cached definition:0.1.9→0.1.100.1.8→0.1.10(skips0.1.9, which was standalone-only)build.rsasserts each vial JSON matches its line's constant, so these cannot drift.Note this is the first patch to reach two digits:
bcdDevicegoes0x0109→0x0110, still monotonically increasing but skipping0x010A–0x010F. The scheme runs out at0.1.99.Verification
thumbv7em-none-eabihf— three standalone (--features production_v22) plus six Qube dongle/halves:OVERALL: ALL PASSEDcargo checkclean onsplit,vial,async_matrix,_ble,vial,storage, and no-default-featurescargo clippy --tests— zero findings inpointing.rscargo +nightly fmt --checkcleansh scripts/test_all.sh— full matrix green: 15 nextest configurations, 7,935 tests run, 0 failed, 0 skipped, plus doctests. Both new tests pass in every applicable config (the classifier test is#[cfg(feature = "split")], so it runs in the 6 split configs).🤖 Generated with Claude Code
https://claude.ai/code/session_017y8Qh1NR26NxtP2wR1nPLu