Skip to content

feat: add StickyMod (SM) action for Alt+Tab-style modifier cycling - #859

Open
ldsands wants to merge 151 commits into
rmk-rs:mainfrom
ldsands:feat/sticky-mod
Open

ldsands wants to merge 151 commits into
rmk-rs:mainfrom
ldsands:feat/sticky-mod

Conversation

@ldsands

@ldsands ldsands commented May 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new SM(key, modifier) action — StickyMod — that holds a modifier
across repeated presses of the same key, then releases it automatically when
any non-SM, non-modifier key is pressed, the active layer changes, or an
optional timeout expires.

Primary use case: Alt+Tab window/tab cycling. Bind SM(Tab, LAlt) to a
key; the first press sends Alt+Tab, subsequent presses send Tab (Alt stays
held), and Alt releases as soon as you press any other key or switch layers.

Motivation

This is a direct port of the KC.SK() (Sticky Key) behavior from KMK firmware.
For those migrating from KMK, SM(Tab, LAlt) replicates KC.SK(KC.LALT) used
in conjunction with Tab for Alt+Tab cycling. This was the last regularly-used
KMK feature I needed in order to fully replicate my KMK keymap in RMK
(though others may find additional gaps).

This feature covers similar ground to #724 (Tabber), which I was aware of
before writing this implementation but found didn't quite fit my needs — I
preferred this approach because it generalizes to any key+modifier combination
rather than being Tab-specific, and includes timeout support. That said, I
have no attachment to the name SM or StickyMod — happy to rename this to
whatever fits best if this is merged.

How it differs from OSM

Behavior OSM(mod) SM(key, mod)
Modifier release After the next single keypress After any non-SM/non-modifier press, or layer change, or timeout
Key bundled No — modifier only Yes — modifier + key in one action
Repeatable cycling No Yes

Behavior details

  • First press: activates SM state, sends modifier + key
  • Release: unregisters key, modifier stays held
  • Subsequent presses: modifier already active, sends key again; timeout resets
  • Release triggers: any non-SM, non-modifier keypress; any layer deactivation; timeout expiry
  • Modifier exclusion: Modifier actions and HID modifier keycodes (Shift, Ctrl, etc.)
    do not release SM — this lets Shift+Tab work for reverse cycling

Optional timeout

Timeout is measured from the last SM press — repeated presses extend the
hold window. Implemented via the main run() loop deadline (same pattern as
mouse repeat), so it fires reliably regardless of how many press/release
cycles have occurred.

[behavior.sticky_mod]
timeout = "5s"   # auto-release modifier after 5s since last SM press

Default: no timeout (modifier held until released by keypress or layer change).

TOML syntax

SM(Tab, LAlt)           # Alt+Tab cycling
SM(Tab, LCtrl)          # Ctrl+Tab cycling
SM(Tab, LCtrl|LShift)   # Ctrl+Shift+Tab (reverse cycling)

Changes

  • rmk-types: new Action::StickyMod(KeyCode, ModifierCombination) variant
  • rmk: new keyboard/sticky_mod.rs module — StickyModState with optional deadline, state machine, and processing logic
  • rmk: integrated into keyboard.rs — dispatch, modifier resolution, release triggers on layer deactivation, deadline-based timeout in run() loop
  • rmk-config: TOML grammar (keymap.pest) and parser for SM(key, mod) syntax
  • rmk-macro: codegen support for SM in action_parser.rs and behavior.rs
  • rmk: sm!() macro in layout_macro.rs
  • docs: behavior.md (Sticky Modifiers section), layout.md (SM syntax entry)

Tests

7 integration tests in rmk/tests/keyboard_sticky_mod_test.rs:

  1. Basic flow: press SM twice while MO held
  2. Layer change cleanup: MO release triggers SM release
  3. Shift integration: Shift key does not release SM (enables reverse cycling)
  4. Rapid presses: 3× SM press/release cycle
  5. Combined modifiers: LCtrl|LShift combination
  6. Timeout: modifier auto-releases after inactivity
  7. Timeout reset: pressing SM again extends the timeout window

@github-actions

github-actions Bot commented May 21, 2026 •

Copy link
Copy Markdown

Size Report

