fix(drm): clear a held cursor move reversed before the next frame
commitState left cursorHeld set when the desired cursor went back to the applied one, so the cursor timer fired at once in a loop.
- commit
- 02f9e7993b6103ee1e74447fce4860c47790ee74
- author
- brice · · 2026-09-28 15:37 UTC
- parent
- 2b40f0a
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +19 -3
M internal/adapters/drm/output.go +2 -3
| @@ -628,6 +628,8 @@ func (o *Output) cursorRefused() bool { | |||
| 628 | 628 | func (o *Output) commitState(vrr bool) error { | |
| 629 | 629 | req := &atomicReq{} | |
| 630 | 630 | cur := cursorState{} | |
| 631 | + | // A move back to the applied state holds nothing. | |
| 632 | + | o.cursorHeld = false | |
| 631 | 633 | if o.cursor != nil { | |
| 632 | 634 | cur = o.cursor.desired() | |
| 633 | 635 | if cur != o.cursor.applied { | |
| @@ -657,9 +659,6 @@ func (o *Output) commitState(vrr bool) error { | |||
| 657 | 659 | } | |
| 658 | 660 | if o.cursor != nil { | |
| 659 | 661 | o.cursor.committed(cur) | |
| 660 | - | if cur == o.cursor.desired() { | |
| 661 | - | o.cursorHeld = false | |
| 662 | - | } | |
| 663 | 662 | } | |
| 664 | 663 | if vrr != o.vrrOn { | |
| 665 | 664 | o.log.Info().Bool("vrr", vrr).Str("connector", o.conn.name).Msg("vrr") | |
| @@ -628,6 +628,8 @@ func (o *Output) cursorRefused() bool { | |||||
| 628 | func (o *Output) commitState(vrr bool) error { | 628 | func (o *Output) commitState(vrr bool) error { | ||
| 629 | req := &atomicReq{} | 629 | req := &atomicReq{} | ||
| 630 | cur := cursorState{} | 630 | cur := cursorState{} | ||
| 631 | + | // A move back to the applied state holds nothing. | |||
| 632 | + | o.cursorHeld = false | |||
| 631 | if o.cursor != nil { | 633 | if o.cursor != nil { | ||
| 632 | cur = o.cursor.desired() | 634 | cur = o.cursor.desired() | ||
| 633 | if cur != o.cursor.applied { | 635 | if cur != o.cursor.applied { | ||
| @@ -657,9 +659,6 @@ func (o *Output) commitState(vrr bool) error { | |||||
| 657 | } | 659 | } | ||
| 658 | if o.cursor != nil { | 660 | if o.cursor != nil { | ||
| 659 | o.cursor.committed(cur) | 661 | o.cursor.committed(cur) | ||
| 660 | - | if cur == o.cursor.desired() { | |||
| 661 | - | o.cursorHeld = false | |||
| 662 | - | } | |||
| 663 | } | 662 | } | ||
| 664 | if vrr != o.vrrOn { | 663 | if vrr != o.vrrOn { | ||
| 665 | o.log.Info().Bool("vrr", vrr).Str("connector", o.conn.name).Msg("vrr") | 664 | o.log.Info().Bool("vrr", vrr).Str("connector", o.conn.name).Msg("vrr") | ||
M internal/adapters/drm/output_test.go +17 -0
| @@ -229,6 +229,23 @@ func TestVRRGameCursorWaitsForFrame(t *testing.T) { | |||
| 229 | 229 | } | |
| 230 | 230 | } | |
| 231 | 231 | ||
| 232 | + | // A held move reversed before the next frame holds nothing: the cursor | |
| 233 | + | // timer is not armed again. | |
| 234 | + | func TestHeldCursorMovedBackClearsHold(t *testing.T) { | |
| 235 | + | o, _, commits := testOutput(t) | |
| 236 | + | o.cursor.image = true | |
| 237 | + | o.vrrOn, o.vrrGame, o.lastFrame = true, true, time.Now() | |
| 238 | + | o.cursor.Move(5, 5) | |
| 239 | + | if err := o.commitState(o.stateVRR()); err != nil || !o.cursorHeld { | |
| 240 | + | t.Fatalf("err %v held=%v", err, o.cursorHeld) | |
| 241 | + | } | |
| 242 | + | o.cursor.Move(0, 0) | |
| 243 | + | o.cursor.applied = o.cursor.desired() | |
| 244 | + | if err := o.commitState(o.stateVRR()); err != nil || o.cursorHeld || len(*commits) != 0 { | |
| 245 | + | t.Fatalf("err %v held=%v commits %d", err, o.cursorHeld, len(*commits)) | |
| 246 | + | } | |
| 247 | + | } | |
| 248 | + | ||
| 232 | 249 | // Without a game, the cursor keeps its own commits. | |
| 233 | 250 | func TestCursorCommitsAloneWithoutGame(t *testing.T) { | |
| 234 | 251 | o, _, commits := testOutput(t) | |
| @@ -229,6 +229,23 @@ func TestVRRGameCursorWaitsForFrame(t *testing.T) { | |||||
| 229 | } | 229 | } | ||
| 230 | } | 230 | } | ||
| 231 | 231 | ||||
| 232 | + | // A held move reversed before the next frame holds nothing: the cursor | |||
| 233 | + | // timer is not armed again. | |||
| 234 | + | func TestHeldCursorMovedBackClearsHold(t *testing.T) { | |||
| 235 | + | o, _, commits := testOutput(t) | |||
| 236 | + | o.cursor.image = true | |||
| 237 | + | o.vrrOn, o.vrrGame, o.lastFrame = true, true, time.Now() | |||
| 238 | + | o.cursor.Move(5, 5) | |||
| 239 | + | if err := o.commitState(o.stateVRR()); err != nil || !o.cursorHeld { | |||
| 240 | + | t.Fatalf("err %v held=%v", err, o.cursorHeld) | |||
| 241 | + | } | |||
| 242 | + | o.cursor.Move(0, 0) | |||
| 243 | + | o.cursor.applied = o.cursor.desired() | |||
| 244 | + | if err := o.commitState(o.stateVRR()); err != nil || o.cursorHeld || len(*commits) != 0 { | |||
| 245 | + | t.Fatalf("err %v held=%v commits %d", err, o.cursorHeld, len(*commits)) | |||
| 246 | + | } | |||
| 247 | + | } | |||
| 248 | + | ||||
| 232 | // Without a game, the cursor keeps its own commits. | 249 | // Without a game, the cursor keeps its own commits. | ||
| 233 | func TestCursorCommitsAloneWithoutGame(t *testing.T) { | 250 | func TestCursorCommitsAloneWithoutGame(t *testing.T) { | ||
| 234 | o, _, commits := testOutput(t) | 251 | o, _, commits := testOutput(t) | ||