Repository navigation
fix(gamescope): direct-touch drags work again in games that confine a hidden cursor #2220
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
maxjivi05
wants to merge
8
commits into
main
Choose a base branch
from
fix/direct-touch-drag
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+332
−1
Open
Changes from 6 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
4cd9415
gamescope: a confine is relative only while it holds the X pointer
maxjivi05 f3dea94
gamescope: a constraint without a hint is relative only while the gam…
maxjivi05 27d0c0e
gamescope: a pointer constraint is relative only while the game holds…
maxjivi05 de57e44
gamescope: a hinted lock starts relative and is released once the X p…
maxjivi05 60be5ba
gamescope: count away frames within one half-second window
maxjivi05 79240a8
gamescope: a hint that arrives with the game's first warp starts the …
maxjivi05 343acf1
fix(input): end the touch press when a program locks the pointer mid-…
maxjivi05 316355a
gamescope: a new focus window or constraint starts its own pointer hold
maxjivi05 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 161 additions & 0 deletions
161
tools/gamescope/patches/0133-constraint-relative-when-held.patch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp | ||
| --- a/src/steamcompmgr.cpp | ||
| +++ b/src/steamcompmgr.cpp | ||
| @@ -3165,6 +3165,116 @@ | ||
| wlserver_unlock(); | ||
| } | ||
|
|
||
| +struct PointerHold | ||
| +{ | ||
| + int nAwayFrames = 0; | ||
| + int nFreeFrames = 0; | ||
| + uint64_t ulFirstAway = 0; | ||
| + bool bHeld = false; | ||
| +}; | ||
| + | ||
| +static bool pointer_hold_step( PointerHold &state, bool bConstrained, double flSeatDX, double flSeatDY, double flPointerDX, double flPointerDY, uint64_t ulNow ) | ||
| +{ | ||
| + if ( !bConstrained ) | ||
| + { | ||
| + state = PointerHold{}; | ||
| + return false; | ||
| + } | ||
| + | ||
| + const double flSeat = std::max( std::abs( flSeatDX ), std::abs( flSeatDY ) ); | ||
| + if ( flSeat < 4.0 ) | ||
| + return state.bHeld; | ||
| + | ||
| + const double flMiss = std::max( std::abs( flSeatDX - flPointerDX ), std::abs( flSeatDY - flPointerDY ) ); | ||
| + if ( flMiss > flSeat * 0.75 ) | ||
| + { | ||
| + if ( state.nAwayFrames == 0 || ulNow - state.ulFirstAway > 500'000'000ul ) | ||
| + { | ||
| + state.nAwayFrames = 0; | ||
| + state.ulFirstAway = ulNow; | ||
| + } | ||
| + state.nFreeFrames = 0; | ||
| + if ( ++state.nAwayFrames >= 3 ) | ||
| + state.bHeld = true; | ||
| + } | ||
| + else if ( state.bHeld && ++state.nFreeFrames >= 30 ) | ||
| + { | ||
| + state = PointerHold{}; | ||
| + } | ||
| + | ||
| + return state.bHeld; | ||
| +} | ||
| + | ||
| +static bool s_bPointerHeld = false; | ||
| + | ||
| +static void update_pointer_hold( global_focus_t *pFocus, bool bConstrained, bool bHinted ) | ||
| +{ | ||
| + static PointerHold s_Hold; | ||
| + static bool s_bTracking = false; | ||
| + static double s_flSeatX = 0.0; | ||
| + static double s_flSeatY = 0.0; | ||
| + static int s_nRootX = 0; | ||
| + static int s_nRootY = 0; | ||
| + static bool s_bHinted = false; | ||
| + | ||
| + wlserver_lock(); | ||
| + const double flX = wlserver.mouse_surface_cursorx; | ||
| + const double flY = wlserver.mouse_surface_cursory; | ||
| + const double flSentX = wlserver.flSentDX; | ||
| + const double flSentY = wlserver.flSentDY; | ||
| + wlserver.flSentDX = 0.0; | ||
| + wlserver.flSentDY = 0.0; | ||
| + wlserver_unlock(); | ||
| + | ||
| + if ( !bConstrained || !pFocus->cursor ) | ||
| + { | ||
| + s_bTracking = false; | ||
| + s_bHinted = false; | ||
| + s_bPointerHeld = pointer_hold_step( s_Hold, false, 0.0, 0.0, 0.0, 0.0, 0 ); | ||
| + return; | ||
| + } | ||
| + | ||
| + if ( s_bTracking && bHinted && !s_bHinted ) | ||
| + { | ||
| + s_Hold = PointerHold{}; | ||
| + s_Hold.bHeld = true; | ||
| + s_bPointerHeld = true; | ||
| + } | ||
| + s_bHinted = bHinted; | ||
| + | ||
| + double flDX = flX - s_flSeatX; | ||
| + double flDY = flY - s_flSeatY; | ||
| + if ( flDX == 0.0 && flDY == 0.0 ) | ||
| + { | ||
| + flDX = flSentX; | ||
| + flDY = flSentY; | ||
| + } | ||
| + | ||
| + if ( s_bTracking && flDX == 0.0 && flDY == 0.0 ) | ||
| + return; | ||
| + | ||
| + int nRootX = 0; | ||
| + int nRootY = 0; | ||
| + pFocus->cursor->QueryRootPosition( &nRootX, &nRootY ); | ||
| + | ||
| + if ( s_bTracking ) | ||
| + { | ||
| + s_bPointerHeld = pointer_hold_step( s_Hold, true, flDX, flDY, nRootX - s_nRootX, nRootY - s_nRootY, get_time_in_nanos() ); | ||
| + } | ||
| + else | ||
| + { | ||
| + s_Hold = PointerHold{}; | ||
| + s_Hold.bHeld = bHinted; | ||
| + s_bPointerHeld = bHinted; | ||
| + } | ||
| + | ||
| + s_bTracking = true; | ||
| + s_flSeatX = flX; | ||
| + s_flSeatY = flY; | ||
| + s_nRootX = nRootX; | ||
| + s_nRootY = nRootY; | ||
| +} | ||
| + | ||
| bool ShouldDrawCursor() | ||
| { | ||
| if ( relative_mouse_forced() ) | ||
| @@ -11029,9 +11139,15 @@ | ||
| nRelativeMouse != 2 && !bExcludedAppId && !bHasPointerConstraint && !window_is_steam( pPaintFocus->inputFocusWindow ) ); | ||
| } | ||
|
|
||
| + if ( vblank ) | ||
| + update_pointer_hold( pPaintFocus, bHasPointerConstraint && bImageEmpty && nRelativeMouse != 2 && !bExcludedAppId, | ||
| + pPaintFocus->cursor && pPaintFocus->cursor->IsConstrained() ); | ||
| + | ||
| + const bool bGameHoldsPointer = bHasPointerConstraint && s_bPointerHeld; | ||
| + | ||
| const bool bRelativeMouseMode = nRelativeMouse == 2 | ||
| ? false | ||
| - : ( bForcedRelativeMouse || s_bFollowClientPointer || ( bImageEmpty && bHasPointerConstraint && !bExcludedAppId ) ); | ||
| + : ( bForcedRelativeMouse || s_bFollowClientPointer || ( bImageEmpty && bGameHoldsPointer && !bExcludedAppId ) ); | ||
|
|
||
| static bool s_bRelativeMouseMode = false; | ||
| if ( s_bRelativeMouseMode && !bRelativeMouseMode && pPaintFocus->cursor && | ||
| diff --git a/src/wlserver.cpp b/src/wlserver.cpp | ||
| --- a/src/wlserver.cpp | ||
| +++ b/src/wlserver.cpp | ||
| @@ -346,6 +346,8 @@ | ||
| { | ||
| assert( wlserver_is_lock_held() ); | ||
|
|
||
| + wlserver.flSentDX += unaccel_dx; | ||
| + wlserver.flSentDY += unaccel_dy; | ||
| wlr_relative_pointer_manager_v1_send_relative_motion( wlserver.relative_pointer_manager, wlserver.wlr.seat, 0, unaccel_dx, unaccel_dy, unaccel_dx, unaccel_dy ); | ||
| } | ||
|
|
||
| diff --git a/src/wlserver.hpp b/src/wlserver.hpp | ||
| --- a/src/wlserver.hpp | ||
| +++ b/src/wlserver.hpp | ||
| @@ -171,6 +171,8 @@ | ||
| bool bFollowClientPointer = false; | ||
| double flFollowDX = 0.0; | ||
| double flFollowDY = 0.0; | ||
| + double flSentDX = 0.0; | ||
| + double flSentDY = 0.0; | ||
|
|
||
| bool button_held[ WLSERVER_BUTTON_COUNT ]; | ||
| std::set <uint32_t> touch_down_ids; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| from pathlib import Path | ||
| import shutil | ||
| import subprocess | ||
| import tempfile | ||
| import unittest | ||
|
|
||
|
|
||
| ROOT = Path(__file__).resolve().parents[2] | ||
| PATCH = ROOT / "tools/gamescope/patches/0133-constraint-relative-when-held.patch" | ||
|
|
||
|
|
||
| def added_source(): | ||
| return "\n".join(line[1:] for line in PATCH.read_text().splitlines() if line.startswith("+") and not line.startswith("+++")) | ||
|
|
||
|
|
||
| def block(text, start): | ||
| begin = text.index(start) | ||
| return text[begin:text.index("\n}", begin) + 3] | ||
|
|
||
|
|
||
| def rules(): | ||
| text = added_source() | ||
| return "\n".join([ | ||
| block(text, "struct PointerHold"), | ||
| block(text, "static bool pointer_hold_step"), | ||
| ]) | ||
|
|
||
|
|
||
| PREAMBLE = r''' | ||
| #include <algorithm> | ||
| #include <cassert> | ||
| #include <cmath> | ||
| #include <cstdint> | ||
| #include <cstdio> | ||
| ''' | ||
|
|
||
| MAIN = r''' | ||
| static const uint64_t FRAME = 16'666'667ul; | ||
|
|
||
| static bool run(PointerHold &state, int frames, double seatDX, double seatDY, double pointerDX, double pointerDY, uint64_t &now, bool constrained = true) { | ||
| bool held = state.bHeld; | ||
| for (int i = 0; i < frames; i++) { | ||
| now += FRAME; | ||
| held = pointer_hold_step(state, constrained, seatDX, seatDY, pointerDX, pointerDY, now); | ||
| } | ||
| return held; | ||
| } | ||
|
|
||
| int main() { | ||
| uint64_t now = 1'000'000'000ul; | ||
|
|
||
| PointerHold cursorGame; | ||
| assert(!run(cursorGame, 600, 12.0, -9.0, 12.0, -9.0, now)); | ||
| assert(!run(cursorGame, 600, 40.0, 3.0, 39.0, 3.0, now)); | ||
| assert(!run(cursorGame, 1, 30.0, 0.0, 0.0, 0.0, now)); | ||
| assert(!run(cursorGame, 1, 30.0, 0.0, 30.0, 0.0, now)); | ||
| assert(!run(cursorGame, 1, 30.0, 0.0, 0.0, 0.0, now)); | ||
| assert(!run(cursorGame, 120, 2.0, 1.0, 0.0, 0.0, now)); | ||
|
|
||
| PointerHold offset; | ||
| assert(!run(offset, 600, 25.0, 25.0, 25.0, 25.0, now)); | ||
|
|
||
| PointerHold recentring; | ||
| assert(!run(recentring, 2, -40.0, 12.0, 0.0, 0.0, now)); | ||
| assert(run(recentring, 1, -40.0, 12.0, 1.0, 0.0, now)); | ||
| assert(run(recentring, 600, 0.0, 0.0, 0.0, 0.0, now)); | ||
| assert(run(recentring, 29, 10.0, 4.0, 10.0, 4.0, now)); | ||
| assert(run(recentring, 1, 20.0, 0.0, 0.0, 0.0, now)); | ||
| assert(run(recentring, 29, 10.0, 4.0, 10.0, 4.0, now)); | ||
| assert(!run(recentring, 1, 10.0, 4.0, 10.0, 4.0, now)); | ||
|
|
||
| PointerHold clipped; | ||
| assert(run(clipped, 3, 0.0, 30.0, 0.0, 2.0, now)); | ||
|
|
||
| PointerHold spaced; | ||
| for (int i = 0; i < 10; i++) { | ||
| assert(!run(spaced, 1, 50.0, 0.0, 0.0, 0.0, now)); | ||
| now += 600'000'000ul; | ||
| } | ||
|
|
||
| PointerHold hinted; | ||
| hinted.bHeld = true; | ||
| assert(run(hinted, 29, 0.0, 15.0, 0.0, 15.0, now)); | ||
| assert(!run(hinted, 1, 0.0, 15.0, 0.0, 15.0, now)); | ||
|
|
||
| PointerHold hintedCamera; | ||
| hintedCamera.bHeld = true; | ||
| assert(run(hintedCamera, 600, 30.0, -8.0, 0.0, 0.0, now)); | ||
|
|
||
| PointerHold chained; | ||
| for (int i = 0; i < 6; i++) { | ||
| assert(!run(chained, 1, 50.0, 0.0, 0.0, 0.0, now)); | ||
| now += 400'000'000ul; | ||
| } | ||
|
|
||
| PointerHold released; | ||
| assert(run(released, 3, 0.0, -25.0, 0.0, 0.0, now)); | ||
| assert(!run(released, 1, 0.0, -25.0, 0.0, 0.0, now, false)); | ||
| assert(!run(released, 2, 0.0, -25.0, 0.0, 0.0, now)); | ||
|
|
||
| puts("ok"); | ||
| return 0; | ||
| } | ||
| ''' | ||
|
|
||
|
|
||
| @unittest.skipUnless(shutil.which("c++"), "needs a C++ compiler") | ||
| class PointerHoldTest(unittest.TestCase): | ||
| def test_only_a_game_that_holds_the_x_pointer_goes_relative(self): | ||
| with tempfile.TemporaryDirectory() as tmp: | ||
| source = Path(tmp) / "hold.cpp" | ||
| binary = Path(tmp) / "hold" | ||
| source.write_text(PREAMBLE + rules() + MAIN) | ||
| subprocess.run(["c++", "-std=c++17", "-Wall", "-Wextra", "-Werror", str(source), "-o", str(binary)], check=True) | ||
| self.assertEqual(subprocess.run([str(binary)], capture_output=True, text=True, check=True).stdout.strip(), "ok") | ||
|
|
||
| def test_a_hinted_constraint_starts_relative_and_others_wait_for_the_hold(self): | ||
| added = [line.strip() for line in added_source().splitlines()] | ||
| self.assertIn("update_pointer_hold( pPaintFocus, bHasPointerConstraint && bImageEmpty && nRelativeMouse != 2 && !bExcludedAppId,", added) | ||
| self.assertIn("pPaintFocus->cursor && pPaintFocus->cursor->IsConstrained() );", added) | ||
| self.assertIn("s_Hold.bHeld = bHinted;", added) | ||
| self.assertIn("if ( s_bTracking && bHinted && !s_bHinted )", added) | ||
| self.assertIn("const bool bGameHoldsPointer = bHasPointerConstraint && s_bPointerHeld;", added) | ||
| self.assertIn(": ( bForcedRelativeMouse || s_bFollowClientPointer || ( bImageEmpty && bGameHoldsPointer && !bExcludedAppId ) );", added) | ||
|
|
||
| def test_the_motion_sent_to_clients_is_counted(self): | ||
| added = [line.strip() for line in added_source().splitlines()] | ||
| self.assertIn("wlserver.flSentDX += unaccel_dx;", added) | ||
| self.assertIn("wlserver.flSentDY += unaccel_dy;", added) | ||
|
|
||
| def test_the_forced_and_follow_rules_are_unchanged(self): | ||
| removed = [line[1:].strip() for line in PATCH.read_text().splitlines() if line.startswith("-") and not line.startswith("---")] | ||
| self.assertEqual(removed, [": ( bForcedRelativeMouse || s_bFollowClientPointer || ( bImageEmpty && bHasPointerConstraint && !bExcludedAppId ) );"]) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.