Example main PR Diff .text .data .bss
use_config/nrf52832_ble 363.1 KiB 371.3 KiB +2.27% ⬆️ +7860 0 +584
use_config/nrf52840_ble 422.2 KiB 431.0 KiB +2.09% ⬆️ +7832 0 +1224
use_config/nrf52840_ble_split (central) 495.2 KiB 502.4 KiB +1.45% ⬆️ +6808 0 +584
use_config/nrf52840_ble_split (peripheral) 316.2 KiB 316.2 KiB +0.00% ⬆️ +32 0 0
use_config/pi_pico_w_ble 669.8 KiB 678.3 KiB +1.26% ⬆️ +8088 0 +584
use_config/rp2040 146.6 KiB 155.0 KiB +5.72% ⬆️ +8016 0 +584
use_config/rp2040_split (central) 161.4 KiB 169.4 KiB +4.95% ⬆️ +7608 0 +584
use_config/rp2040_split (peripheral) 27.8 KiB 27.8 KiB +0.00% 0 0 0
use_config/stm32f1 63.0 KiB 67.3 KiB +6.71% ⬆️ +3676 0 +656
use_config/stm32h7 101.0 KiB 109.9 KiB +8.80% ⬆️ +8448 0 +664
use_rust/nrf52832_ble 350.9 KiB 359.3 KiB +2.37% ⬆️ +7968 0 +584
use_rust/nrf52840_ble 416.7 KiB 425.0 KiB +2.01% ⬆️ +7988 0 +592
use_rust/nrf52840_ble_split (central) 493.9 KiB 501.7 KiB +1.57% ⬆️ +7360 0 +584
use_rust/nrf52840_ble_split (peripheral) 308.1 KiB 308.1 KiB +0.01% ⬆️ +44 0 0
use_rust/pi_pico_w_ble 669.1 KiB 677.5 KiB +1.24% ⬆️ +7948 0 +584
use_rust/rp2040 146.1 KiB 154.1 KiB +5.50% ⬆️ +7656 0 +584
use_rust/rp2040_split (central) 160.2 KiB 168.5 KiB +5.18% ⬆️ +7916 0 +584
use_rust/rp2040_split (peripheral) 28.1 KiB 28.1 KiB +0.00% 0 0 0
use_rust/stm32f1 62.3 KiB 69.5 KiB +11.44% ⬆️ +6640 0 +664
use_rust/stm32h7 119.5 KiB 127.0 KiB +6.26% ⬆️ +7080 0 +584
use_config/nrf52832_ble — 363.1 KiB → 371.3 KiB (+2.27% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 337996	   8592	  33640	 380228	  5cd44	rmk-nrf52832

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 330136	   8592	  33056	 371784	  5ac48	rmk-nrf52832

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +2.5% +91.1Ki  [ = ]       0    .debug_str
  +2.2% +40.0Ki  [ = ]       0    .debug_info
  +1.6% +11.0Ki  [ = ]       0    .debug_loc
  +2.4% +6.92Ki  +2.4% +6.92Ki    .text
  +3.5% +6.88Ki  [ = ]       0    .debug_ranges
  +2.1% +6.26Ki  [ = ]       0    .debug_line
  +2.0% +6.25Ki  [ = ]       0    .strtab
  +2.2% +2.38Ki  [ = ]       0    .symtab
  +3.0% +1.24Ki  [ = ]       0    .debug_frame
  +2.1%    +776  +2.1%    +776    .rodata
  [ = ]       0  +1.8%    +584    .bss
  +1.2%    +576  [ = ]       0    .debug_aranges
  +1.7%    +152  [ = ]       0    .debug_abbrev
  +3.1%     +26  [ = ]       0    .defmt
   +42%     +18  [ = ]       0    [Unmapped]
  +2.3%  +173Ki  +2.3% +8.25Ki    TOTAL
use_config/nrf52840_ble — 422.2 KiB → 431.0 KiB (+2.09% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 387308	   8628	  45408	 441344	  6bc00	rmk-nrf52840

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 379476	   8628	  44184	 432288	  698a0	rmk-nrf52840

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +2.3% +93.0Ki  [ = ]       0    .debug_str
  +1.9% +40.4Ki  [ = ]       0    .debug_info
  +1.7% +12.4Ki  [ = ]       0    .debug_loc
  +3.4% +7.60Ki  [ = ]       0    .debug_ranges
  +2.1% +6.89Ki  +2.1% +6.89Ki    .text
  +2.0% +6.61Ki  [ = ]       0    .debug_line
  +1.8% +6.27Ki  [ = ]       0    .strtab
  +1.7% +2.16Ki  [ = ]       0    .symtab
  [ = ]       0  +2.8% +1.20Ki    .bss
  +2.5% +1.16Ki  [ = ]       0    .debug_frame
  +1.9%    +776  +1.9%    +776    .rodata
  +1.1%    +528  [ = ]       0    .debug_aranges
  +0.4%     +41  [ = ]       0    .debug_abbrev
  +1.9%     +18  [ = ]       0    .defmt
   +19%      +9  [ = ]       0    [Unmapped]
  +2.1%  +177Ki  +2.1% +8.84Ki    TOTAL
use_config/nrf52840_ble_split (central) — 495.2 KiB → 502.4 KiB (+1.45% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 454816	   8668	  51000	 514484	  7d9b4	central

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 448008	   8668	  50416	 507092	  7bcd4	central

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +1.9% +91.5Ki  [ = ]       0    .debug_str
  +1.7% +40.4Ki  [ = ]       0    .debug_info
  +1.5% +12.9Ki  [ = ]       0    .debug_loc
  +1.7% +6.73Ki  [ = ]       0    .strtab
  +1.7% +6.29Ki  [ = ]       0    .debug_line
  +1.5% +5.90Ki  +1.5% +5.90Ki    .text
  +2.1% +5.20Ki  [ = ]       0    .debug_ranges
  +1.3% +1.88Ki  [ = ]       0    .symtab
  +2.0% +1.05Ki  [ = ]       0    .debug_frame
  +1.7%    +768  +1.7%    +768    .rodata
  [ = ]       0  +1.2%    +584    .bss
  +0.9%    +472  [ = ]       0    .debug_aranges
  +0.3%     +30  [ = ]       0    .debug_abbrev
  +1.9%     +18  [ = ]       0    .defmt
 -10.9%      -6  [ = ]       0    [Unmapped]
  +1.8%  +173Ki  +1.5% +7.22Ki    TOTAL
use_config/nrf52840_ble_split (peripheral) — 316.2 KiB → 316.2 KiB (+0.00% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 289804	   8508	  25512	 323824	  4f0f0	peripheral

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 289772	   8508	  25512	 323792	  4f0d0	peripheral

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.2% +6.16Ki  [ = ]       0    .debug_str
  +0.1% +1.63Ki  [ = ]       0    .debug_info
  +0.1%    +653  [ = ]       0    .debug_loc
  +0.4%    +192  [ = ]       0    .debug_aranges
  +0.1%     +88  [ = ]       0    .debug_ranges
  +0.0%     +45  [ = ]       0    .debug_line
  +0.0%     +32  +0.0%     +32    .text
  +0.2%     +20  [ = ]       0    .debug_abbrev
  +2.7%      +2  [ = ]       0    [Unmapped]
  -0.1%     -28  [ = ]       0    .debug_frame
  -0.2%    -448  [ = ]       0    .strtab
  +0.1% +8.33Ki  +0.0%     +32    TOTAL
use_config/pi_pico_w_ble — 669.8 KiB → 678.3 KiB (+1.26% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 639792	      0	  54784	 694576	  a9930	rmk-pi-pico-w

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 631704	      0	  54200	 685904	  a7750	rmk-pi-pico-w

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +2.1% +91.3Ki  [ = ]       0    .debug_str
  +1.5% +36.0Ki  [ = ]       0    .debug_info
  +1.1% +11.9Ki  [ = ]       0    .debug_loc
  +2.2% +7.14Ki  +2.2% +7.14Ki    .text
  +1.5% +5.87Ki  [ = ]       0    .debug_line
  +1.6% +5.52Ki  [ = ]       0    .strtab
  +2.2% +5.39Ki  [ = ]       0    .debug_ranges
  +2.3% +1.88Ki  [ = ]       0    .symtab
  +2.0%    +836  [ = ]       0    .debug_frame
  +0.3%    +776  +0.3%    +776    .rodata
  [ = ]       0  +1.1%    +584    .bss
  +0.8%    +384  [ = ]       0    .debug_aranges
  +0.2%     +20  [ = ]       0    .debug_abbrev
  +2.0%     +18  [ = ]       0    .defmt
   +19%     +10  [ = ]       0    [Unmapped]
  +1.7%  +166Ki  +1.3% +8.47Ki    TOTAL
use_config/rp2040 — 146.6 KiB → 155.0 KiB (+5.72% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 143908	      0	  14788	 158696	  26be8	rmk-rp2040

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 135892	      0	  14204	 150096	  24a50	rmk-rp2040

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +5.3% +90.6Ki  [ = ]       0    .debug_str
  +3.7% +36.1Ki  [ = ]       0    .debug_info
  +2.8% +8.80Ki  [ = ]       0    .debug_loc
  +6.1% +7.07Ki  +6.1% +7.07Ki    .text
  +4.8% +5.61Ki  [ = ]       0    .strtab
  +3.3% +5.41Ki  [ = ]       0    .debug_line
  +5.2% +4.24Ki  [ = ]       0    .debug_ranges
  +5.6% +1.78Ki  [ = ]       0    .symtab
  +5.0%    +832  [ = ]       0    .debug_frame
  +4.7%    +772  +4.7%    +772    .rodata
  [ = ]       0  +4.4%    +584    .bss
  +2.3%    +384  [ = ]       0    .debug_aranges
  +6.2%     +26  [ = ]       0    .defmt
 -32.3%     -20  [ = ]       0    [Unmapped]
  -0.4%     -28  [ = ]       0    .debug_abbrev
  +4.5%  +161Ki  +5.7% +8.40Ki    TOTAL
use_config/rp2040_split (central) — 161.4 KiB → 169.4 KiB (+4.95% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 157640	      0	  15860	 173500	  2a5bc	central

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 150032	      0	  15276	 165308	  285bc	central

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +4.2% +86.9Ki  [ = ]       0    .debug_str
  +3.1% +35.4Ki  [ = ]       0    .debug_info
  +2.5% +9.27Ki  [ = ]       0    .debug_loc
  +5.2% +6.68Ki  +5.2% +6.68Ki    .text
  +2.9% +5.37Ki  [ = ]       0    .debug_line
  +4.0% +5.30Ki  [ = ]       0    .strtab
  +4.8% +4.42Ki  [ = ]       0    .debug_ranges
  +5.5% +1.92Ki  [ = ]       0    .symtab
  +4.6%    +868  [ = ]       0    .debug_frame
  +4.1%    +772  +4.1%    +772    .rodata
  [ = ]       0  +4.1%    +584    .bss
  +2.2%    +392  [ = ]       0    .debug_aranges
  +4.0%     +18  [ = ]       0    .defmt
  -0.2%     -12  [ = ]       0    .debug_abbrev
 -34.9%     -22  [ = ]       0    [Unmapped]
  +3.7%  +157Ki  +5.0% +8.00Ki    TOTAL
use_config/rp2040_split (peripheral) — 27.8 KiB → 27.8 KiB (+0.00%)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
  25480	     60	   2908	  28448	   6f20	peripheral

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
  25480	     60	   2908	  28448	   6f20	peripheral

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.6% +4.72Ki  [ = ]       0    .debug_str
  +0.2%    +952  [ = ]       0    .debug_info
  +1.4%    +192  [ = ]       0    .debug_aranges
  +0.1%     +34  [ = ]       0    .debug_line
   +17%      +7  [ = ]       0    [Unmapped]
  +0.0%      +1  [ = ]       0    .strtab
  +0.4% +5.88Ki  [ = ]       0    TOTAL
use_config/stm32f1 — 63.0 KiB → 67.3 KiB (+6.71% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
  61528	     28	   7316	  68872	  10d08	rmk-stm32f1

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
  57852	     28	   6660	  64540	   fc1c	rmk-stm32f1

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +7.7% +71.8Ki  [ = ]       0    .debug_str
  +4.2% +23.9Ki  [ = ]       0    .debug_info
  +4.9% +6.21Ki  [ = ]       0    .debug_loc
   +11% +4.53Ki  [ = ]       0    .debug_ranges
  +6.5% +3.59Ki  +6.5% +3.59Ki    .text
  +3.7% +3.26Ki  [ = ]       0    .debug_line
  +4.8% +2.08Ki  [ = ]       0    .strtab
  [ = ]       0  +9.8%    +656    .bss
  +1.6%    +320  [ = ]       0    .symtab
  +2.5%    +312  [ = ]       0    .debug_frame
  +0.4%     +20  [ = ]       0    .debug_abbrev
  +0.3%     +16  [ = ]       0    .debug_aranges
  +5.3%      +3  [ = ]       0    [Unmapped]
  +6.1%  +116Ki  +6.7% +4.23Ki    TOTAL
use_config/stm32h7 — 101.0 KiB → 109.9 KiB (+8.80% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 103212	    264	   9080	 112556	  1b7ac	rmk-stm32h7

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
  94764	    264	   8416	 103444	  19414	rmk-stm32h7

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +4.3% +85.7Ki  [ = ]       0    .debug_str
  +3.8% +36.6Ki  [ = ]       0    .debug_info
  +6.0% +10.8Ki  [ = ]       0    .debug_loc
  +8.6% +6.82Ki  +8.6% +6.82Ki    .text
  +8.5% +5.77Ki  [ = ]       0    .debug_ranges
  +4.5% +5.47Ki  [ = ]       0    .debug_line
  +4.9% +3.06Ki  [ = ]       0    .strtab
  +6.9% +1.81Ki  [ = ]       0    .symtab
   +11% +1.43Ki   +11% +1.43Ki    .rodata
  +8.6% +1.20Ki  [ = ]       0    .debug_frame
  [ = ]       0  +7.9%    +664    .bss
  +1.1%    +368  [ = ]       0    .debug_aranges
  -1.7%      -1  [ = ]       0    [Unmapped]
  -0.2%     -11  [ = ]       0    .debug_abbrev
  +4.5%  +158Ki  +8.8% +8.90Ki    TOTAL
use_rust/nrf52832_ble — 350.9 KiB → 359.3 KiB (+2.37% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 325736	   8592	  33584	 367912	  59d28	rmk-nrf52832

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 317768	   8592	  33000	 359360	  57bc0	rmk-nrf52832

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +2.5% +91.8Ki  [ = ]       0    .debug_str
  +2.2% +39.9Ki  [ = ]       0    .debug_info
  +1.7% +11.4Ki  [ = ]       0    .debug_loc
  +2.6% +7.03Ki  +2.6% +7.03Ki    .text
  +3.6% +6.77Ki  [ = ]       0    .debug_ranges
  +2.3% +6.59Ki  [ = ]       0    .debug_line
  +1.9% +4.74Ki  [ = ]       0    .strtab
  +2.2% +2.22Ki  [ = ]       0    .symtab
  +3.1% +1.23Ki  [ = ]       0    .debug_frame
  +2.1%    +768  +2.1%    +768    .rodata
  [ = ]       0  +1.8%    +584    .bss
  +1.3%    +584  [ = ]       0    .debug_aranges
  +0.1%     +12  [ = ]       0    .debug_abbrev
  +1.7%      +8  [ = ]       0    .defmt
   +11%      +6  [ = ]       0    [Unmapped]
  +2.3%  +173Ki  +2.4% +8.35Ki    TOTAL
use_rust/nrf52840_ble — 416.7 KiB → 425.0 KiB (+2.01% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 384604	   8628	  42000	 435232	  6a420	rmk-nrf52840

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 376616	   8628	  41408	 426652	  6829c	rmk-nrf52840

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +2.3% +93.6Ki  [ = ]       0    .debug_str
  +1.9% +39.9Ki  [ = ]       0    .debug_info
  +1.4% +10.3Ki  [ = ]       0    .debug_loc
  +3.5% +7.57Ki  [ = ]       0    .debug_ranges
  +2.2% +7.04Ki  +2.2% +7.04Ki    .text
  +2.0% +6.50Ki  [ = ]       0    .debug_line
  +1.9% +6.33Ki  [ = ]       0    .strtab
  +1.6% +1.94Ki  [ = ]       0    .symtab
  +2.4% +1.08Ki  [ = ]       0    .debug_frame
  +1.9%    +776  +1.9%    +776    .rodata
  [ = ]       0  +1.5%    +592    .bss
  +1.0%    +480  [ = ]       0    .debug_aranges
  +2.1%     +18  [ = ]       0    .defmt
   +12%      +7  [ = ]       0    [Unmapped]
  -0.6%     -56  [ = ]       0    .debug_abbrev
  +2.1%  +175Ki  +2.0% +8.38Ki    TOTAL
use_rust/nrf52840_ble_split (central) — 493.9 KiB → 501.7 KiB (+1.57% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 452608	   8668	  52416	 513692	  7d69c	central

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 445248	   8668	  51832	 505748	  7b794	central

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +1.8% +89.4Ki  [ = ]       0    .debug_str
  +1.7% +40.9Ki  [ = ]       0    .debug_info
  +3.1% +7.77Ki  [ = ]       0    .debug_ranges
  +0.9% +7.47Ki  [ = ]       0    .debug_loc
  +1.9% +6.70Ki  [ = ]       0    .debug_line
  +1.6% +6.44Ki  +1.6% +6.44Ki    .text
  +1.4% +5.79Ki  [ = ]       0    .strtab
  +1.6% +2.25Ki  [ = ]       0    .symtab
  +2.4% +1.23Ki  [ = ]       0    .debug_frame
  +1.7%    +768  +1.7%    +768    .rodata
  [ = ]       0  +1.1%    +584    .bss
  +1.1%    +576  [ = ]       0    .debug_aranges
  +1.9%     +18  [ = ]       0    .defmt
 +10.0%      +5  [ = ]       0    [Unmapped]
  -0.0%      -1  [ = ]       0    .debug_abbrev
  +1.7%  +169Ki  +1.6% +7.76Ki    TOTAL
use_rust/nrf52840_ble_split (peripheral) — 308.1 KiB → 308.1 KiB (+0.01% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 281932	   8508	  25056	 315496	  4d068	peripheral

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 281888	   8508	  25056	 315452	  4d03c	peripheral

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.2% +5.59Ki  [ = ]       0    .debug_str
  +0.1% +1.63Ki  [ = ]       0    .debug_info
  +0.1%    +713  [ = ]       0    .debug_loc
  +0.4%    +192  [ = ]       0    .debug_aranges
  +0.1%     +88  [ = ]       0    .debug_ranges
  +0.0%     +44  +0.0%     +44    .text
  +0.0%     +37  [ = ]       0    .debug_line
  +0.0%     +32  [ = ]       0    .symtab
  +0.2%     +20  [ = ]       0    .debug_abbrev
 -15.1%      -8  [ = ]       0    [Unmapped]
  -0.1%     -28  [ = ]       0    .debug_frame
  -0.2%    -474  [ = ]       0    .strtab
  +0.1% +7.82Ki  +0.0%     +44    TOTAL
use_rust/pi_pico_w_ble — 669.1 KiB → 677.5 KiB (+1.24% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 639272	      0	  54464	 693736	  a95e8	rmk-pi-pico-w

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 631324	      0	  53880	 685204	  a7494	rmk-pi-pico-w

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +2.1% +91.0Ki  [ = ]       0    .debug_str
  +1.5% +35.8Ki  [ = ]       0    .debug_info
  +0.9% +9.58Ki  [ = ]       0    .debug_loc
  +2.2% +7.01Ki  +2.2% +7.01Ki    .text
  +1.4% +5.74Ki  [ = ]       0    .debug_line
  +1.6% +5.52Ki  [ = ]       0    .strtab
  +2.1% +5.06Ki  [ = ]       0    .debug_ranges
  +2.5% +2.06Ki  [ = ]       0    .symtab
  +2.0%    +836  [ = ]       0    .debug_frame
  +0.3%    +768  +0.3%    +768    .rodata
  [ = ]       0  +1.1%    +584    .bss
  +0.8%    +384  [ = ]       0    .debug_aranges
   +40%     +21  [ = ]       0    [Unmapped]
  +0.2%     +20  [ = ]       0    .debug_abbrev
  +2.0%     +18  [ = ]       0    .defmt
  +1.7%  +163Ki  +1.2% +8.33Ki    TOTAL
use_rust/rp2040 — 146.1 KiB → 154.1 KiB (+5.50% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 143228	      0	  14620	 157848	  26898	rmk-rp2040

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 135572	      0	  14036	 149608	  24868	rmk-rp2040

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +5.4% +90.9Ki  [ = ]       0    .debug_str
  +3.7% +36.3Ki  [ = ]       0    .debug_info
  +2.3% +7.33Ki  [ = ]       0    .debug_loc
  +5.8% +6.72Ki  +5.8% +6.72Ki    .text
  +4.7% +5.55Ki  [ = ]       0    .strtab
  +3.3% +5.47Ki  [ = ]       0    .debug_line
  +5.4% +4.37Ki  [ = ]       0    .debug_ranges
  +4.6% +1.47Ki  [ = ]       0    .symtab
  +5.0%    +832  [ = ]       0    .debug_frame
  +4.7%    +772  +4.8%    +772    .rodata
  [ = ]       0  +4.5%    +584    .bss
  +2.3%    +384  [ = ]       0    .debug_aranges
  +6.2%     +26  [ = ]       0    .defmt
 -13.8%      -8  [ = ]       0    [Unmapped]
  -0.4%     -28  [ = ]       0    .debug_abbrev
  +4.5%  +160Ki  +5.5% +8.05Ki    TOTAL
use_rust/rp2040_split (central) — 160.2 KiB → 168.5 KiB (+5.18% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 156916	      0	  15604	 172520	  2a1e8	central

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 149000	      0	  15020	 164020	  280b4	central

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +4.3% +86.9Ki  [ = ]       0    .debug_str
  +3.2% +35.9Ki  [ = ]       0    .debug_info
  +2.3% +8.48Ki  [ = ]       0    .debug_loc
  +5.5% +6.98Ki  +5.5% +6.98Ki    .text
  +3.1% +5.63Ki  [ = ]       0    .debug_line
  +4.0% +5.30Ki  [ = ]       0    .strtab
  +5.2% +4.72Ki  [ = ]       0    .debug_ranges
  +5.4% +1.89Ki  [ = ]       0    .symtab
  +4.7%    +868  [ = ]       0    .debug_frame
  +4.1%    +768  +4.1%    +768    .rodata
  [ = ]       0  +4.2%    +584    .bss
  +2.2%    +392  [ = ]       0    .debug_aranges
  +4.0%     +18  [ = ]       0    .defmt
   +33%     +14  [ = ]       0    [Unmapped]
  -0.2%     -12  [ = ]       0    .debug_abbrev
  +3.8%  +157Ki  +5.2% +8.30Ki    TOTAL
use_rust/rp2040_split (peripheral) — 28.1 KiB → 28.1 KiB (+0.00%)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
  25584	     60	   3172	  28816	   7090	peripheral

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
  25584	     60	   3172	  28816	   7090	peripheral

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.5% +4.72Ki  [ = ]       0    .debug_str
  +0.2%    +952  [ = ]       0    .debug_info
  +1.4%    +192  [ = ]       0    .debug_aranges
  +0.1%     +34  [ = ]       0    .debug_line
  +0.0%      +1  [ = ]       0    .strtab
  -6.9%      -5  [ = ]       0    [Unmapped]
  +0.4% +5.87Ki  [ = ]       0    TOTAL
use_rust/stm32f1 — 62.3 KiB → 69.5 KiB (+11.44% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
  63872	     28	   7244	  71144	  115e8	rmk-stm32f1

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
  57232	     28	   6580	  63840	   f960	rmk-stm32f1

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +9.7% +86.5Ki  [ = ]       0    .debug_str
  +7.3% +41.0Ki  [ = ]       0    .debug_info
  +8.9% +11.3Ki  [ = ]       0    .debug_loc
   +12% +6.48Ki   +12% +6.48Ki    .text
   +15% +6.20Ki  [ = ]       0    .debug_ranges
  +6.7% +5.88Ki  [ = ]       0    .debug_line
  +6.5% +2.77Ki  [ = ]       0    .strtab
  +6.3% +1.20Ki  [ = ]       0    .symtab
  +7.9%    +956  [ = ]       0    .debug_frame
  [ = ]       0   +10%    +664    .bss
  +4.9%    +216  [ = ]       0    .debug_aranges
  +0.8%     +44  [ = ]       0    .debug_abbrev
  +0.5%      +4  +0.6%      +4    .rodata
 -24.3%     -17  [ = ]       0    [Unmapped]
  +8.8%  +162Ki   +11% +7.13Ki    TOTAL
use_rust/stm32h7 — 119.5 KiB → 127.0 KiB (+6.26% ⬆️)

cargo size (PR):

   text	   data	    bss	    dec	    hex	filename
 114788	    320	  14904	 130012	  1fbdc	rmk-stm32h7

cargo size (main):

   text	   data	    bss	    dec	    hex	filename
 107708	    320	  14320	 122348	  1ddec	rmk-stm32h7

Bloaty diff (PR vs main):

    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +3.6% +89.9Ki  [ = ]       0    .debug_str
  +3.4% +40.4Ki  [ = ]       0    .debug_info
  +5.3% +13.0Ki  [ = ]       0    .debug_loc
  +9.1% +7.12Ki  [ = ]       0    .debug_ranges
  +6.9% +6.90Ki  +6.9% +6.90Ki    .text
  +4.0% +6.04Ki  [ = ]       0    .debug_line
  +4.0% +4.03Ki  [ = ]       0    .strtab
  +3.2% +1.12Ki  [ = ]       0    .symtab
  +3.6%    +696  [ = ]       0    .debug_frame
  [ = ]       0  +4.4%    +584    .bss
  +0.7%    +304  [ = ]       0    .debug_aranges
  +0.4%     +16  +0.4%     +16    .rodata
  +2.6%      +8  [ = ]       0    .defmt
 -18.5%     -10  [ = ]       0    [Unmapped]
  -0.3%     -22  [ = ]       0    .debug_abbrev
  +3.8%  +169Ki  +6.3% +7.48Ki    TOTAL

@HaoboGu

HaoboGu commented May 21, 2026

Copy link
Copy Markdown
Collaborator

There is One Shot Sticky Modifier in main branch, what's the difference?

@ldsands

ldsands commented May 21, 2026

Copy link
Copy Markdown
Contributor Author

There is One Shot Sticky Modifier in main branch, what's the difference?

OSM(LAlt) releases the modifier after one keypress — press OSM, release, press Tab, Alt+Tab fires once and Alt is gone. SM(Tab, LAlt) bundles the modifier and key together and keeps the modifier held across repeated presses of the same SM key: first press sends Alt+Tab, second press sends Alt+Tab again (Alt still held), and Alt only releases when you press something else entirely. OSM is for one-shot use; SM is specifically for cycling (Alt+Tab, Ctrl+Tab) where you need the modifier to persist across multiple presses of the same key.

@HaoboGu

HaoboGu commented May 21, 2026 •

Copy link
Copy Markdown
Collaborator

OSM(LAlt) releases the modifier after one keypress — press OSM, release, press Tab, Alt+Tab fires once and Alt is gone. SM(Tab, LAlt) bundles the modifier and key together and keeps the modifier held across repeated presses of the same SM key: first press sends Alt+Tab, second press sends Alt+Tab again (Alt still held), and Alt only releases when you press something else entirely. OSM is for one-shot use; SM is specifically for cycling (Alt+Tab, Ctrl+Tab) where you need the modifier to persist across multiple presses of the same key.

Great, maybe the two types be merged into a single type of behavior, i.e. a general "Sticky Key"?

I'm imaging some like SK(key, keep, max_repeat):

  • When this SK is triggered, key is activated
  • key keeps to be activated if keys in keep list is pressed
  • key is released when keep is pressed for max_repeat times, or any other keys is triggered

With this, one-shot mod can be represented as SK(mod, [], 1), and SM(Tab, LAlt) can be represented as SK(Tab, [LAlt], MAX_REPEAT)

What do you think?

@ldsands

ldsands commented May 21, 2026

Copy link
Copy Markdown
Contributor Author

OSM(LAlt) releases the modifier after one keypress — press OSM, release, press Tab, Alt+Tab fires once and Alt is gone. SM(Tab, LAlt) bundles the modifier and key together and keeps the modifier held across repeated presses of the same SM key: first press sends Alt+Tab, second press sends Alt+Tab again (Alt still held), and Alt only releases when you press something else entirely. OSM is for one-shot use; SM is specifically for cycling (Alt+Tab, Ctrl+Tab) where you need the modifier to persist across multiple presses of the same key.

Great, maybe the two types be merged into a single type of behavior, i.e. a general "Sticky Key"?

I'm imaging some like SK(key, keep, max_repeat):

  • When this SK is triggered, key is activated
  • key keeps to be activated if keys in keep list is pressed
  • key is released when keep is pressed for max_repeat times, or any other keys is triggered

With this, one-shot mod can be represented as SK(mod, [], 1), and SM(Tab, LAlt) can be represented as SK(Tab, [LAlt], MAX_REPEAT)

What do you think?

I like that idea, since the two are conceptually similar. Let me think through what implementing this in the one-shot would need (and what I'd want from it).

Max repeat, on the other hand, isn't something I'd personally use, though I see the utility. Right now I'm thinking of the browser tabs I have open. As long as we can set "no max repeat" or "infinite" as an option, that works for me.

I'd also like a timeout that's independent of the one-shot timeout. This probably isn't strictly necessary, but I suspect most people who use this would want a different timeout than the one for one-shot keys. I usually exit a sticky mod with my layer button, but when cycling through browser tabs I'll sometimes go through several, pause to look at the screen, then continue. Again, a personal preference I could make work with a shared timeout, but worth mentioning.

I also wonder how this interacts with layer changes. I use this key on another layer (via MO) so that returning to the base layer automatically exits the sticky mod and I can resume typing immediately. So I'd want an optional per-key feature to exit on layer change. I wouldn't want this on my other one-shot keys, though, since I use those across layers constantly; I added it specifically to this sticky mod implementation.

Forgive the verbosity; talking through it helped. I'm happy to fold this into the current one-shot keys implementation, but if you want to go that route, I would prefer a per-key timeout option and a per-key "exit on layer change" option. Adding max retries is a great idea. I don't know what you'd want as the default, but I'd like to have the max retries configurable to have infinite, or to assume infinite until the timeout from the last sticky mod key press.

What do you think?

@HaoboGu

HaoboGu commented May 21, 2026 •

Copy link
Copy Markdown
Collaborator

Max repeat, on the other hand, isn't something I'd personally use, though I see the utility. Right now I'm thinking of the browser tabs I have open. As long as we can set "no max repeat" or "infinite" as an option, that works for me.

Yes, I agree. Omitting it means "infinite", i.e. SK(Tab, [LAlt], MAX_REPEAT) == SK(Tab, [LAlt]).
But I'm not sure what the default behavior should be: max_repeat == 1 or infinite?

About the per-key timeout, I have no strong opinion on it. Using the shared timeout as the default and overriding it when a per-key timeout exists is fine with me. But it does increase the complexity and RAM/Flash usage.

I also wonder how this interacts with layer changes.

Should it be included in the keep list?

Also, the length of keep list is a bit tricky. I think it should be calculated at compile-time and applied to the type. TBH I don't know if the idea works, but I think it's worth a try at least.

@ldsands

ldsands commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

Just a quick update, I think I'll be done with this by the end of the week or earlier for you to look at. I may also wait until #854 is done as well and make sure that I have it working with that merge.

@HaoboGu

HaoboGu commented May 29, 2026

Copy link
Copy Markdown
Collaborator

Great!

Only a minor comment left in #854, I think we can get it merged first.

@HaoboGu

HaoboGu commented May 29, 2026 •

Copy link
Copy Markdown
Collaborator

#854 is merged

ldsands added 15 commits May 30, 2026 16:48
- Restore pre-existing comments in LayerOff, LayerToggle, DefaultLayer arms
- Fix typo in LayerToggle comment ("release" → "released")
- Remove unused HidKeyCode import from sticky_mod.rs
…ase guard

Action::Key(KeyCode::Hid(LShift)) etc. are modifier keys expressed via
the Key action rather than the Modifier action. The SM release guard now
checks hid_key.is_modifier() so that holding Shift for reverse-Tab cycling
doesn't break StickyMod state.
Five rusty_fork_test cases covering: basic two-press flow, layer-change
cleanup, Shift-does-not-release-SM, rapid triple presses, and combined
LCtrl|LShift modifier.
rusty-fork was used by keyboard_sticky_mod_test but missing from
Cargo.toml dev-dependencies. Also add missing .await on
process_action_layer_switch call in test_key_action_transparent
(function became async in upstream refactor).
…d StickyMod docs

- Make DurationMillis pub (was pub(crate), caused visibility warning via StickyModConfig pub field)
- Add test_sm_action_parsing and test_sm_action_grammar to rmk-config/src/layout.rs
- Add Sticky Modifiers section to behavior.md
- Add SM(key, modifier) entry to layout.md advanced layer operations list
Move timeout tracking out of the release handler's blocking select and into
the main run() loop, following the same pattern as mouse repeat deadlines.

- StickyModState::Active now stores an optional Instant deadline
- Deadline is set (and reset) on each SM key PRESS, so repeated presses
  extend the hold window rather than starting from the release
- run() combines SM and mouse deadlines and uses with_deadline(); on expiry
  it calls release_sticky_mod_if_active() before continuing
- Remove embassy_futures select from release handler (was fragile: any
  event arriving cancelled the timer, preventing timeout on 2nd+ press)
- Add sticky_mod_timeout() accessor to KeyMap
- Add 2 integration tests: test_sm_timeout and test_sm_timeout_resets_on_press
…shots

- Replace map_or(false, ...) with is_some_and(...) in keyboard.rs run() loop
- Regenerate endpoint key snapshots in rmk-types: Action::StickyMod added a
  variant to the Action enum, changing the postcard schema hash for keymap,
  combo, and morse endpoints
@ldsands

ldsands commented May 30, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback on consolidating SM and OSM! I've rebased this branch onto the latest main (which includes PR #854's quick_release feature) and reworked the implementation to address your request.

What changed:

  • Removed StickyMod entirely — replaced by the unified StickyKey (SK) action
  • SK(key, [modifier], max_repeat, timeout_ms, exit_on_layer_change) — all args after key are optional
    • max_repeat = 0 → infinite repeats (default)
    • timeout_ms = 0 → uses global [behavior.sticky_key] timeout (default; no timeout if unset)
    • exit_on_layer_change → defaults to false (modifier survives layer changes)
  • OSM remains as-is for the one-shot use case; SK handles the "hold modifier across repeated presses" use case
  • 11 integration tests added for SK behavior; all 450 tests passing
  • Docs updated in behavior.md and layout.md

Example usage: SK(Tab, [LAlt]) for Alt+Tab window cycling — first press sends Alt+Tab, each subsequent press sends Tab again while holding Alt, releases when you press anything else.

@ldsands
ldsands force-pushed the feat/sticky-mod branch from 2a2f462 to 8d51d88 Compare May 30, 2026 23:56
ldsands added 3 commits June 2, 2026 11:05
…es not yet implemented)

Tests cover: basic flow, layer-change cleanup, shift coexistence, rapid presses,
combined modifiers, global timeout, timeout reset, max_repeat, per-key timeout,
exit_on_layer_change=true, and exit_on_layer_change=false (survives layer change).
Compile fails on StickyKeyConfig, StickyKeyAction, sk! macro, and
BehaviorConfig::sticky_key — all to be added in Tasks 3–8.
Comment thread rmk/src/keyboard.rs
/// Modifier and layer effects can coexist and therefore own distinct policies,
/// phases, sources, and deadlines. A tap key is exclusive with both.
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct StickyKeyState {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think sticky key should be general enough to process all held Actions. Distinguishing between modifier/layer/keys makes the current implementation not general at all. That means, this state should not be the current shape.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks — I reworked the state around that model in 03eb1a91. StickyKeyState now owns a uniform bounded collection of StickyEntry values, and every entry stores its canonical held Action together with the shared phase, policy, deadline, repetition, and buffered-claim lifecycle. OSM, OSL, and modified tap keys no longer have separate top-level state slots or lifecycle types.

Action-specific handling is now limited to applying and releasing the concrete effect. The small amount of modifier-only metadata remains because modifiers can have multiple physical producers and must remember whether their HID effect has already been reported; layer and tap-key effects are represented completely by their stored Action. The OSM and OSL aliases remain unchanged. 46673893 adds parity and edge-case coverage to ensure the consolidation does not remove existing behavior.

@HaoboGu HaoboGu Aug 4, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

sorry maybe I didn't make it clear, the current code still has modifier/layer/tap-key processing logic in sticky key, which leads to the binary size bloats -- these actions' processing logic is duplicate in normal path and sticky key path.

Actually sticky key itself should not execute/classify any Action variant, all "effect" should be removed. Only a thin state like sticky: (Action, u8) is needed.

@HaoboGu HaoboGu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For the release_on_keyup_after, I feel it's more like a Morse/Tap-Hold behavior, not Sticky Key's. But since we've promoted the StickyKey as a KeyAction, I don't know if there's other solutions, so your code might be right and the only way to achieve this.

I prefer to have a name like release_after_hold and set a longer threshold?

@ldsands

ldsands commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

For the release_on_keyup_after, I feel it's more like a Morse/Tap-Hold behavior, not Sticky Key's. But since we've promoted the StickyKey as a KeyAction, I don't know if there's other solutions, so your code might be right and the only way to achieve this.

I prefer to have a name like release_after_hold and set a longer threshold?

Works for me.

  • Renamed release_on_keyup_after to release_after_hold throughout.
  • Changed the documented example threshold from 300ms to 500ms.
  • Kept the threshold optional and disabled by default.

@braindefender

Copy link
Copy Markdown
Contributor

@HaoboGu @ldsands What is the current status of this PR?

@ldsands

ldsands commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

@HaoboGu @ldsands What is the current status of this PR?

Hi @braindefender

Sorry, I've been busy with a conference, getting ready to move soon, starting a new job and more. It has been a crazy month. Thus I've been unable to keep up on this PR.

I resolved all the conflicts and now I'm working through anything that code reviews bring up (better models mean more identified potential issues) and I'm working through them right now. I'll also see if I cannot reduce the flash/ram usage as well one more time. Once they're all done (hopefully today) I'll tag you and HaoboGu to let you know that I think I got everything.

Also, as I've been using this branch daily since July I did come across another minor issue that does come up on occasion. I fixed the issue and now (confirmed on my keyboard).

For the record, the issue I mentioned above is explained with an example below:

If I hold OSM(ctrl)+OSM(shift)+leftarrow (again just an example) and then I lift off of OSM(ctrl) and continue to hold OSM(shift)+leftarrow the OSM(ctrl) is still being sent. To get the desired behavior, I must release all keys on the keyboard before I can use OSM(shift)+leftarrow correctly. Also, note that I can lift up on the leftarrow while holding down the OSM(shift), but the keyboard is still sending the control and the shift simultaneously. If I left up on the OSM(ctrl) while still holding any other key. I want it to just send only the keys (be they OSM or not) that I'm holding down. I also want when I release that, OSM(ctrl) for the keyboard to immediately stop sending that OSM(ctrl) to the OS but continue sending the OSM(shift) which I am holding.

@ldsands

ldsands commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

@HaoboGu

Okay, I think I've gone about as far as I can but in one of the final reviews of the code codex found an issue with Auto Mouse Layer below is the small summary of the issue along with the recommended fix after exploring a few different possibilities.

Auto Mouse Layer currently sees SK(LCtrl) as an ordinary Ctrl action, so it can incorrectly deactivate the mouse layer when deactivate_on_key is enabled. A possible fix is to retain a small “came from Sticky” marker in the action event, allowing Auto Mouse Layer to keep the layer active and reset its timeout as documented. This would be a small change, but may slightly increase event size and affect dongle serialization.

I've tried as much as possible (though not always successfully) not to touch any other features, so I thought I would ask on this one. I've committed the fix that it came up with, but I'll undo it and tackle it some other way if you think there is a better approach or if you think we can just leave it as is.

Other recent changes (mostly just to deal with merges, or to address failed tests) as summarized by codex.

  • Added immediate live release for held Sticky modifiers.
  • Fixed Sticky combo buffering, identity, ownership, and claim timing.
  • Preserved modifier ownership across Caps Word and other interactions.
  • Added compile-time elimination of unused Sticky handlers to reduce firmware flash.
  • Strengthened Sticky configuration, capacity documentation, and tests.

Let me know if you need anything else.

@HaoboGu

HaoboGu commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Thanks @ldsands !

I have an idea about sharing state between processors. Would you mind me editing code in this PR?

@ldsands

ldsands commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks @ldsands !

I have an idea about sharing state between processors. Would you mind me editing code in this PR?

@HaoboGu Absolutely feel free.

edit: I think I had said something about this before, but I am mostly self-taught in Python for data science. I know enough to be able to muddle my way through other programming languages and now with AI where it is, I'm able to make contributions that work, but as you have clearly observed, my ability to do programming of this kind "well" is limited. Thanks again for your patience with me on this pr.

This branch has not been deployed

No deployments
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.

3 participants