fix(wayland): send preferred_buffer_transform only when it differs and never to cursors
- commit
- 8d6e44d514bf71d57968743016a3147f8bb13b47
- author
- brice · · 2026-10-03 19:15 UTC
- parent
- c1aa599
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +60 -37
M internal/adapters/wayland/outputmanagement_test.go +1 -1
| @@ -285,7 +285,6 @@ func TestOutputManagementUnsupportedSettings(t *testing.T) { | |||
| 285 | 285 | } | |
| 286 | 286 | } | |
| 287 | 287 | ||
| 288 | - | // Each case uses a new connection because wl_display.error terminates that client. | |
| 289 | 288 | // set_transform reaches the backend, and heads report the placement's | |
| 290 | 289 | // transform, at bind time and when it changes. | |
| 291 | 290 | func TestOutputManagementTransform(t *testing.T) { | |
| @@ -365,6 +364,7 @@ func TestOutputManagementTransform(t *testing.T) { | |||
| 365 | 364 | } | |
| 366 | 365 | } | |
| 367 | 366 | ||
| 367 | + | // Each case uses a new connection because wl_display.error terminates that client. | |
| 368 | 368 | func TestOutputManagementProtocolErrors(t *testing.T) { | |
| 369 | 369 | const ( | |
| 370 | 370 | enable = wlr.ZwlrOutputConfigurationV1RequestEnableHead | |
| @@ -285,7 +285,6 @@ func TestOutputManagementUnsupportedSettings(t *testing.T) { | |||||
| 285 | } | 285 | } | ||
| 286 | } | 286 | } | ||
| 287 | 287 | ||||
| 288 | - | // Each case uses a new connection because wl_display.error terminates that client. | |||
| 289 | // set_transform reaches the backend, and heads report the placement's | 288 | // set_transform reaches the backend, and heads report the placement's | ||
| 290 | // transform, at bind time and when it changes. | 289 | // transform, at bind time and when it changes. | ||
| 291 | func TestOutputManagementTransform(t *testing.T) { | 290 | func TestOutputManagementTransform(t *testing.T) { | ||
| @@ -365,6 +364,7 @@ func TestOutputManagementTransform(t *testing.T) { | |||||
| 365 | } | 364 | } | ||
| 366 | } | 365 | } | ||
| 367 | 366 | ||||
| 367 | + | // Each case uses a new connection because wl_display.error terminates that client. | |||
| 368 | func TestOutputManagementProtocolErrors(t *testing.T) { | 368 | func TestOutputManagementProtocolErrors(t *testing.T) { | ||
| 369 | const ( | 369 | const ( | ||
| 370 | enable = wlr.ZwlrOutputConfigurationV1RequestEnableHead | 370 | enable = wlr.ZwlrOutputConfigurationV1RequestEnableHead | ||
M internal/adapters/wayland/scale.go +8 -12
| @@ -77,14 +77,15 @@ func (surf *surface) sendScale() { | |||
| 77 | 77 | if o != nil { | |
| 78 | 78 | scale = o.place.Scale | |
| 79 | 79 | } | |
| 80 | - | // The output holds the scene under its transform, so a client that | |
| 81 | - | // renders with the same buffer transform avoids a compositing pass. | |
| 80 | + | // The hint lets the client render in the output's orientation (rotated | |
| 81 | + | // outputs are always composed). Normal is the protocol default, so it is | |
| 82 | + | // sent only on change; cursor buffers must stay untransformed. | |
| 82 | 83 | var t ports.BufferTransform | |
| 83 | 84 | if o != nil { | |
| 84 | 85 | t = o.place.Transform | |
| 85 | 86 | } | |
| 86 | - | if surf.wl.Version() >= 6 && (!surf.prefTransformSent || t != surf.prefTransform) { | |
| 87 | - | surf.prefTransform, surf.prefTransformSent = t, true | |
| 87 | + | if surf.wl.Version() >= 6 && surf.kind != roleCursor && t != surf.prefTransform { | |
| 88 | + | surf.prefTransform = t | |
| 88 | 89 | surf.wl.SendPreferredBufferTransform(uint32(t)) | |
| 89 | 90 | } | |
| 90 | 91 | if scale == surf.scale { | |
| @@ -236,10 +237,8 @@ func (s *surface) source(bw, bh int) ([4]float32, bool) { | |||
| 236 | 237 | v := s.committedViewport.src | |
| 237 | 238 | scale := float64(max(s.bufferScale, 1)) | |
| 238 | 239 | // Transformed buffer size, in the surface's axes. | |
| 239 | - | tw, th := float64(bw), float64(bh) | |
| 240 | - | if s.transform.Rotated() { | |
| 241 | - | tw, th = th, tw | |
| 242 | - | } | |
| 240 | + | iw, ih := s.transformedSize(bw, bh) | |
| 241 | + | tw, th := float64(iw), float64(ih) | |
| 243 | 242 | x0, y0 := float64(v[0])*scale/256, float64(v[1])*scale/256 | |
| 244 | 243 | x1, y1 := x0+float64(v[2])*scale/256, y0+float64(v[3])*scale/256 | |
| 245 | 244 | // validateViewport tolerates a rounding overshoot: sample inside the buffer. | |
| @@ -252,10 +251,7 @@ func (s *surface) source(bw, bh int) ([4]float32, bool) { | |||
| 252 | 251 | // transformedSize is the buffer size in the surface's axes: width and height | |
| 253 | 252 | // swap for a 90° or 270° buffer transform. | |
| 254 | 253 | func (s *surface) transformedSize(bw, bh int) (int, int) { | |
| 255 | - | if s.transform.Rotated() { | |
| 256 | - | return bh, bw | |
| 257 | - | } | |
| 258 | - | return bw, bh | |
| 254 | + | return s.transform.Size(bw, bh) | |
| 259 | 255 | } | |
| 260 | 256 | ||
| 261 | 257 | func (s *surface) validateViewport(bw, bh int) bool { | |
| @@ -77,14 +77,15 @@ func (surf *surface) sendScale() { | |||||
| 77 | if o != nil { | 77 | if o != nil { | ||
| 78 | scale = o.place.Scale | 78 | scale = o.place.Scale | ||
| 79 | } | 79 | } | ||
| 80 | - | // The output holds the scene under its transform, so a client that | 80 | + | // The hint lets the client render in the output's orientation (rotated |
| 81 | - | // renders with the same buffer transform avoids a compositing pass. | 81 | + | // outputs are always composed). Normal is the protocol default, so it is |
| 82 | + | // sent only on change; cursor buffers must stay untransformed. | |||
| 82 | var t ports.BufferTransform | 83 | var t ports.BufferTransform | ||
| 83 | if o != nil { | 84 | if o != nil { | ||
| 84 | t = o.place.Transform | 85 | t = o.place.Transform | ||
| 85 | } | 86 | } | ||
| 86 | - | if surf.wl.Version() >= 6 && (!surf.prefTransformSent || t != surf.prefTransform) { | 87 | + | if surf.wl.Version() >= 6 && surf.kind != roleCursor && t != surf.prefTransform { |
| 87 | - | surf.prefTransform, surf.prefTransformSent = t, true | 88 | + | surf.prefTransform = t |
| 88 | surf.wl.SendPreferredBufferTransform(uint32(t)) | 89 | surf.wl.SendPreferredBufferTransform(uint32(t)) | ||
| 89 | } | 90 | } | ||
| 90 | if scale == surf.scale { | 91 | if scale == surf.scale { | ||
| @@ -236,10 +237,8 @@ func (s *surface) source(bw, bh int) ([4]float32, bool) { | |||||
| 236 | v := s.committedViewport.src | 237 | v := s.committedViewport.src | ||
| 237 | scale := float64(max(s.bufferScale, 1)) | 238 | scale := float64(max(s.bufferScale, 1)) | ||
| 238 | // Transformed buffer size, in the surface's axes. | 239 | // Transformed buffer size, in the surface's axes. | ||
| 239 | - | tw, th := float64(bw), float64(bh) | 240 | + | iw, ih := s.transformedSize(bw, bh) |
| 240 | - | if s.transform.Rotated() { | 241 | + | tw, th := float64(iw), float64(ih) |
| 241 | - | tw, th = th, tw | |||
| 242 | - | } | |||
| 243 | x0, y0 := float64(v[0])*scale/256, float64(v[1])*scale/256 | 242 | x0, y0 := float64(v[0])*scale/256, float64(v[1])*scale/256 | ||
| 244 | x1, y1 := x0+float64(v[2])*scale/256, y0+float64(v[3])*scale/256 | 243 | x1, y1 := x0+float64(v[2])*scale/256, y0+float64(v[3])*scale/256 | ||
| 245 | // validateViewport tolerates a rounding overshoot: sample inside the buffer. | 244 | // validateViewport tolerates a rounding overshoot: sample inside the buffer. | ||
| @@ -252,10 +251,7 @@ func (s *surface) source(bw, bh int) ([4]float32, bool) { | |||||
| 252 | // transformedSize is the buffer size in the surface's axes: width and height | 251 | // transformedSize is the buffer size in the surface's axes: width and height | ||
| 253 | // swap for a 90° or 270° buffer transform. | 252 | // swap for a 90° or 270° buffer transform. | ||
| 254 | func (s *surface) transformedSize(bw, bh int) (int, int) { | 253 | func (s *surface) transformedSize(bw, bh int) (int, int) { | ||
| 255 | - | if s.transform.Rotated() { | 254 | + | return s.transform.Size(bw, bh) |
| 256 | - | return bh, bw | |||
| 257 | - | } | |||
| 258 | - | return bw, bh | |||
| 259 | } | 255 | } | ||
| 260 | 256 | ||||
| 261 | func (s *surface) validateViewport(bw, bh int) bool { | 257 | func (s *surface) validateViewport(bw, bh int) bool { | ||
M internal/adapters/wayland/scale_test.go +49 -21
| @@ -68,8 +68,9 @@ func contentServer(t *testing.T) (*Server, chan ports.ClientEvent, chan ports.Cl | |||
| 68 | 68 | return s, events, commands, contents, dir | |
| 69 | 69 | } | |
| 70 | 70 | ||
| 71 | - | // A v6 surface learns the transform of its output once, and again only when | |
| 72 | - | // the transform changes. | |
| 71 | + | // A v6 surface learns the transform of its output when it differs from | |
| 72 | + | // normal (the protocol default), and again only when it changes. A cursor | |
| 73 | + | // surface never does: cursor buffers must stay untransformed. | |
| 73 | 74 | func TestPreferredBufferTransform(t *testing.T) { | |
| 74 | 75 | s, _, _, _, dir := contentServer(t) | |
| 75 | 76 | info := testOutputs[0].Info | |
| @@ -80,40 +81,67 @@ func TestPreferredBufferTransform(t *testing.T) { | |||
| 80 | 81 | t.Fatal("display stopped") | |
| 81 | 82 | } | |
| 82 | 83 | } | |
| 83 | - | setOutputs(1, 1) | |
| 84 | + | newSurface := func(c *wlturbo.Display, comp uint32) (uint32, func() []uint32) { | |
| 85 | + | surf := c.AllocateID() | |
| 86 | + | p := &surfaceTransformProxy{transforms: make(chan uint32, 8)} | |
| 87 | + | p.SetID(surf) | |
| 88 | + | registerWireProxy(c, p) | |
| 89 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf) | |
| 90 | + | return surf, func() []uint32 { | |
| 91 | + | if err := c.Roundtrip(); err != nil { | |
| 92 | + | t.Fatal(err) | |
| 93 | + | } | |
| 94 | + | var got []uint32 | |
| 95 | + | for len(p.transforms) > 0 { | |
| 96 | + | got = append(got, <-p.transforms) | |
| 97 | + | } | |
| 98 | + | return got | |
| 99 | + | } | |
| 100 | + | } | |
| 101 | + | setOutputs(0, 1) | |
| 84 | 102 | c := protocolClient(t, s, dir) | |
| 85 | 103 | g, _ := c.Registry().FindGlobal("wl_compositor") | |
| 86 | 104 | comp, err := bindWireID(c, g.Name, g.Interface, 6) | |
| 87 | 105 | if err != nil { | |
| 88 | 106 | t.Fatal(err) | |
| 89 | 107 | } | |
| 90 | - | surf := c.AllocateID() | |
| 91 | - | p := &surfaceTransformProxy{transforms: make(chan uint32, 8)} | |
| 92 | - | p.SetID(surf) | |
| 93 | - | registerWireProxy(c, p) | |
| 94 | - | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf) | |
| 95 | - | drain := func() []uint32 { | |
| 96 | - | if err := c.Roundtrip(); err != nil { | |
| 97 | - | t.Fatal(err) | |
| 98 | - | } | |
| 99 | - | var got []uint32 | |
| 100 | - | for len(p.transforms) > 0 { | |
| 101 | - | got = append(got, <-p.transforms) | |
| 102 | - | } | |
| 103 | - | return got | |
| 108 | + | seat := bindProtocol(t, c, "wl_seat") | |
| 109 | + | registerProtocol(t, c, seat) | |
| 110 | + | pointer := c.AllocateID() | |
| 111 | + | registerProtocol(t, c, pointer) | |
| 112 | + | requestProtocol(t, c, seat, wayland.SeatRequestGetPointer, pointer) | |
| 113 | + | ||
| 114 | + | _, normal := newSurface(c, comp) | |
| 115 | + | cursor, cursorSeen := newSurface(c, comp) | |
| 116 | + | if got := normal(); len(got) != 0 { | |
| 117 | + | t.Fatalf("normal output sent transforms %v", got) | |
| 104 | 118 | } | |
| 105 | - | if got := drain(); !slices.Equal(got, []uint32{1}) { | |
| 106 | - | t.Fatalf("initial transforms %v, want [1]", got) | |
| 119 | + | cursorSeen() | |
| 120 | + | requestProtocol(t, c, pointer, wayland.PointerRequestSetCursor, uint32(1), cursor, int32(0), int32(0)) | |
| 121 | + | ||
| 122 | + | setOutputs(1, 1) | |
| 123 | + | if got := normal(); !slices.Equal(got, []uint32{1}) { | |
| 124 | + | t.Fatalf("rotated transforms %v, want [1]", got) | |
| 125 | + | } | |
| 126 | + | if got := cursorSeen(); len(got) != 0 { | |
| 127 | + | t.Fatalf("cursor surface got transforms %v", got) | |
| 107 | 128 | } | |
| 108 | 129 | // A scale change alone does not repeat the transform. | |
| 109 | 130 | setOutputs(1, 2) | |
| 110 | - | if got := drain(); len(got) != 0 { | |
| 131 | + | if got := normal(); len(got) != 0 { | |
| 111 | 132 | t.Fatalf("unchanged transform resent: %v", got) | |
| 112 | 133 | } | |
| 113 | 134 | setOutputs(3, 2) | |
| 114 | - | if got := drain(); !slices.Equal(got, []uint32{3}) { | |
| 135 | + | if got := normal(); !slices.Equal(got, []uint32{3}) { | |
| 115 | 136 | t.Fatalf("changed transforms %v, want [3]", got) | |
| 116 | 137 | } | |
| 138 | + | setOutputs(0, 2) | |
| 139 | + | if got := normal(); !slices.Equal(got, []uint32{0}) { | |
| 140 | + | t.Fatalf("back to normal: %v, want [0]", got) | |
| 141 | + | } | |
| 142 | + | if got := cursorSeen(); len(got) != 0 { | |
| 143 | + | t.Fatalf("cursor surface got transforms %v", got) | |
| 144 | + | } | |
| 117 | 145 | } | |
| 118 | 146 | ||
| 119 | 147 | func TestFractionalScaleAndViewport(t *testing.T) { | |
| @@ -68,8 +68,9 @@ func contentServer(t *testing.T) (*Server, chan ports.ClientEvent, chan ports.Cl | |||||
| 68 | return s, events, commands, contents, dir | 68 | return s, events, commands, contents, dir | ||
| 69 | } | 69 | } | ||
| 70 | 70 | ||||
| 71 | - | // A v6 surface learns the transform of its output once, and again only when | 71 | + | // A v6 surface learns the transform of its output when it differs from |
| 72 | - | // the transform changes. | 72 | + | // normal (the protocol default), and again only when it changes. A cursor |
| 73 | + | // surface never does: cursor buffers must stay untransformed. | |||
| 73 | func TestPreferredBufferTransform(t *testing.T) { | 74 | func TestPreferredBufferTransform(t *testing.T) { | ||
| 74 | s, _, _, _, dir := contentServer(t) | 75 | s, _, _, _, dir := contentServer(t) | ||
| 75 | info := testOutputs[0].Info | 76 | info := testOutputs[0].Info | ||
| @@ -80,40 +81,67 @@ func TestPreferredBufferTransform(t *testing.T) { | |||||
| 80 | t.Fatal("display stopped") | 81 | t.Fatal("display stopped") | ||
| 81 | } | 82 | } | ||
| 82 | } | 83 | } | ||
| 83 | - | setOutputs(1, 1) | 84 | + | newSurface := func(c *wlturbo.Display, comp uint32) (uint32, func() []uint32) { |
| 85 | + | surf := c.AllocateID() | |||
| 86 | + | p := &surfaceTransformProxy{transforms: make(chan uint32, 8)} | |||
| 87 | + | p.SetID(surf) | |||
| 88 | + | registerWireProxy(c, p) | |||
| 89 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf) | |||
| 90 | + | return surf, func() []uint32 { | |||
| 91 | + | if err := c.Roundtrip(); err != nil { | |||
| 92 | + | t.Fatal(err) | |||
| 93 | + | } | |||
| 94 | + | var got []uint32 | |||
| 95 | + | for len(p.transforms) > 0 { | |||
| 96 | + | got = append(got, <-p.transforms) | |||
| 97 | + | } | |||
| 98 | + | return got | |||
| 99 | + | } | |||
| 100 | + | } | |||
| 101 | + | setOutputs(0, 1) | |||
| 84 | c := protocolClient(t, s, dir) | 102 | c := protocolClient(t, s, dir) | ||
| 85 | g, _ := c.Registry().FindGlobal("wl_compositor") | 103 | g, _ := c.Registry().FindGlobal("wl_compositor") | ||
| 86 | comp, err := bindWireID(c, g.Name, g.Interface, 6) | 104 | comp, err := bindWireID(c, g.Name, g.Interface, 6) | ||
| 87 | if err != nil { | 105 | if err != nil { | ||
| 88 | t.Fatal(err) | 106 | t.Fatal(err) | ||
| 89 | } | 107 | } | ||
| 90 | - | surf := c.AllocateID() | 108 | + | seat := bindProtocol(t, c, "wl_seat") |
| 91 | - | p := &surfaceTransformProxy{transforms: make(chan uint32, 8)} | 109 | + | registerProtocol(t, c, seat) |
| 92 | - | p.SetID(surf) | 110 | + | pointer := c.AllocateID() |
| 93 | - | registerWireProxy(c, p) | 111 | + | registerProtocol(t, c, pointer) |
| 94 | - | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf) | 112 | + | requestProtocol(t, c, seat, wayland.SeatRequestGetPointer, pointer) |
| 95 | - | drain := func() []uint32 { | 113 | + | |
| 96 | - | if err := c.Roundtrip(); err != nil { | 114 | + | _, normal := newSurface(c, comp) |
| 97 | - | t.Fatal(err) | 115 | + | cursor, cursorSeen := newSurface(c, comp) |
| 98 | - | } | 116 | + | if got := normal(); len(got) != 0 { |
| 99 | - | var got []uint32 | 117 | + | t.Fatalf("normal output sent transforms %v", got) |
| 100 | - | for len(p.transforms) > 0 { | |||
| 101 | - | got = append(got, <-p.transforms) | |||
| 102 | - | } | |||
| 103 | - | return got | |||
| 104 | } | 118 | } | ||
| 105 | - | if got := drain(); !slices.Equal(got, []uint32{1}) { | 119 | + | cursorSeen() |
| 106 | - | t.Fatalf("initial transforms %v, want [1]", got) | 120 | + | requestProtocol(t, c, pointer, wayland.PointerRequestSetCursor, uint32(1), cursor, int32(0), int32(0)) |
| 121 | + | ||||
| 122 | + | setOutputs(1, 1) | |||
| 123 | + | if got := normal(); !slices.Equal(got, []uint32{1}) { | |||
| 124 | + | t.Fatalf("rotated transforms %v, want [1]", got) | |||
| 125 | + | } | |||
| 126 | + | if got := cursorSeen(); len(got) != 0 { | |||
| 127 | + | t.Fatalf("cursor surface got transforms %v", got) | |||
| 107 | } | 128 | } | ||
| 108 | // A scale change alone does not repeat the transform. | 129 | // A scale change alone does not repeat the transform. | ||
| 109 | setOutputs(1, 2) | 130 | setOutputs(1, 2) | ||
| 110 | - | if got := drain(); len(got) != 0 { | 131 | + | if got := normal(); len(got) != 0 { |
| 111 | t.Fatalf("unchanged transform resent: %v", got) | 132 | t.Fatalf("unchanged transform resent: %v", got) | ||
| 112 | } | 133 | } | ||
| 113 | setOutputs(3, 2) | 134 | setOutputs(3, 2) | ||
| 114 | - | if got := drain(); !slices.Equal(got, []uint32{3}) { | 135 | + | if got := normal(); !slices.Equal(got, []uint32{3}) { |
| 115 | t.Fatalf("changed transforms %v, want [3]", got) | 136 | t.Fatalf("changed transforms %v, want [3]", got) | ||
| 116 | } | 137 | } | ||
| 138 | + | setOutputs(0, 2) | |||
| 139 | + | if got := normal(); !slices.Equal(got, []uint32{0}) { | |||
| 140 | + | t.Fatalf("back to normal: %v, want [0]", got) | |||
| 141 | + | } | |||
| 142 | + | if got := cursorSeen(); len(got) != 0 { | |||
| 143 | + | t.Fatalf("cursor surface got transforms %v", got) | |||
| 144 | + | } | |||
| 117 | } | 145 | } | ||
| 118 | 146 | ||||
| 119 | func TestFractionalScaleAndViewport(t *testing.T) { | 147 | func TestFractionalScaleAndViewport(t *testing.T) { | ||
M internal/adapters/wayland/surface.go +2 -3
| @@ -56,9 +56,8 @@ type surface struct { | |||
| 56 | 56 | on *output | |
| 57 | 57 | scale float64 | |
| 58 | 58 | // prefTransform is the last preferred buffer transform sent (wl_surface | |
| 59 | - | // v6+); prefTransformSent is false until the first one. | |
| 60 | - | prefTransform ports.BufferTransform | |
| 61 | - | prefTransformSent bool | |
| 59 | + | // v6+); zero (normal) is the protocol default. | |
| 60 | + | prefTransform ports.BufferTransform | |
| 62 | 61 | // content is the last content built from this surface's own buffer; | |
| 63 | 62 | // has is false while no buffer is attached. | |
| 64 | 63 | inputAll bool | |
| @@ -56,9 +56,8 @@ type surface struct { | |||||
| 56 | on *output | 56 | on *output | ||
| 57 | scale float64 | 57 | scale float64 | ||
| 58 | // prefTransform is the last preferred buffer transform sent (wl_surface | 58 | // prefTransform is the last preferred buffer transform sent (wl_surface | ||
| 59 | - | // v6+); prefTransformSent is false until the first one. | 59 | + | // v6+); zero (normal) is the protocol default. |
| 60 | - | prefTransform ports.BufferTransform | 60 | + | prefTransform ports.BufferTransform |
| 61 | - | prefTransformSent bool | |||
| 62 | // content is the last content built from this surface's own buffer; | 61 | // content is the last content built from this surface's own buffer; | ||
| 63 | // has is false while no buffer is attached. | 62 | // has is false while no buffer is attached. | ||
| 64 | inputAll bool | 63 | inputAll bool | ||