From b3d0eaca3774f5346f84248e4a906dfa72e1c9da Mon Sep 17 00:00:00 2001 From: Hao Ye Date: Wed, 31 Dec 2025 00:14:34 -0800 Subject: [PATCH 1/8] Add option to use grab-and-move action for scrolling --- src/APILayer/APILayer.cpp | 6 ++---- src/APILayer/HandTrackingSource.cpp | 32 +++++++++++++++++++++++++++++ src/APILayer/HandTrackingSource.h | 4 ++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/APILayer/APILayer.cpp b/src/APILayer/APILayer.cpp index 801f9d4..046a361 100644 --- a/src/APILayer/APILayer.cpp +++ b/src/APILayer/APILayer.cpp @@ -345,10 +345,8 @@ XrResult APILayer::xrWaitFrame( rightHand.mActions.mPrimary = r.mActions.mPrimary; rightHand.mActions.mSecondary = r.mActions.mSecondary; } - if (Config::PinchToScroll) { - leftHand.mActions.mValueChange = l.mActions.mValueChange; - rightHand.mActions.mValueChange = r.mActions.mValueChange; - } + leftHand.mActions.mValueChange = l.mActions.mValueChange; + rightHand.mActions.mValueChange = r.mActions.mValueChange; } if (mPointCtrl) { diff --git a/src/APILayer/HandTrackingSource.cpp b/src/APILayer/HandTrackingSource.cpp index 3add0ba..e57c9e3 100644 --- a/src/APILayer/HandTrackingSource.cpp +++ b/src/APILayer/HandTrackingSource.cpp @@ -345,6 +345,38 @@ void HandTrackingSource::UpdateHand(const FrameInfo& frameInfo, Hand* hand) { state.mPose = {}; break; } + + // --- Grab-and-Move Scroll Gesture --- + if (!Config::PinchToScroll) { + bool pinchActive = state.mActions.mPrimary; + + if (pinchActive && state.mPose) { + float currentY = state.mPose->position.y; + + if (!hand->mScrolling) { + // Start scroll mode + hand->mScrolling = true; + hand->mLastScrollY = currentY; + } else { + float delta = currentY - hand->mLastScrollY; + + // Threshold to avoid jitter + constexpr float scrollThreshold = 0.01f; + + if (delta > scrollThreshold) { + state.mActions.mValueChange = ActionState::ValueChange::Increase; + hand->mLastScrollY = currentY; + } else if (delta < -scrollThreshold) { + state.mActions.mValueChange = ActionState::ValueChange::Decrease; + hand->mLastScrollY = currentY; + } + } + } else { + // Pinch released → stop scrolling + hand->mScrolling = false; + hand->mLastScrollY = 0.0f; + } + } } void HandTrackingSource::InitHandTracker(Hand* hand) { diff --git a/src/APILayer/HandTrackingSource.h b/src/APILayer/HandTrackingSource.h index 77df03d..b71d463 100644 --- a/src/APILayer/HandTrackingSource.h +++ b/src/APILayer/HandTrackingSource.h @@ -45,6 +45,10 @@ class HandTrackingSource final : public InputSource { ActionState mRawActions {}; XrTime mRawActionsSince {}; + + // --- Added for grab‑and‑move scroll --- + float mLastScrollY = 0.0f; + bool mScrolling = false; }; bool mHibernating {false}; From 8014fc58ff1c133d66f3277087df5ee1a28ae7b4 Mon Sep 17 00:00:00 2001 From: Hao Ye Date: Sun, 25 Jan 2026 21:20:05 -0800 Subject: [PATCH 2/8] src/SettingsApp/HTCCSettingsApp.cpp --- reg/MSFS.reg | Bin 638 -> 343 bytes src/APILayer/HandTrackingSource.cpp | 56 ++++++++++++++-------------- src/SettingsApp/HTCCSettingsApp.cpp | 10 ++++- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/reg/MSFS.reg b/reg/MSFS.reg index 66e277b8a4a7d1edcb9f81eeb5a1d6817f84f375..a7a7b89ef1ef91cb2b7d366cd2ad72f568265069 100644 GIT binary patch literal 343 zcmZ|J!AiqG5C-7;KE;v;D5>D3;9&_HBW;W%p+$_8ZFY!5HalUmw)*y__8_!4*ZKJW ze}~@EX_6&$I0gQe+F~G!vEwdZ$X=qzqQ- zVW1G2%y?U~X?w5 Qn?p`VsOe&_GL}O80F2XdZvX%Q literal 638 zcmb7?-Acni5QWdR;5(E)KvBUP!HW_M6|G8BT12JPro>>Ilufky^6EE}h&LjYWivZ- z=I5LtpC41@DwL|!R+Ty$X{n4@rCLGnM9x5Syb`~o33vd_@eBN8^>o15*Ph=y4Rk@x zt!BE^wIcPgGYzp3*txDi@0EbX8f!{?=`HK$}|P3AyzXE+9*YoTY)ioor? zac+@TSo2LyMpYkV!;Dww8;Ga8Dv;Q7GdiNk$J1IN7ftn!PRu(Qh+^@Vcfbp z<-7G=q&jisroK6cZOsk1@u;Z@KBIuyZnYD(VcM)ZU{^jlpl3m(*}Ac|jzgH4%0}~U b{eL#~TgSh99JTrqxO{W_f0t%U_P=}q1H5TA diff --git a/src/APILayer/HandTrackingSource.cpp b/src/APILayer/HandTrackingSource.cpp index e57c9e3..678ba5b 100644 --- a/src/APILayer/HandTrackingSource.cpp +++ b/src/APILayer/HandTrackingSource.cpp @@ -346,36 +346,36 @@ void HandTrackingSource::UpdateHand(const FrameInfo& frameInfo, Hand* hand) { break; } - // --- Grab-and-Move Scroll Gesture --- - if (!Config::PinchToScroll) { - bool pinchActive = state.mActions.mPrimary; - - if (pinchActive && state.mPose) { - float currentY = state.mPose->position.y; - - if (!hand->mScrolling) { - // Start scroll mode - hand->mScrolling = true; - hand->mLastScrollY = currentY; - } else { - float delta = currentY - hand->mLastScrollY; - - // Threshold to avoid jitter - constexpr float scrollThreshold = 0.01f; - - if (delta > scrollThreshold) { - state.mActions.mValueChange = ActionState::ValueChange::Increase; - hand->mLastScrollY = currentY; - } else if (delta < -scrollThreshold) { - state.mActions.mValueChange = ActionState::ValueChange::Decrease; - hand->mLastScrollY = currentY; - } - } +// --- Grab-and-Move Scroll Gesture --- + if (Config::GrabMoveToScroll) { + bool pinchActive = state.mActions.mPrimary; + + if (pinchActive && state.mPose) { + float currentY = state.mPose->position.y; + + if (!hand->mScrolling) { + // Start scroll mode + hand->mScrolling = true; + hand->mLastScrollY = currentY; } else { - // Pinch released → stop scrolling - hand->mScrolling = false; - hand->mLastScrollY = 0.0f; + float delta = currentY - hand->mLastScrollY; + + // Threshold to avoid jitter + constexpr float scrollThreshold = 0.01f; + + if (delta > scrollThreshold) { + state.mActions.mValueChange = ActionState::ValueChange::Increase; + hand->mLastScrollY = currentY; + } else if (delta < -scrollThreshold) { + state.mActions.mValueChange = ActionState::ValueChange::Decrease; + hand->mLastScrollY = currentY; + } } + } else { + // Pinch released → stop scrolling + hand->mScrolling = false; + hand->mLastScrollY = 0.0f; + } } } diff --git a/src/SettingsApp/HTCCSettingsApp.cpp b/src/SettingsApp/HTCCSettingsApp.cpp index 8889454..9c57879 100644 --- a/src/SettingsApp/HTCCSettingsApp.cpp +++ b/src/SettingsApp/HTCCSettingsApp.cpp @@ -270,9 +270,15 @@ static void OpenXRGUI() { HTCC::Config::SavePinchToClick(); } - if (ToggleSwitch(&HTCC::Config::PinchToScroll).Caption("Pinch to scroll")) { + if (ToggleSwitch(&HTCC::Config::PinchToScroll) + .Caption("Pinch to scroll")) { HTCC::Config::SavePinchToScroll(); } + + if (ToggleSwitch(&HTCC::Config::GrabMoveToScroll) + .Caption("Grab and move to scroll")) { + HTCC::Config::SaveGrabMoveToScroll(); + } } EndVStackPanel(); @@ -469,4 +475,4 @@ int WINAPI wWinMain( }, }, }); -} \ No newline at end of file +} From a0da1d556026930e87aaa345b740601717273c8a Mon Sep 17 00:00:00 2001 From: Hao Ye Date: Sun, 25 Jan 2026 23:31:58 -0800 Subject: [PATCH 3/8] Add GrabMoveToScroll configuration option - Add new boolean config: GrabMoveToScroll (default: false) - Allows enabling/disabling grab move to scroll feature - Part of grab_move_scroll feature branch Co-Authored-By: Claude Sonnet 4.5 --- src/lib/Config.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/Config.h b/src/lib/Config.h index fe6d4b2..dd723cc 100644 --- a/src/lib/Config.h +++ b/src/lib/Config.h @@ -66,6 +66,7 @@ enum class HandTrackingHands : DWORD { IT(XrHandJointEXT, HandTrackingAimJoint, XR_HAND_JOINT_INDEX_PROXIMAL_EXT) \ IT(bool, PinchToClick, true) \ IT(bool, PinchToScroll, true) \ + IT(bool, GrabMoveToScroll, false) \ IT(uint16_t, ShortPressLongPressMilliseconds, 200) \ IT(uint16_t, ScrollWheelDelayMilliseconds, 600) \ IT(uint16_t, ScrollWheelIntervalMilliseconds, 50) \ From aebb6eee9fc0e02581aea55af94c6927283da8ff Mon Sep 17 00:00:00 2001 From: Hao Ye Date: Mon, 26 Jan 2026 10:26:45 -0800 Subject: [PATCH 4/8] Add GrabMoveScrollScale configuration for scroll sensitivity Adds a configurable scale factor for grab-move-to-scroll gesture. Default is 1.0, DCS configured to 4.0 via registry override. --- reg/DCS.reg | Bin 568 -> 608 bytes src/APILayer/HandTrackingSource.cpp | 3 +++ src/lib/Config.h | 1 + 3 files changed, 4 insertions(+) diff --git a/reg/DCS.reg b/reg/DCS.reg index 464eed489ddf29de1c1f067e66916c0d5b6085c8..8aafee2843550b20873b84d9db7e324c2155de56 100644 GIT binary patch delta 94 zcmdnN@_=Q67bDk1|E-hR7>y=dFFw*j!|#&J;oqAE(RqAcZMQ{M1~{=Uxs{! nGKN%!V1{I%NIpXj5Q5l=Ksptu&K8JG81xtnfHW5aFAxI&hocjX delta 53 zcmaFBvV&!U7b`CV7X$A^@2!ly6aO1C@=i8nv<8ydjOIXcA)`K!JkJ=+0agW62>^3J B3&a2b diff --git a/src/APILayer/HandTrackingSource.cpp b/src/APILayer/HandTrackingSource.cpp index 678ba5b..53c2c3f 100644 --- a/src/APILayer/HandTrackingSource.cpp +++ b/src/APILayer/HandTrackingSource.cpp @@ -360,6 +360,9 @@ void HandTrackingSource::UpdateHand(const FrameInfo& frameInfo, Hand* hand) { } else { float delta = currentY - hand->mLastScrollY; + // Apply grab-and-move scroll scale for different applications + delta *= Config::GrabMoveScrollScale; + // Threshold to avoid jitter constexpr float scrollThreshold = 0.01f; diff --git a/src/lib/Config.h b/src/lib/Config.h index dd723cc..08872ae 100644 --- a/src/lib/Config.h +++ b/src/lib/Config.h @@ -151,6 +151,7 @@ enum class HandTrackingHands : DWORD { IT(HandTrackingActionHFOV, std::numbers::pi_v / 2) \ IT(HandTrackingHibernateCutoff, std::numbers::pi_v / 8) \ IT(SmoothingFactor, 1.0f) \ + IT(GrabMoveScrollScale, 1.0f) \ IT(LeftEyeFOVLeft, 0.0f) \ IT(LeftEyeFOVRight, 0.0f) \ IT(LeftEyeFOVUp, 0.0f) \ From 68cc3b58d2274730e37ba748c9744b17a785698a Mon Sep 17 00:00:00 2001 From: Hao Ye Date: Mon, 26 Jan 2026 23:29:26 -0800 Subject: [PATCH 5/8] Make MSFS.reg and DCS.reg to be text --- reg/DCS.reg | Bin 608 -> 626 bytes reg/MSFS.reg | Bin 343 -> 708 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/reg/DCS.reg b/reg/DCS.reg index 8aafee2843550b20873b84d9db7e324c2155de56..312271459ebb66b8a7f5e0033bf297f0d49900ae 100644 GIT binary patch delta 59 zcmaFB@`+`F7b`CV7X$A^@2!ly6aO1C@=i8nv<8ydjOIXcA)`K!JkJ;mBo&z~IlyXx F>H*Q04Eq28 delta 42 ycmeyw@_=Q67bDk1|E-hR7>y=dFFw*j!|#&J;tEPMoboLTnxMnTnqp${R_hY diff --git a/reg/MSFS.reg b/reg/MSFS.reg index a7a7b89ef1ef91cb2b7d366cd2ad72f568265069..3d16d668b22508a6a9893d4d0f4887c0ff0b3b49 100644 GIT binary patch literal 708 zcmb7?UrPc}5XH}P(033&fK<>+(1R?QQC8@pgoUiGY?AKE)v~^P^_$t1dI&KtyR$QA z{+u&+zdi?=DOajO8?97RS5sxgR$43QgUAKw46neisSh4NXZSh(sTw-s9O}TIq1w8p z=0PLf=uVNE*pWKe2<%F?ppWW-wG?YWyz7w`eW%Q_i3?7_%r%*9CC)GgPc+esXGP%l zy>xDo=2-JhO-5A{WXX&-=NpKpe62uQo}1~J?mglQy%I?(g$=Uad7fNN9WFW8m<;1q z*E#Qv?;_QiD>wD!Ij(B%!Hvh7n&2~XnC(~FXB(!?ssncJlLLAtM9QrjYwOs7nW-!_ s@7MolQ-5{b-Q%Rv*MrNJ+yA?4w{+?EIYvd{w-jT4cizpV>|%f2H=?w2?f?J) literal 343 zcmZ|J!AiqG5C-7;KE;v;D5>D3;9&_HBW;W%p+$_8ZFY!5HalUmw)*y__8_!4*ZKJW ze}~@EX_6&$I0gQe+F~G!vEwdZ$X=qzqQ- zVW1G2%y?U~X?w5 Qn?p`VsOe&_GL}O80F2XdZvX%Q From c71fb11e61ab70fd6efe31a72a1125477f3cd602 Mon Sep 17 00:00:00 2001 From: Hao Ye Date: Mon, 26 Jan 2026 23:36:14 -0800 Subject: [PATCH 6/8] Update DCS.reg with UTF-8 coding --- reg/DCS.reg | Bin 626 -> 315 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/reg/DCS.reg b/reg/DCS.reg index 312271459ebb66b8a7f5e0033bf297f0d49900ae..2e63add606c399d1b22e1b26a11dafdd6b5cbb1b 100644 GIT binary patch literal 315 zcmZvXK~BRk5JmTqa)&4fhysEQQWufyHi#CZq-s@RRJr!FGI8xNHZA39EI1NZz)_bl z%lZGm`SbJjz0y{>lg}F1Yaj5OQKf^!YzgSKv)OG?mT8hE&-DXcZ)S{Cv(3CB_5CB| zEt@P>RYUm%L1pxCa3SzoShYYQJ5a>QuGfJWEjw-Zyy|;)1VpXC^D$`)_<)x*$*C1v z11cY>lS6e^Ugjz)%)-E9)|~%QpU}O646)`ob{AGcKFZ7DI*-X^6F&%A3d43g4`EO) od*WF_d1ANBdpW9Ou+qpYJD+hNQNdx|-7%*w_3x1Min*aa+ literal 626 zcma))QBQ+F5QOL1#Q)In0~$5@V&X$Xtuu3+(O9 z&F;*?_t#XZa>XjNQl*XpC0cM+saB~EPL4rSvVy#$EASFDCC|wZ)YT4aTU+kA(NJe_ zt~JxS#`4u8&NL$WV5hnOy;TS{&_q+t19vi@ZbC2X_{=KkS#xu!xl=d+pDWUnyXAx1 ze(ALN%7~Vmn+2>M$dVp!PH)M1%&r3Iqa#FhqIaG7^$!4K}qWiJ2# From bc5874007d9f0fbb7fc2f988a8734c64e15eeecf Mon Sep 17 00:00:00 2001 From: Hao Ye Date: Mon, 26 Jan 2026 23:37:41 -0800 Subject: [PATCH 7/8] Update MSFS.reg transcode it to UTF-8 --- reg/MSFS.reg | Bin 708 -> 356 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/reg/MSFS.reg b/reg/MSFS.reg index 3d16d668b22508a6a9893d4d0f4887c0ff0b3b49..0e36d96b2b5aa260c8a0b961253b435acc9318e5 100644 GIT binary patch literal 356 zcmZ|J!AiqG5C-6TAny?R03`}u1P>u>jMx}SLW{Ukw%H*L+3bYL+Um1;@R584H})Wu zI@kI5|Nr~*YlBwPVTvl~aB}<+35||8S_4l=Ho98IaTtWbbAB(LwyQEtR@)*;^ScKj z>oRLLNhM^)pd&F1Yqb5D@48CKIwoU)lh*m^Gc43N`sOqKchny| c5tqKNZs)Xr$vo+n)vlmDG<3u#DV~JE59!QwS^xk5 literal 708 zcmb7?UrPc}5XH}P(033&fK<>+(1R?QQC8@pgoUiGY?AKE)v~^P^_$t1dI&KtyR$QA z{+u&+zdi?=DOajO8?97RS5sxgR$43QgUAKw46neisSh4NXZSh(sTw-s9O}TIq1w8p z=0PLf=uVNE*pWKe2<%F?ppWW-wG?YWyz7w`eW%Q_i3?7_%r%*9CC)GgPc+esXGP%l zy>xDo=2-JhO-5A{WXX&-=NpKpe62uQo}1~J?mglQy%I?(g$=Uad7fNN9WFW8m<;1q z*E#Qv?;_QiD>wD!Ij(B%!Hvh7n&2~XnC(~FXB(!?ssncJlLLAtM9QrjYwOs7nW-!_ s@7MolQ-5{b-Q%Rv*MrNJ+yA?4w{+?EIYvd{w-jT4cizpV>|%f2H=?w2?f?J) From 6d4823725b9dbef92b8c52d9516e409a3f3e0e93 Mon Sep 17 00:00:00 2001 From: Hao Ye Date: Wed, 28 Jan 2026 15:16:53 -0800 Subject: [PATCH 8/8] Remove GrabMoveScroll from reg file --- reg/DCS.reg | Bin 315 -> 622 bytes reg/MSFS.reg | Bin 356 -> 638 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/reg/DCS.reg b/reg/DCS.reg index 2e63add606c399d1b22e1b26a11dafdd6b5cbb1b..13577d7b761fd1d48f3fd7836341cc6983f2859e 100644 GIT binary patch literal 622 zcma))QBQ+F5QOL1#Q)In0~$5@V&X$Xtuu3+(O9 z&F;*?_t#XZa>XjNQl*XpC0cM+saB~EPL4rSvVy#$EASFDCC|wZ)YT4aTU+kA(NJe_ zt~JxS#`4u8&NL$WV5hnOy;TS{&_q+t19vi@ZbC2X_{=KkS#xu!xl=d+pDWUnyXAx1 ze(ALN%7~Vmn+2>M$dVp!PH)M1%&r3Iqa#FhqIaG7@-09q1d7ytkO literal 315 zcmZvXK~BRk5JmTqa)&4fhysEQQWufyHi#CZq-s@RRJr!FGI8xNHZA39EI1NZz)_bl z%lZGm`SbJjz0y{>lg}F1Yaj5OQKf^!YzgSKv)OG?mT8hE&-DXcZ)S{Cv(3CB_5CB| zEt@P>RYUm%L1pxCa3SzoShYYQJ5a>QuGfJWEjw-Zyy|;)1VpXC^D$`)_<)x*$*C1v z11cY>lS6e^Ugjz)%)-E9)|~%QpU}O646)`ob{AGcKFZ7DI*-X^6F&%A3d43g4`EO) od*WF_d1ANBdpW9Ou+qpYJD+hNQNdx|-7%*w_3x1Min*aa+ diff --git a/reg/MSFS.reg b/reg/MSFS.reg index 0e36d96b2b5aa260c8a0b961253b435acc9318e5..66e277b8a4a7d1edcb9f81eeb5a1d6817f84f375 100644 GIT binary patch literal 638 zcmb7?-Acni5QWdR;5(E)KvBUP!HW_M6|G8BT12JPro>>Ilufky^6EE}h&LjYWivZ- z=I5LtpC41@DwL|!R+Ty$X{n4@rCLGnM9x5Syb`~o33vd_@eBN8^>o15*Ph=y4Rk@x zt!BE^wIcPgGYzp3*txDi@0EbX8f!{?=`HK$}|P3AyzXE+9*YoTY)ioor? zac+@TSo2LyMpYkV!;Dww8;Ga8Dv;Q7GdiNk$J1IN7ftn!PRu(Qh+^@Vcfbp z<-7G=q&jisroK6cZOsk1@u;Z@KBIuyZnYD(VcM)ZU{^jlpl3m(*}Ac|jzgH4%0}~U b{eL#~TgSh99JTrqxO{W_f0t%U_P=}q1H5TA literal 356 zcmZ|J!AiqG5C-6TAny?R03`}u1P>u>jMx}SLW{Ukw%H*L+3bYL+Um1;@R584H})Wu zI@kI5|Nr~*YlBwPVTvl~aB}<+35||8S_4l=Ho98IaTtWbbAB(LwyQEtR@)*;^ScKj z>oRLLNhM^)pd&F1Yqb5D@48CKIwoU)lh*m^Gc43N`sOqKchny| c5tqKNZs)Xr$vo+n)vlmDG<3u#DV~JE59!QwS^xk5