Honour the negotiated pixel format for the cursor, enqueue key edges reliably, drop racy test writes

This commit is contained in:
Viktor Liu
2026-08-29 12:40:14 +02:00
parent d196b23de6
commit d8236002c7
6 changed files with 86 additions and 72 deletions
+33 -45
View File
@@ -11,70 +11,58 @@ import (
"github.com/stretchr/testify/require"
)
// fakeCursorCapturer plays back a scripted sequence of cursor sprites, each
// with the serial its platform would report.
type fakeCursorCapturer struct {
sprites []fakeSprite
next int
}
type fakeSprite struct {
// stubCursorSource returns a scripted sequence of cursors, standing in for a
// platform cursor source.
type stubCursorSource struct {
img *image.RGBA
serial uint64
err error
}
func (f *fakeCursorCapturer) Width() int { return 100 }
func (f *fakeCursorCapturer) Height() int { return 100 }
func (s *stubCursorSource) Width() int { return 64 }
func (s *stubCursorSource) Height() int { return 64 }
func (f *fakeCursorCapturer) Capture() (*image.RGBA, error) {
return image.NewRGBA(image.Rect(0, 0, 100, 100)), nil
func (s *stubCursorSource) Capture() (*image.RGBA, error) {
return image.NewRGBA(image.Rect(0, 0, 64, 64)), nil
}
func (f *fakeCursorCapturer) Cursor() (*image.RGBA, int, int, uint64, error) {
s := f.sprites[min(f.next, len(f.sprites)-1)]
f.next++
if s.err != nil {
return nil, 0, 0, 0, s.err
}
return image.NewRGBA(image.Rect(0, 0, 16, 16)), 0, 0, s.serial, nil
func (s *stubCursorSource) Cursor() (*image.RGBA, int, int, uint64, error) {
return s.img, 0, 0, s.serial, nil
}
func newCursorSession(t *testing.T, cap ScreenCapturer) *session {
t.Helper()
func newCursorSession(src *stubCursorSource) *session {
return &session{
capturer: cap,
capturer: src,
clientSupportsCursor: true,
log: log.WithField("test", t.Name()),
log: log.WithField("test", "cursor"),
}
}
// X11 reports the XFixes cursor-serial, which names the cursor rather than
// counting upwards: switching back to a cursor shown earlier yields a lower
// value. Ordering the serials treated that as stale and left the client stuck
// on whichever cursor had the highest one, typically the I-beam.
func TestPendingCursorRect_SerialGoingBackwardsStillUpdates(t *testing.T) {
cap := &fakeCursorCapturer{sprites: []fakeSprite{
{serial: 100}, // arrow
{serial: 250}, // I-beam over a text field
{serial: 100}, // back to the arrow
}}
s := newCursorSession(t, cap)
require.NotNil(t, s.pendingCursorRect(), "the first cursor must be sent")
assert.Equal(t, uint64(100), s.lastCursorSerial)
// X11 passes through the XFixes cursor-serial, which names the cursor rather
// than counting upwards: going back to a cursor shown earlier reports a lower
// value. An ordering comparison discarded that update and left the client stuck
// on whichever cursor had the highest serial, in practice the I-beam.
func TestPendingCursorRect_SwitchingBackToALowerSerial(t *testing.T) {
sprite := image.NewRGBA(image.Rect(0, 0, 16, 16))
src := &stubCursorSource{img: sprite, serial: 100}
s := newCursorSession(src)
// The arrow, then an I-beam the X server happens to number higher.
require.NotNil(t, s.pendingCursorRect(), "first cursor must be sent")
src.serial = 250
require.NotNil(t, s.pendingCursorRect(), "a different cursor must be sent")
assert.Equal(t, uint64(250), s.lastCursorSerial)
require.NotNil(t, s.pendingCursorRect(), "returning to an earlier cursor must be sent too")
assert.Equal(t, uint64(100), s.lastCursorSerial)
// Back to the arrow: a lower serial, and still a real change.
src.serial = 100
assert.NotNil(t, s.pendingCursorRect(), "returning to an earlier cursor must be sent, not dropped as stale")
}
// The same serial twice in a row is the same cursor and carries no update.
// An unchanged serial is still the one case that must not produce a rect,
// otherwise every framebuffer update would carry a redundant cursor.
func TestPendingCursorRect_UnchangedSerialIsSkipped(t *testing.T) {
cap := &fakeCursorCapturer{sprites: []fakeSprite{{serial: 7}, {serial: 7}}}
s := newCursorSession(t, cap)
sprite := image.NewRGBA(image.Rect(0, 0, 16, 16))
src := &stubCursorSource{img: sprite, serial: 7}
s := newCursorSession(src)
require.NotNil(t, s.pendingCursorRect())
assert.Nil(t, s.pendingCursorRect(), "an unchanged serial must not re-send the sprite")
assert.Nil(t, s.pendingCursorRect(), "the same cursor must not be re-sent")
}