Skip to content

feat(k04): add auto layer deactivate on key, release v0.1.10 - #19

Open
iakunin wants to merge 2 commits into
ergohaven:mainfrom
iakunin:feat/k04-auto-layer-deactivate-on-key
Open

feat(k04): add auto layer deactivate on key, release v0.1.10#19
iakunin wants to merge 2 commits into
ergohaven:mainfrom
iakunin:feat/k04-auto-layer-deactivate-on-key

Conversation

@iakunin

@iakunin iakunin commented Sep 8, 2026

Copy link
Copy Markdown

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]] — no keyboards/k04/*.toml declares one. Its auto layer is implemented in QubePointingModeProcessor (rmk/src/input_device/pointing.rs) with its own timer, so AutoMouseLayerRunner's deactivate_on_key never 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_step minus extra_mouse_keys (a keycode array no QMK setting can express):

Action Drops the layer?
Mouse keys (MouseUpMouseAccel2), OneShotKey of one no
Again / repeated keycode (keycode unknown here) no
Layer switches, macros, Action::User pointer-mode keys no
Ordinary keys, KeyWithModifier, modifier-only actions yes

Storage

The flag occupies the last free bit (7) of the existing IDX_AUTO_FLAGS byte — bits 0–3 are the four modes, 4–5 touch gestures, 6 charge indicator. That byte already ships whole to the peripheral as data[12], so MODULE_SETTINGS_VERSION stays at 9, the layout is unchanged, and no migration is needed. Existing keyboards read the bit back as 0.

Compile-time guard

VialCommand::BehaviorSettingQuery only advertises a key when get_setting returns Some, which needs a module_qsid_width arm as well as a module_qsid_value arm. A key added to SETTING_KEYS but 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_width is now a const fn with an assertion that every SETTING_KEYS entry resolves to a width. Verified by re-introducing the bug: the build fails with evaluation panicked: every SETTING_KEYS entry needs a module_qsid_width arm.

Versions

Both lines move to 0.1.10 (BCD 0x0110) so every changed Vial definition ships under a new version string — otherwise configurators reuse a cached definition:

  • standalone K:04 Series: 0.1.90.1.10
  • K:04 Qube profiles: 0.1.80.1.10 (skips 0.1.9, which was standalone-only)

build.rs asserts each vial JSON matches its line's constant, so these cannot drift.

Note this is the first patch to reach two digits: bcdDevice goes 0x01090x0110, still monotonically increasing but skipping 0x010A0x010F. The scheme runs out at 0.1.99.

Verification

  • All nine K:04 images build for thumbv7em-none-eabihf — three standalone (--features production_v22) plus six Qube dongle/halves: OVERALL: ALL PASSED
  • cargo check clean on split,vial,async_matrix,_ble, vial,storage, and no-default-features
  • cargo clippy --tests — zero findings in pointing.rs
  • cargo +nightly fmt --check clean
  • Flashed to a K:04 Mini and confirmed the setting appears and persists

sh scripts/test_all.shfull 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

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 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 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();

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.

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:

  1. Configure the auto-layer target as layer 2 and enable it in Normal mode.
  2. Manually activate layer 2 with MO(2) or TG(2).
  3. Move the pointer while layer 2 is already active.
  4. 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.

@iakunin iakunin Sep 10, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@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
@iakunin
iakunin requested a review from kissetfall September 10, 2026 15:14
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