detached 02f9e79

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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 {
628628 func (o *Output) commitState(vrr bool) error {
629629 req := &atomicReq{}
630630 cur := cursorState{}
631+ // A move back to the applied state holds nothing.
632+ o.cursorHeld = false
631633 if o.cursor != nil {
632634 cur = o.cursor.desired()
633635 if cur != o.cursor.applied {
@@ -657,9 +659,6 @@ func (o *Output) commitState(vrr bool) error {
657659 }
658660 if o.cursor != nil {
659661 o.cursor.committed(cur)
660- if cur == o.cursor.desired() {
661- o.cursorHeld = false
662- }
663662 }
664663 if vrr != o.vrrOn {
665664 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) {
229229 }
230230 }
231231
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+
232249 // Without a game, the cursor keeps its own commits.
233250 func TestCursorCommitsAloneWithoutGame(t *testing.T) {
234251 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)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 26

Tags 10

↑↓ moveenter openesc close