Repository: HaoboGu/rmk (language: rust)

Issue:
Hold after tap



Discussion / hints:

Fix patch:
diff --git a/docs/src/images/tap_hold.drawio b/docs/src/images/tap_hold.drawio
new file mode 100644
index 00000000..3056b153
--- /dev/null
+++ b/docs/src/images/tap_hold.drawio
@@ -0,0 +1,209 @@
+<mxfile host="65bd71144e">
+    <diagram id="fiZZQFQv48HDeNDv6MQ4" name="第 1 页">
+        <mxGraphModel dx="660" dy="998" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
+            <root>
+                <mxCell id="0"/>
+                <mxCell id="1" parent="0"/>
+                <mxCell id="26" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=12;" edge="1" parent="1" source="4" target="25">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="4" value="&lt;font style=&quot;font-size: 14px;&quot;&gt;A tap/hold key is pressed&lt;/font&gt;" style="ellipse;whiteSpace=wrap;html=1;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=20;" vertex="1" parent="1">
+                    <mxGeometry x="210" width="120" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="8" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="5" target="7">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="9" value="timeout" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=14;fontFamily=Verdana;" vertex="1" connectable="0" parent="8">
+                    <mxGeometry x="-0.15" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="17" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=12;" edge="1" parent="1" source="5" target="16">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="5" value="&lt;meta charset=&quot;utf-8&quot;&gt;&lt;span style=&quot;color: rgb(0, 0, 0); font-family: Verdana; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;&quot;&gt;wait for timeout or next key&lt;/span&gt;" style="rhombus;whiteSpace=wrap;html=1;fontSize=20;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="210" y="320" width="120" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="11" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=12;" edge="1" parent="1" source="7" target="10">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="13" value="wait for release" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=14;fontFamily=Verdana;" vertex="1" connectable="0" parent="11">
+                    <mxGeometry x="0.025" y="-3" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="7" value="press hold" style="whiteSpace=wrap;html=1;fontSize=14;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="440" y="330" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="10" value="release hold" style="whiteSpace=wrap;html=1;fontSize=14;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="730" y="330" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="19" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=12;" edge="1" parent="1" source="16" target="18">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="20" value="press" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=14;fontFamily=Verdana;" vertex="1" connectable="0" parent="19">
+                    <mxGeometry x="-0.125" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="22" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=12;" edge="1" parent="1" source="16">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="270" y="640" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="23" value="release" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=12;fontFamily=Verdana;" vertex="1" connectable="0" parent="22">
+                    <mxGeometry x="-0.075" y="2" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="16" value="&lt;span style=&quot;font-size: 14px; background-color: rgb(255, 255, 255);&quot;&gt;next key is press?&lt;/span&gt;" style="rhombus;whiteSpace=wrap;html=1;fontSize=20;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="210" y="480" width="120" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="36" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=0;entryDx=0;entryDy=0;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="18" target="16">
+                    <mxGeometry relative="1" as="geometry">
+                        <Array as="points">
+                            <mxPoint x="400" y="450"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="18" value="record key, continue wait" style="whiteSpace=wrap;html=1;fontSize=14;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="440" y="490" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="24" value="No key during the threshold" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=12;" vertex="1" parent="1">
+                    <mxGeometry x="520" y="280" width="210" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="27" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=12;" edge="1" parent="1" source="25" target="5">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="31" value="larger than" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=14;fontFamily=Verdana;" vertex="1" connectable="0" parent="27">
+                    <mxGeometry x="-0.0222" y="1" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="29" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=12;" edge="1" parent="1" source="25" target="28">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="30" value="less than threshold" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=14;fontFamily=Verdana;" vertex="1" connectable="0" parent="29">
+                    <mxGeometry x="-0.05" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="25" value="&lt;span style=&quot;background-color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;font-size: 14px;&quot;&gt;check prior-idle time&lt;/span&gt;&lt;br&gt;&lt;/span&gt;" style="rhombus;whiteSpace=wrap;html=1;fontSize=20;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="210" y="150" width="120" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="28" value="trigger tap" style="whiteSpace=wrap;html=1;fontSize=14;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="587" y="160" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="48" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="37" target="41">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="270" y="800" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="49" value="no" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=14;fontFamily=Verdana;" vertex="1" connectable="0" parent="48">
+                    <mxGeometry x="-0.0444" y="2" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="88" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="37" target="50">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="89" value="yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=14;fontFamily=Verdana;" vertex="1" connectable="0" parent="88">
+                    <mxGeometry x="-0.06" relative="1" as="geometry">
+                        <mxPoint y="1" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="37" value="&lt;span style=&quot;font-size: 14px; background-color: rgb(255, 255, 255);&quot;&gt;key is recorded?&lt;/span&gt;" style="rhombus;whiteSpace=wrap;html=1;fontSize=20;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="210" y="630" width="120" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="46" value="no" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="41" target="45">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="53" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="41" target="52">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="61" value="yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=14;fontFamily=Verdana;" vertex="1" connectable="0" parent="53">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="41" value="&lt;span style=&quot;font-size: 14px; background-color: rgb(255, 255, 255);&quot;&gt;same tap hold key released?&lt;/span&gt;" style="rhombus;whiteSpace=wrap;html=1;fontSize=20;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="210" y="810" width="120" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="45" value="&lt;font style=&quot;font-size: 14px;&quot;&gt;release this key as normal&lt;/font&gt;" style="whiteSpace=wrap;html=1;fontSize=20;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="410" y="820" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="50" value="trigger&amp;nbsp;&lt;b&gt;hold&lt;/b&gt;, then tap recorded key" style="whiteSpace=wrap;html=1;fontSize=14;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="430" y="640" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="55" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="52" target="54">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="56" value="no" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=14;fontFamily=Verdana;" vertex="1" connectable="0" parent="55">
+                    <mxGeometry x="-0.025" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="68" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="52" target="67">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="52" value="&lt;span style=&quot;font-size: 14px; background-color: rgb(255, 255, 255);&quot;&gt;is there a key in recorded list?&lt;/span&gt;" style="rhombus;whiteSpace=wrap;html=1;fontSize=20;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="210" y="970" width="120" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="54" value="impossible" style="whiteSpace=wrap;html=1;fontSize=20;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="410" y="980" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="66" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="59" target="65">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="59" value="&lt;font style=&quot;font-size: 14px;&quot;&gt;tap ALL the recorded keys&lt;/font&gt;" style="whiteSpace=wrap;html=1;fontSize=20;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="200" y="1490" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="64" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="62" target="59">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="62" value="trigger &lt;b&gt;hold&lt;/b&gt;" style="whiteSpace=wrap;html=1;fontSize=14;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="200" y="1370" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="65" value="end" style="ellipse;whiteSpace=wrap;html=1;fontSize=20;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="220" y="1589" width="80" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="82" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="67" target="81">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="67" value="wait another 50 ms" style="whiteSpace=wrap;html=1;fontSize=14;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="210" y="1090" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="92" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="71">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="340" y="1120" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="71" value="for roling keys, extend hold for a while" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontFamily=Verdana;fontSize=14;" vertex="1" parent="1">
+                    <mxGeometry x="420" y="1105" width="290" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="83" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="81" target="62">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="85" value="yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=14;fontFamily=Verdana;" vertex="1" connectable="0" parent="83">
+                    <mxGeometry x="-0.2" y="2" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="86" value="" style="edgeStyle=none;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;html=1;fontFamily=Verdana;fontSize=14;" edge="1" parent="1" source="81" target="84">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="87" value="no" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=14;fontFamily=Verdana;" vertex="1" connectable="0" parent="86">
+                    <mxGeometry x="-0.0444" y="1" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="81" value="&lt;span style=&quot;font-size: 14px; background-color: rgb(255, 255, 255);&quot;&gt;key in recorded list is released too&lt;/span&gt;" style="rhombus;whiteSpace=wrap;html=1;fontSize=20;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="200" y="1220" width="120" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="84" value="trigger &lt;b&gt;tap&lt;/b&gt;, and tap recorded key" style="whiteSpace=wrap;html=1;fontSize=14;fontFamily=Verdana;sketch=1;hachureGap=4;jiggle=2;curveFitting=1;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;" vertex="1" parent="1">
+                    <mxGeometry x="410" y="1230" width="120" height="60" as="geometry"/>
+                </mxCell>
+            </root>
+        </mxGraphModel>
+    </diagram>
+</mxfile>
\ No newline at end of file
diff --git a/docs/src/keyboard_configuration.md b/docs/src/keyboard_configuration.md
index 2c2099f1..42833ae5 100644
--- a/docs/src/keyboard_configuration.md
+++ b/docs/src/keyboard_configuration.md
@@ -172,6 +172,25 @@ adjust = 3
 ```
 In this example, when both layers 1 (`upper`) and 2 (`lower`) are active, layer 3 (`adjust`) will also be enabled.
 
+#### Tap Hold
+
+In the `tap_hold` sub-table, you can configure the following parameters:
+
+- `enable_hrm`: Enables or disables HRM (Home Row Mod) mode. When enabled, the `prior_idle_time` setting becomes functional. Defaults to `false`.
+- `prior_idle_time`: If the previous non-modifier key is released within this period before pressing the current tap-hold key, the tap action for the tap-hold behavior will be triggered. This parameter is effective only when enable_hrm is set to `true`. Defaults to 120ms.
+- `hold_timeout`: Defines the duration a tap-hold key must be pressed to determine hold behavior. If tap-hold key is released within this time, the key is recognized as a "tap". Holding it beyond this duration triggers the "hold" action. Defaults to 250ms.
+- `post_wait_time`: Adds an additional delay after releasing a tap-hold key to check if any keys pressed during the `hold_timeout` are released. This helps accommodate fast typing scenarios where some keys may not be fully released during a hold. Defaults to 50ms
+
+The following are the typical configurations:
+
+```toml
+[behavior]
+# Enable HRM 
+tap_hold = { enable_hrm = true, prior_idle_time = "120ms", hold_timeout = "250ms", post_wait_time = "50ms"}
+# Disable HRM, you can safely ignore any fields if you don't want to change them
+tap_hold = { enable_hrm = false, hold_timeout = "200ms" }
+```
+
 #### One Shot
 
 In the `one_shot` sub-table you can define how long OSM or OSL will wait before releasing the modifier/layer with the `timeout` option, default is one second.
diff --git a/examples/use_config/nrf52840_ble_split/keyboard.toml b/examples/use_config/nrf52840_ble_split/keyboard.toml
index f2d0c77e..47dc633c 100644
--- a/examples/use_config/nrf52840_ble_split/keyboard.toml
+++ b/examples/use_config/nrf52840_ble_split/keyboard.toml
@@ -32,6 +32,9 @@ keymap = [
 [ble]
 enabled = true
 
+[behavior]
+tap_hold = { enable_hrm = true, prior_idle_time = "120ms", hold_timeout = "250ms", post_wait_time = "50ms"}
+
 [split]
 connection = "ble"
 
diff --git a/rmk-macro/src/behavior.rs b/rmk-macro/src/behavior.rs
index ec49c629..2e9148ae 100644
--- a/rmk-macro/src/behavior.rs
+++ b/rmk-macro/src/behavior.rs
@@ -1,10 +1,9 @@
 //! Initialize behavior config boilerplate of RMK
 //!
 
-use quote::quote;
-
-use crate::config::{OneShotConfig, TriLayerConfig};
+use crate::config::{OneShotConfig, TapHoldConfig, TriLayerConfig};
 use crate::keyboard_config::KeyboardConfig;
+use quote::quote;
 
 fn expand_tri_layer(tri_layer: &Option<TriLayerConfig>) -> proc_macro2::TokenStream {
     match tri_layer {
@@ -39,13 +38,46 @@ fn expand_one_shot(one_shot: &Option<OneShotConfig>) -> proc_macro2::TokenStream
     }
 }
 
+fn expand_tap_hold(tap_hold: &Option<TapHoldConfig>) -> proc_macro2::TokenStream {
+    let default = quote! {::rmk::config::TapHoldConfig::default()};
+    match tap_hold {
+        Some(tap_hold) => {
+            let enable_hrm = tap_hold.enable_hrm.unwrap_or_default();
+            let prior_idle_time = match &tap_hold.prior_idle_time {
+                Some(t) => t.0,
+                None => 120,
+            };
+            let post_wait_time = match &tap_hold.post_wait_time {
+                Some(t) => t.0,
+                None => 50,
+            };
+            let hold_timeout = match &tap_hold.hold_timeout {
+                Some(t) => t.0,
+                None => 250,
+            };
+
+            quote! {
+                ::rmk::config::TapHoldConfig {
+                    enable_hrm: #enable_hrm,
+                    prior_idle_time: ::embassy_time::Duration::from_millis(#prior_idle_time),
+                    post_wait_time: ::embassy_time::Duration::from_millis(#post_wait_time),
+                    hold_timeout: ::embassy_time::Duration::from_millis(#hold_timeout),
+                }
+            }
+        }
+        None => default,
+    }
+}
+
 pub(crate) fn expand_behavior_config(keyboard_config: &KeyboardConfig) -> proc_macro2::TokenStream {
     let tri_layer = expand_tri_layer(&keyboard_config.behavior.tri_layer);
+    let tap_hold = expand_tap_hold(&keyboard_config.behavior.tap_hold);
     let one_shot = expand_one_shot(&keyboard_config.behavior.one_shot);
 
     quote! {
         let behavior_config = ::rmk::config::BehaviorConfig {
             tri_layer: #tri_layer,
+            tap_hold: #tap_hold,
             one_shot: #one_shot,
         };
     }
diff --git a/rmk-macro/src/config/mod.rs b/rmk-macro/src/config/mod.rs
index ffc03c30..4be0dc62 100644
--- a/rmk-macro/src/config/mod.rs
+++ b/rmk-macro/src/config/mod.rs
@@ -133,9 +133,19 @@ pub struct LayoutConfig {
 #[derive(Clone, Debug, Default, Deserialize)]
 pub struct BehaviorConfig {
     pub tri_layer: Option<TriLayerConfig>,
+    pub tap_hold: Option<TapHoldConfig>,
     pub one_shot: Option<OneShotConfig>,
 }
 
+/// Configurations for tap hold
+#[derive(Clone, Debug, Deserialize)]
+pub struct TapHoldConfig {
+    pub enable_hrm: Option<bool>,
+    pub prior_idle_time: Option<DurationMillis>,
+    pub post_wait_time: Option<DurationMillis>,
+    pub hold_timeout: Option<DurationMillis>,
+}
+
 /// Configurations for tri layer
 #[derive(Clone, Debug, Deserialize)]
 pub struct TriLayerConfig {
diff --git a/rmk-macro/src/keyboard_config.rs b/rmk-macro/src/keyboard_config.rs
index c3503c34..1861f7ae 100644
--- a/rmk-macro/src/keyboard_config.rs
+++ b/rmk-macro/src/keyboard_config.rs
@@ -71,7 +71,7 @@ pub(crate) struct KeyboardConfig {
     pub(crate) board: BoardConfig,
     // Layout config
     pub(crate) layout: LayoutConfig,
-    // Begavior Config
+    // Behavior Config
     pub(crate) behavior: BehaviorConfig,
     // Light config
     pub(crate) light: LightConfig,
@@ -339,7 +339,9 @@ impl KeyboardConfig {
         split: Option<SplitConfig>,
     ) -> Result<BoardConfig, TokenStream2> {
         match (matrix, split) {
-            (None, Some(s)) => Ok(BoardConfig::Split(s)),
+            (None, Some(s)) => {
+                Ok(BoardConfig::Split(s))
+            },
             (Some(m), None) => {
                 match m.matrix_type {
                     MatrixType::normal => {
@@ -438,6 +440,7 @@ impl KeyboardConfig {
                     None => default.tri_layer,
                 };
 
+                behavior.tap_hold = behavior.tap_hold.or(default.tap_hold);
                 behavior.one_shot = behavior.one_shot.or(default.one_shot);
 
                 Ok(behavior)
diff --git a/rmk/CHANGELOG.md b/rmk/CHANGELOG.md
index 8f13c763..9bb1db96 100644
--- a/rmk/CHANGELOG.md
+++ b/rmk/CHANGELOG.md
@@ -15,7 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
-- Support `direct_pin` type matrix for split configurations
+- Support home row mod(HRM) mode with improved tap hold processing
+- BREAKING: Support `direct_pin` type matrix for split configurations, move pin config to [split.central/peripheral.matrix]
 - Add `clear_storage` option
 
 ### Changed
diff --git a/rmk/src/config/mod.rs b/rmk/src/config/mod.rs
index 03147bf3..56259c2f 100644
--- a/rmk/src/config/mod.rs
+++ b/rmk/src/config/mod.rs
@@ -44,9 +44,29 @@ impl<'a, O: OutputPin> Default for RmkConfig<'a, O> {
 #[derive(Default)]
 pub struct BehaviorConfig {
     pub tri_layer: Option<[u8; 3]>,
+    pub tap_hold: TapHoldConfig,
     pub one_shot: OneShotConfig,
 }
 
+/// Configurations for tap hold behavior
+pub struct TapHoldConfig {
+    pub enable_hrm: bool,
+    pub prior_idle_time: Duration,
+    pub post_wait_time: Duration,
+    pub hold_timeout: Duration,
+}
+
+impl Default for TapHoldConfig {
+    fn default() -> Self {
+        Self {
+            enable_hrm: false,
+            prior_idle_time: Duration::from_millis(120),
+            post_wait_time: Duration::from_millis(50),
+            hold_timeout: Duration::from_millis(250),
+        }
+    }
+}
+
 /// Config for one shot behavior
 pub struct OneShotConfig {
     pub timeout: Duration,
diff --git a/rmk/src/keyboard.rs b/rmk/src/keyboard.rs
index 0dbc049d..a29de15e 100644
--- a/rmk/src/keyboard.rs
+++ b/rmk/src/keyboard.rs
@@ -9,7 +9,7 @@ use crate::{
     KEYBOARD_STATE,
 };
 use core::cell::RefCell;
-use defmt::{debug, error, warn, Format};
+use defmt::{debug, error, info, warn, Format};
 use embassy_futures::{select::select, yield_now};
 use embassy_sync::{
     blocking_mutex::raw::CriticalSectionRawMutex,
@@ -132,6 +132,12 @@ pub(crate) struct Keyboard<'a, const ROW: usize, const COL: usize, const NUM_LAY
     /// Timer which records the timestamp of key changes
     pub(crate) timer: [[Option<Instant>; ROW]; COL],
 
+    /// Record the timestamp of last release, (event, is_modifier, timestamp)
+    last_release: (KeyEvent, bool, Option<Instant>),
+
+    /// Record whether the keyboard is in hold-after-tap state
+    hold_after_tap: (KeyEvent, bool),
+
     /// Options for configurable action behavior
     behavior: BehaviorConfig,
 
@@ -173,6 +179,23 @@ impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
             keymap,
             sender,
             timer: [[None; ROW]; COL],
+            last_release: (
+                KeyEvent {
+                    row: 0,
+                    col: 0,
+                    pressed: false,
+                },
+                false,
+                None,
+            ),
+            hold_after_tap: (
+                KeyEvent {
+                    row: 0,
+                    col: 0,
+                    pressed: false,
+                },
+                false,
+            ),
             behavior,
             osm_state: OneShotState::default(),
             osl_state: OneShotState::default(),
@@ -255,9 +278,8 @@ impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
                     break;
                 }
                 // Process unprocessed events
-                if let Some(e) = self.unprocessed_events.pop() {
-                    self.process_key_change(e).await;
-                }
+                let e = self.unprocessed_events.remove(0);
+                self.process_key_change(e).await;
             }
         }
     }
@@ -301,6 +323,19 @@ impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
             }
         }
 
+        // Record release of current key, which will be used in tap/hold processing
+        if !key_event.pressed {
+            // Check key release only
+            let mut is_mod = false;
+            if let KeyAction::Single(Action::Key(k)) = action {
+                if k.is_modifier() {
+                    is_mod = true;
+                }
+            }
+            // Record the last release event
+            self.last_release = (key_event, is_mod, Some(Instant::now()));
+        }
+
         // Tri Layer
         if let Some(ref tri_layer) = self.behavior.tri_layer {
             self.keymap.borrow_mut().update_tri_layer(tri_layer);
@@ -422,31 +457,78 @@ impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
 
             key_event.pressed = false;
             self.process_key_action_normal(action, key_event).await;
+
+            // Record the release event
+            let mut is_mod = false;
+            if let Action::Key(k) = action {
+                if k.is_modifier() {
+                    is_mod = true;
+                }
+            }
+            self.last_release = (key_event, is_mod, Some(Instant::now()));
         }
     }
 
-    /// Process tap/hold action.
+    /// Process tap/hold action for home row mods(HRM)
     ///
-    /// This function will wait until timeout or a new key event comes:
-    /// - timeout: trigger hold action
-    /// - new key event: means there's another key within the threshold
-    ///     - if it's the same key, trigger tap action
-    ///     - if it's another key, trigger hold action + that key
+    /// For HRMs, the "tap" action actually has higher priority, especially when typing fast.
     ///
-    /// This behavior is same as "Hold On Other Key Press" in qmk or "hold-preferred" in zmk
-    ///     
-    /// TODO: make tap/hold threshold customizable
+    /// There are only several cases that we should trigger "hold":
+    ///
+    /// - When another key is pressed and released within the tapping-term, or released at approximately the same time with the tap/hold key
+    /// - When the holding threshold is expired(a relatively longer holding threshold should be set)
+    /// - When mouse keys are triggered
+    ///
+    /// Furthermore, the "tap" action can be resolved immediately in the following cases, to increase the speed:
+    /// - the key is in the "key streak", similar with setting `require-prior-idle-ms` in zmk. The previous key should be non-modifier.
+    /// - the next key is on the same side of the keyboard
+    ///
+    /// When do we make the decision of tap/hold?
+    /// - When the key is pressed("key streak", or position based tap/hold)
+    /// - When the next key is releasing
+    /// - When current tap/hold key is releasing
+    /// - When tap/hold key is expired
     async fn process_key_action_tap_hold(
         &mut self,
         tap_action: Action,
         hold_action: Action,
         key_event: KeyEvent,
     ) {
+        if self.behavior.tap_hold.enable_hrm {
+            // If HRM is enabled, check whether it's a different key is in key streak
+            if let Some(last_release_time) = self.last_release.2 {
+                if key_event.pressed {
+                    if last_release_time.elapsed() < self.behavior.tap_hold.prior_idle_time
+                        && !(key_event.row == self.last_release.0.row
+                            && key_event.col == self.last_release.0.col)
+                    {
+                        // The previous key is a different key and released within `prior_idle_time`, it's in key streak
+                        debug!("Key streak detected, trigger tap action");
+                        self.process_key_action_tap(tap_action, key_event).await;
+                        self.hold_after_tap = (key_event, false);
+                        return;
+                    } else if last_release_time.elapsed() < self.behavior.tap_hold.hold_timeout
+                        && key_event.row == self.last_release.0.row
+                        && key_event.col == self.last_release.0.col
+                    {
+                        // Pressed a same key after tapped it within `hold_timeout`
+                        // Trigger the tap action just as it's pressed
+                        self.process_key_action_normal(tap_action, key_event).await;
+                        self.hold_after_tap = (key_event, true);
+                        return;
+                    }
+                }
+            }
+        }
+
         let row = key_event.row as usize;
         let col = key_event.col as usize;
         if key_event.pressed {
+            // Press
             self.timer[col][row] = Some(Instant::now());
-            let hold_timeout = embassy_time::Timer::after_millis(200);
+
+            let hold_timeout =
+                embassy_time::Timer::after_millis(self.behavior.tap_hold.hold_timeout.as_millis());
             match select(hold_timeout, key_event_channel.receive()).await {
                 embassy_futures::select::Either::First(_) => {
                     // Timeout, trigger hold
@@ -455,7 +537,7 @@ impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
                 }
                 embassy_futures::select::Either::Second(e) => {
                     if e.row == key_event.row && e.col == key_event.col {
-                        // If it's same key event and releasing within 200ms, trigger tap
+                        // If it's same key event and releasing within `hold_timeout`, trigger tap
                         if !e.pressed {
                             let elapsed = self.timer[col][row].unwrap().elapsed().as_millis();
                             debug!("TAP action: {}, time elapsed: {}ms", tap_action, elapsed);
@@ -465,25 +547,82 @@ impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
                             self.timer[col][row] = None;
                         }
                     } else {
-                        // A different key comes within the threshold, trigger hold + that key
+                        // A different key comes
+                        // If it's a release event, the key is pressed BEFORE tap/hold key, so it should be regarded as a normal key
+                        self.unprocessed_events.push(e).ok();
+                        if !e.pressed {
+                            // we push the current tap/hold event again, the loop will process the release first, then re-process current tap/hold
+                            self.unprocessed_events.push(key_event).ok();
+                            return;
+                        }
+
+                        // Wait for key release, record all pressed keys during this
+                        loop {
+                            let next_key_event = key_event_channel.receive().await;
+                            self.unprocessed_events.push(next_key_event).ok();
+                            if !next_key_event.pressed {
+                                break;
+                            }
+                        }
 
-                        // Process hold action first
+                        // Process hold action
                         self.process_key_action_normal(hold_action, key_event).await;
 
-                        // The actual processing is postponed because we cannot do recursion on async function without alloc
-                        // After current key processing is done, we can process events in queue until the queue is empty
-                        if self.unprocessed_events.push(e).is_err() {
-                            warn!("unprocessed event queue is full, dropping event");
-                        }
+                        // All other unprocessed events will be processed later
                     }
                 }
             }
         } else {
+            // Release
+            if self.hold_after_tap.1 {
+                // Releasing a key in hold after tap state, check position
+                if key_event.row == self.hold_after_tap.0.row
+                    && key_event.col == self.hold_after_tap.0.col
+                {
+                    // Release the hold after tap key
+                    info!("Hold after tap, hold the tap: {} {}", tap_action, key_event);
+                    self.process_key_action_normal(tap_action, key_event).await;
+                    self.hold_after_tap = (key_event, false);
+                    return;
+                }
+            }
             if let Some(_) = self.timer[col][row] {
-                // Release hold action, then clear timer
+                // Release hold action, wait for `post_wait_time`, then clear timer
+                debug!(
+                    "HOLD releasing: {}, {}, wait for `post_wait_time` for new releases",
+                    hold_action, key_event.pressed
+                );
+                let wait_release = async {
+                    loop {
+                        let next_key_event = key_event_channel.receive().await;
+                        if !next_key_event.pressed {
+                            self.unprocessed_events.push(next_key_event).ok();
+                        } else {
+                            break next_key_event;
+                        }
+                    }
+                };
+
+                let wait_timeout = embassy_time::Timer::after_millis(
+                    self.behavior.tap_hold.post_wait_time.as_millis(),
+                );
+                match select(wait_timeout, wait_release).await {
+                    embassy_futures::select::Either::First(_) => {
+                        // Wait timeout, release the hold key finally
+                        self.process_key_action_normal(hold_action, key_event).await;
+                    }
+                    embassy_futures::select::Either::Second(next_press) => {
+                        // Next press event comes, add hold release to unprocessed list first, then add next press
+                        self.unprocessed_events.push(key_event).ok();
+                        self.unprocessed_events.push(next_press).ok();
+                    }
+                };
+                // Clear timer
+                self.timer[col][row] = None;
+            } else {
+                // The timer has been reset, fire hold release event
                 debug!("HOLD releasing: {}, {}", hold_action, key_event.pressed);
                 self.process_key_action_normal(hold_action, key_event).await;
-                self.timer[col][row] = None;
             }
         }
     }
diff --git a/rmk/src/matrix.rs b/rmk/src/matrix.rs
index 440a193e..d4b0f78d 100644
--- a/rmk/src/matrix.rs
+++ b/rmk/src/matrix.rs
@@ -6,10 +6,7 @@ use defmt::{error, Format};
 use embassy_time::{Instant, Timer};
 use embedded_hal::digital::{InputPin, OutputPin};
 #[cfg(feature = "async_matrix")]
-use {
-    defmt::info, embassy_futures::select::select_slice, embedded_hal_async::digital::Wait,
-    heapless::Vec,
-};
+use {embassy_futures::select::select_slice, embedded_hal_async::digital::Wait, heapless::Vec};
 
 /// MatrixTrait is the trait for keyboard matrix.
 ///
@@ -150,7 +147,6 @@ impl<
             out.set_high().ok();
         }
         Timer::after_micros(1).await;
-        info!("Waiting for high");
         let mut futs: Vec<_, INPUT_PIN_NUM> = self
             .input_pins
             .iter_mut()


Test patch: