Repository: jtroo/kanata (Rust)

Issue:
Bug: `on-physical-idle` prevents `$tap-repress-timeout`
### Requirements

- [x] I've searched [platform-specific issues](https://github.com/jtroo/kanata/blob/main/docs/platform-known-issues.adoc), [issues](https://github.com/jtroo/kanata/issues) and [discussions](https://github.com/jtroo/kanata/discussions) to see if this has been reported before.
- [x] My issue does not involve multiple simultaneous key presses, OR it does but I've confirmed it is not [key rollover or ghosting](https://github.com/jtroo/kanata/discussions/822).

### Describe the bug

I tried modifying the [advanced home row mods](https://github.com/jtroo/kanata/blob/main/cfg_samples/home-row-mod-advanced.kbd) config so that it takes advantage of the recently-implemented [`on-physical-idle`](https://github.com/jtroo/kanata/pull/1715) feature. 

Everything behaves correctly, except that repressing the mod key quickly within the `$tap-repress-timeout` window no longer causes `$tap-action` to be held down.

### Relevant kanata config

Taken straight from the [advanced home row mods](https://github.com/jtroo/kanata/blob/main/cfg_samples/home-row-mod-advanced.kbd) example config, but with `on-idle` replaced with `on-physical-idle`:

```lisp
(defcfg
  process-unmapped-keys yes
)
(defsrc
  a   s   d   f   j   k   l   ;
)
(defvar
  ;; Note: consider using different time values for your different fingers.
  ;; For example, your pinkies might be slower to release keys and index
  ;; fingers faster.
  tap-time 200
  hold-time 150

  left-hand-keys (
    q w e r t
    a s d f g
    z x c v b
  )
  right-hand-keys (
    y u i o p
    h j k l ;
    n m , . /
  )
)
(deflayer base
  @a  @s  @d  @f  @j  @k  @l  @;
)

(deflayer nomods
  a   s   d   f   j   k   l   ;
)
(defvirtualkeys
  to-base (layer-switch base)
)
(defalias
  tap (multi
    (layer-switch nomods)
    (on-physical-idle 20 tap-vkey to-base)
  )

  a (tap-hold-release-keys $tap-time $hold-time (multi a @tap) lmet $left-hand-keys)
  s (tap-hold-release-keys $tap-time $hold-time (multi s @tap) lalt $left-hand-keys)
  d (tap-hold-release-keys $tap-time $hold-time (multi d @tap) lctl $left-hand-keys)
  f (tap-hold-release-keys $tap-time $hold-time (multi f @tap) lsft $left-hand-keys)
  j (tap-hold-release-keys $tap-time $hold-time (multi j @tap) rsft $right-hand-keys)
  k (tap-hold-release-keys $tap-time $hold-time (multi k @tap) rctl $right-hand-keys)
  l (tap-hold-release-keys $tap-time $hold-time (multi l @tap) ralt $right-hand-keys)
  ; (tap-hold-release-keys $tap-time $hold-time (multi ; @tap) rmet $right-hand-keys)
)
```

`on-physical-idle` is not available yet in the simulator, but here's the `on-idle` version which works correctly: [simulator link](https://jtroo.github.io?data=BQEwpgZgxhDmBQACRAHATgeymAzjgtAK4B2AtgIYopgj4DWYAnjoo7vAJTyiQ5pRJE5ZIhbIQIiCIBWIuiIA2IgNydu4CADdyaQcuWIAchgAuYAFyIoGYjgCW4NIkL3isRCDsQIYNGGImiCZ2pGCI2gqEuIgQGE6MGIROnt6+-oEQdm6+OAB0egYAYnGIYAAe5KQoCmAANKyJTihZdHbRpHawABaBAEZhOAoYAO6+QRiIfjXkOGEMzELEElngZQUxWbA5MTNmaPnIJpT4waGIAEwADJeCXRgKtKdhAIwArDeCNRAm+F3kS-QmCxgIJkABHRDDUqTIKgoSiDwxRAIESIABeiDKVnCiF6gi4yDQnR6v3+tHmwLhjGciDsiAmKDhXUQsnkSlUqOIiFIiHquUQAHp8WoeBAFOQ2E5ejMwIIAALCRBysRyiRKqRK2RK+RKpRK1RcdSQcWSxDEDCkDAgHCCRUI8SSGRyRQqEUaTR2NAmQjkBQUwQmDD4aWzRDAE2+fA4YZ2ExQZkhsBcLii312GYByhh0iEBTBOHhiWR6Ox+Nmi1WnAEkTAGz4Bw1C6XILHTTzcbBmXVw3IYTAI4oX73WhTMAywELAAkA5OITCk7uD1nZ2AObzdOEcoHHEQClCgUnXx+fwBFOrwJni5HYGmswnLGnxyeiAXw+XYVXueCCK3lB34rzF8j1JU8gWrCR+2OK98FHccKRfGdn1fJdn0-ddEV-FB-ygEwlEPSBjzJe9qykSDB2g2C73gx9ByQ6DULXb8pEw-8cG+ICCJA8kwMEWQyKHJdKLAe8EKfOcX3o8S0O-WQWMmNiDyJbpCNA5hq3kfiKJvMcqKBUTaPE5DHikxi6XkOT+Fwl8lJJE9uLUz4w0vN8hJEmj3wkt8GK-OklAs31FOJFT7KrPQnKglztLgvT3LoryTJ8xADAs-drKCrjiM4AAfHLcry-KspAcwAGkmAAQSCcxnmbQgSvKyrqo8OrGAqkxzAATmuS4gA).

### To Reproduce

1. Copy the config above to a file
2. Run `kanata --cfg config.kdb`
3. Repress the <kbd>A</kbd> key quickly and try to trigger the `$tap-repress-timeout` behavior


### Expected behavior

Repressing the key quickly should cause `$tap-action` to be held down.

### Kanata version

kanata 1.9.0, patched with #1715

### Debug logs

_No response_

### Operating system

Linux

### Additional context

_No response_


Discussion:
I believe the issue happens because `on-idle` doesn't activate the vkey while tap timeout hasn't activated yet, while `on-physical-idle` does activate the vkey, and it's the vkey activation that interferes with the tap repress.

Can probably be fixed by skipping the "last pressed" tracking update if it's a vkey instead of physical input.
Oh, the code already does it. I guess it's the activation of a vkey at all 🤔?

```
    fn update_coord(&mut self, coord: KCoord) {
        if coord.0 == REAL_KEY_ROW {
            // Only update if it's a real key press.
            self.coord = coord;
        }
    }
```
Ah it's probably this:

```rs
        if self.last_press_tracker.coord != coord {
            self.last_press_tracker.tap_hold_timeout = 0;
        }
```
Just tried out the latest commit and it fixed the issue, thanks!



Patch:
diff --git a/keyberon/src/layout.rs b/keyberon/src/layout.rs
index 519a8fd70..3eb68d9da 100644
--- a/keyberon/src/layout.rs
+++ b/keyberon/src/layout.rs
@@ -1633,7 +1633,7 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout<
         }
         let action = action;
 
-        if self.last_press_tracker.coord != coord {
+        if self.last_press_tracker.coord != coord && coord.0 == REAL_KEY_ROW {
             self.last_press_tracker.tap_hold_timeout = 0;
         }
         use Action::*;


Test patch:
diff --git a/src/tests/sim_tests/mod.rs b/src/tests/sim_tests/mod.rs
index ee0f5b0ba..11495f740 100644
--- a/src/tests/sim_tests/mod.rs
+++ b/src/tests/sim_tests/mod.rs
@@ -55,9 +55,11 @@ fn simulate_with_file_content<S: AsRef<str>>(
         match pair.split_once(':') {
             Some((kind, val)) => match kind {
                 "t" => {
-                    let tick = str::parse::<u128>(val).expect("valid num for tick");
-                    k.tick_ms(tick, &None).unwrap();
-                    k.can_block_update_idle_waiting(tick as u16);
+                    let ticks = str::parse::<u128>(val).expect("valid num for tick");
+                    for _ in 0..ticks {
+                        let _ = k.tick_ms(1, &None);
+                        let _ = k.can_block_update_idle_waiting(1);
+                    }
                 }
                 "d" => {
                     let key_code = str_to_oscode(val).expect("valid keycode");
diff --git a/src/tests/sim_tests/tap_hold_tests.rs b/src/tests/sim_tests/tap_hold_tests.rs
index 52135e66b..e5d2395ab 100644
--- a/src/tests/sim_tests/tap_hold_tests.rs
+++ b/src/tests/sim_tests/tap_hold_tests.rs
@@ -45,3 +45,26 @@ fn tap_hold_release_timeout_with_reset() {
     .to_ascii();
     assert_eq!("t:125ms dn:Y t:6ms dn:B t:1ms up:B t:1ms up:Y", result);
 }
+
+#[test]
+fn on_physical_idle_with_tap_repress() {
+    let result = simulate(
+        "
+(defsrc a)
+(deflayer base @a)
+(deflayer nomods a)
+(defvirtualkeys to-base (layer-switch base))
+(defalias
+  tap (multi
+    (layer-switch nomods)
+    (on-physical-idle 20 tap-vkey to-base)
+  )
+  a (tap-hold 100 100 (multi a @tap) b)
+)
+        ",
+        "d:a t:30 u:a t:30 d:a t:1000",
+    )
+    .to_ascii();
+    // t:30ms dn:A t:6ms up:A t:124ms dn:B
+    assert_eq!("t:30ms dn:A t:6ms up:A t:24ms dn:A", result);
+}
diff --git a/src/tests/sim_tests/vkey_sim_tests.rs b/src/tests/sim_tests/vkey_sim_tests.rs
index 2f192bab6..5e676ac76 100644
--- a/src/tests/sim_tests/vkey_sim_tests.rs
+++ b/src/tests/sim_tests/vkey_sim_tests.rs
@@ -53,5 +53,5 @@ fn on_idle_must_be_single_threaded() {
         "d:b t:10 u:b t:5 d:a t:50 u:a t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10",
     )
     .to_ascii();
-    assert_eq!("t:136ms dn:LGui t:1ms up:LGui", result);
+    assert_eq!("t:137ms dn:LGui t:1ms up:LGui", result);
 }