fix(wayland): resend input regions after remapping
- commit
- 45fc19192d3032d46374b160685c1ad250ba20cd
- author
- brice · · 2026-09-27 13:33 UTC
- parent
- 5278439
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
7 files · +138 -4
M internal/adapters/wayland/constraints.go +20 -2
| @@ -277,7 +277,24 @@ func (s *Server) emitConstraint(c *constraint) { | |||
| 277 | 277 | box = ports.Rect{X: x0, Y: y0, W: max(box.X+box.W, input.X+input.W) - x0, H: max(box.Y+box.H, input.Y+input.H) - y0} | |
| 278 | 278 | } | |
| 279 | 279 | } | |
| 280 | - | r = box | |
| 280 | + | // Core treats an empty rect as the entire window. Keep the | |
| 281 | + | // original constraint (or the input box for an unbounded one) | |
| 282 | + | // when disjoint, rather than widening it to the whole window. | |
| 283 | + | if box.W > 0 { | |
| 284 | + | r = box | |
| 285 | + | } else if r.W <= 0 || r.H <= 0 { | |
| 286 | + | for _, input := range rects { | |
| 287 | + | if input.W <= 0 || input.H <= 0 { | |
| 288 | + | continue | |
| 289 | + | } | |
| 290 | + | if r.W <= 0 || r.H <= 0 { | |
| 291 | + | r = input | |
| 292 | + | } else { | |
| 293 | + | x0, y0 := min(r.X, input.X), min(r.Y, input.Y) | |
| 294 | + | r = ports.Rect{X: x0, Y: y0, W: max(r.X+r.W, input.X+input.W) - x0, H: max(r.Y+r.H, input.Y+input.H) - y0} | |
| 295 | + | } | |
| 296 | + | } | |
| 297 | + | } | |
| 281 | 298 | } | |
| 282 | 299 | } | |
| 283 | 300 | s.emit(ports.PointerConstrained{ID: x.window.id, PointerConstraint: ports.PointerConstraint{Mode: mode, Rect: r}}) | |
| @@ -290,9 +307,10 @@ func (c *constraint) contains(w *window) bool { | |||
| 290 | 307 | x, y := w.surfacePoint(w.xdg.server.pointerX, w.xdg.server.pointerY) | |
| 291 | 308 | all, rects := c.surface.effectiveInput() | |
| 292 | 309 | if !all { | |
| 310 | + | localX, localY := x-float64(w.xdg.geometry.X), y-float64(w.xdg.geometry.Y) | |
| 293 | 311 | inside := false | |
| 294 | 312 | for _, input := range rects { | |
| 295 | - | if x-float64(w.xdg.geometry.X) >= float64(input.X) && x-float64(w.xdg.geometry.X) < float64(input.X+input.W) && y-float64(w.xdg.geometry.Y) >= float64(input.Y) && y-float64(w.xdg.geometry.Y) < float64(input.Y+input.H) { | |
| 313 | + | if localX >= float64(input.X) && localX < float64(input.X+input.W) && localY >= float64(input.Y) && localY < float64(input.Y+input.H) { | |
| 296 | 314 | inside = true | |
| 297 | 315 | break | |
| 298 | 316 | } | |
| @@ -277,7 +277,24 @@ func (s *Server) emitConstraint(c *constraint) { | |||||
| 277 | box = ports.Rect{X: x0, Y: y0, W: max(box.X+box.W, input.X+input.W) - x0, H: max(box.Y+box.H, input.Y+input.H) - y0} | 277 | box = ports.Rect{X: x0, Y: y0, W: max(box.X+box.W, input.X+input.W) - x0, H: max(box.Y+box.H, input.Y+input.H) - y0} | ||
| 278 | } | 278 | } | ||
| 279 | } | 279 | } | ||
| 280 | - | r = box | 280 | + | // Core treats an empty rect as the entire window. Keep the |
| 281 | + | // original constraint (or the input box for an unbounded one) | |||
| 282 | + | // when disjoint, rather than widening it to the whole window. | |||
| 283 | + | if box.W > 0 { | |||
| 284 | + | r = box | |||
| 285 | + | } else if r.W <= 0 || r.H <= 0 { | |||
| 286 | + | for _, input := range rects { | |||
| 287 | + | if input.W <= 0 || input.H <= 0 { | |||
| 288 | + | continue | |||
| 289 | + | } | |||
| 290 | + | if r.W <= 0 || r.H <= 0 { | |||
| 291 | + | r = input | |||
| 292 | + | } else { | |||
| 293 | + | x0, y0 := min(r.X, input.X), min(r.Y, input.Y) | |||
| 294 | + | r = ports.Rect{X: x0, Y: y0, W: max(r.X+r.W, input.X+input.W) - x0, H: max(r.Y+r.H, input.Y+input.H) - y0} | |||
| 295 | + | } | |||
| 296 | + | } | |||
| 297 | + | } | |||
| 281 | } | 298 | } | ||
| 282 | } | 299 | } | ||
| 283 | s.emit(ports.PointerConstrained{ID: x.window.id, PointerConstraint: ports.PointerConstraint{Mode: mode, Rect: r}}) | 300 | s.emit(ports.PointerConstrained{ID: x.window.id, PointerConstraint: ports.PointerConstraint{Mode: mode, Rect: r}}) | ||
| @@ -290,9 +307,10 @@ func (c *constraint) contains(w *window) bool { | |||||
| 290 | x, y := w.surfacePoint(w.xdg.server.pointerX, w.xdg.server.pointerY) | 307 | x, y := w.surfacePoint(w.xdg.server.pointerX, w.xdg.server.pointerY) | ||
| 291 | all, rects := c.surface.effectiveInput() | 308 | all, rects := c.surface.effectiveInput() | ||
| 292 | if !all { | 309 | if !all { | ||
| 310 | + | localX, localY := x-float64(w.xdg.geometry.X), y-float64(w.xdg.geometry.Y) | |||
| 293 | inside := false | 311 | inside := false | ||
| 294 | for _, input := range rects { | 312 | for _, input := range rects { | ||
| 295 | - | if x-float64(w.xdg.geometry.X) >= float64(input.X) && x-float64(w.xdg.geometry.X) < float64(input.X+input.W) && y-float64(w.xdg.geometry.Y) >= float64(input.Y) && y-float64(w.xdg.geometry.Y) < float64(input.Y+input.H) { | 313 | + | if localX >= float64(input.X) && localX < float64(input.X+input.W) && localY >= float64(input.Y) && localY < float64(input.Y+input.H) { |
| 296 | inside = true | 314 | inside = true | ||
| 297 | break | 315 | break | ||
| 298 | } | 316 | } | ||
M internal/adapters/wayland/input_region.go +7 -0
| @@ -70,6 +70,13 @@ func (s *surface) effectiveInput() (bool, []ports.Rect) { | |||
| 70 | 70 | return false, rects | |
| 71 | 71 | } | |
| 72 | 72 | ||
| 73 | + | // resetInputEmission makes the next mapping publish its current region again. | |
| 74 | + | func (s *surface) resetInputEmission() { | |
| 75 | + | s.sentInput = false | |
| 76 | + | s.lastInputAll = false | |
| 77 | + | s.lastInputRects = nil | |
| 78 | + | } | |
| 79 | + | ||
| 73 | 80 | func (s *surface) emitInput() { | |
| 74 | 81 | root := s.root() | |
| 75 | 82 | id := root.windowID() | |
| @@ -70,6 +70,13 @@ func (s *surface) effectiveInput() (bool, []ports.Rect) { | |||||
| 70 | return false, rects | 70 | return false, rects | ||
| 71 | } | 71 | } | ||
| 72 | 72 | ||||
| 73 | + | // resetInputEmission makes the next mapping publish its current region again. | |||
| 74 | + | func (s *surface) resetInputEmission() { | |||
| 75 | + | s.sentInput = false | |||
| 76 | + | s.lastInputAll = false | |||
| 77 | + | s.lastInputRects = nil | |||
| 78 | + | } | |||
| 79 | + | ||||
| 73 | func (s *surface) emitInput() { | 80 | func (s *surface) emitInput() { | ||
| 74 | root := s.root() | 81 | root := s.root() | ||
| 75 | id := root.windowID() | 82 | id := root.windowID() | ||
M internal/adapters/wayland/input_region_test.go +59 -2
| @@ -1,13 +1,14 @@ | |||
| 1 | 1 | package wayland | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "github.com/bnema/purego-libwayland/protocol/pointerconstraints" | |
| 5 | - | "golang.org/x/sys/unix" | |
| 6 | 4 | "testing" | |
| 7 | 5 | "time" | |
| 8 | 6 | ||
| 9 | 7 | "github.com/bnema/neferwl/internal/ports" | |
| 8 | + | "github.com/bnema/purego-libwayland/protocol/pointerconstraints" | |
| 10 | 9 | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 10 | + | "github.com/bnema/purego-libwayland/protocol/xdgshell" | |
| 11 | + | "golang.org/x/sys/unix" | |
| 11 | 12 | ) | |
| 12 | 13 | ||
| 13 | 14 | func inputChanged(t *testing.T, events <-chan ports.ClientEvent) ports.InputRegionChanged { | |
| @@ -157,3 +158,59 @@ func TestConstraintIntersectsInputRegion(t *testing.T) { | |||
| 157 | 158 | t.Fatalf("constraint intersection: %+v", got) | |
| 158 | 159 | } | |
| 159 | 160 | } | |
| 161 | + | ||
| 162 | + | func TestInputRegionResentAfterRemap(t *testing.T) { | |
| 163 | + | s, events, _, dir := lifecycleServer(t) | |
| 164 | + | c := protocolClient(t, s, dir) | |
| 165 | + | w, surf, xdg := surfaceMapper(t, c, events)() | |
| 166 | + | comp := bindProtocol(t, c, "wl_compositor") | |
| 167 | + | region := c.AllocateID() | |
| 168 | + | registerProtocol(t, c, region) | |
| 169 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, region) | |
| 170 | + | requestProtocol(t, c, region, wayland.RegionRequestAdd, int32(0), int32(0), int32(1), int32(1)) | |
| 171 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, region) | |
| 172 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |
| 173 | + | if err := c.Roundtrip(); err != nil { | |
| 174 | + | t.Fatal(err) | |
| 175 | + | } | |
| 176 | + | if got := inputChanged(t, events); got.ID != w.ID || got.All { | |
| 177 | + | t.Fatalf("initial input: %+v", got) | |
| 178 | + | } | |
| 179 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, uint32(0), int32(0), int32(0)) | |
| 180 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |
| 181 | + | if err := c.Roundtrip(); err != nil { | |
| 182 | + | t.Fatal(err) | |
| 183 | + | } | |
| 184 | + | unmapped(t, events, w.ID) | |
| 185 | + | // Remapping requires a new initial configure and an ack before attaching. | |
| 186 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |
| 187 | + | if err := c.Roundtrip(); err != nil { | |
| 188 | + | t.Fatal(err) | |
| 189 | + | } | |
| 190 | + | var serial uint32 | |
| 191 | + | if !s.display.Do(func() { | |
| 192 | + | for _, v := range s.surfaces { | |
| 193 | + | if v.xdg != nil && v.xdg.window != nil && v.xdg.window.id == w.ID && len(v.xdg.serials) > 0 { | |
| 194 | + | serial = v.xdg.serials[len(v.xdg.serials)-1] | |
| 195 | + | } | |
| 196 | + | } | |
| 197 | + | }) { | |
| 198 | + | t.Fatal("server stopped") | |
| 199 | + | } | |
| 200 | + | if serial == 0 { | |
| 201 | + | t.Fatal("no remap configure") | |
| 202 | + | } | |
| 203 | + | requestProtocol(t, c, xdg, xdgshell.SurfaceRequestAckConfigure, serial) | |
| 204 | + | buffer := shmBuffer(t, c) | |
| 205 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, buffer, int32(0), int32(0)) | |
| 206 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |
| 207 | + | if err := c.Roundtrip(); err != nil { | |
| 208 | + | t.Fatal(err) | |
| 209 | + | } | |
| 210 | + | if got := mapped(t, events, 2*time.Second); got.ID != w.ID { | |
| 211 | + | t.Fatalf("remapped: %+v", got) | |
| 212 | + | } | |
| 213 | + | if got := inputChanged(t, events); got.ID != w.ID || got.All || len(got.Rects) != 1 || got.Rects[0] != (ports.Rect{W: 1, H: 1}) { | |
| 214 | + | t.Fatalf("remap input: %+v", got) | |
| 215 | + | } | |
| 216 | + | } | |
| @@ -1,13 +1,14 @@ | |||||
| 1 | package wayland | 1 | package wayland | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "github.com/bnema/purego-libwayland/protocol/pointerconstraints" | |||
| 5 | - | "golang.org/x/sys/unix" | |||
| 6 | "testing" | 4 | "testing" | ||
| 7 | "time" | 5 | "time" | ||
| 8 | 6 | ||||
| 9 | "github.com/bnema/neferwl/internal/ports" | 7 | "github.com/bnema/neferwl/internal/ports" | ||
| 8 | + | "github.com/bnema/purego-libwayland/protocol/pointerconstraints" | |||
| 10 | "github.com/bnema/purego-libwayland/protocol/wayland" | 9 | "github.com/bnema/purego-libwayland/protocol/wayland" | ||
| 10 | + | "github.com/bnema/purego-libwayland/protocol/xdgshell" | |||
| 11 | + | "golang.org/x/sys/unix" | |||
| 11 | ) | 12 | ) | ||
| 12 | 13 | ||||
| 13 | func inputChanged(t *testing.T, events <-chan ports.ClientEvent) ports.InputRegionChanged { | 14 | func inputChanged(t *testing.T, events <-chan ports.ClientEvent) ports.InputRegionChanged { | ||
| @@ -157,3 +158,59 @@ func TestConstraintIntersectsInputRegion(t *testing.T) { | |||||
| 157 | t.Fatalf("constraint intersection: %+v", got) | 158 | t.Fatalf("constraint intersection: %+v", got) | ||
| 158 | } | 159 | } | ||
| 159 | } | 160 | } | ||
| 161 | + | ||||
| 162 | + | func TestInputRegionResentAfterRemap(t *testing.T) { | |||
| 163 | + | s, events, _, dir := lifecycleServer(t) | |||
| 164 | + | c := protocolClient(t, s, dir) | |||
| 165 | + | w, surf, xdg := surfaceMapper(t, c, events)() | |||
| 166 | + | comp := bindProtocol(t, c, "wl_compositor") | |||
| 167 | + | region := c.AllocateID() | |||
| 168 | + | registerProtocol(t, c, region) | |||
| 169 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, region) | |||
| 170 | + | requestProtocol(t, c, region, wayland.RegionRequestAdd, int32(0), int32(0), int32(1), int32(1)) | |||
| 171 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, region) | |||
| 172 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |||
| 173 | + | if err := c.Roundtrip(); err != nil { | |||
| 174 | + | t.Fatal(err) | |||
| 175 | + | } | |||
| 176 | + | if got := inputChanged(t, events); got.ID != w.ID || got.All { | |||
| 177 | + | t.Fatalf("initial input: %+v", got) | |||
| 178 | + | } | |||
| 179 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, uint32(0), int32(0), int32(0)) | |||
| 180 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |||
| 181 | + | if err := c.Roundtrip(); err != nil { | |||
| 182 | + | t.Fatal(err) | |||
| 183 | + | } | |||
| 184 | + | unmapped(t, events, w.ID) | |||
| 185 | + | // Remapping requires a new initial configure and an ack before attaching. | |||
| 186 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |||
| 187 | + | if err := c.Roundtrip(); err != nil { | |||
| 188 | + | t.Fatal(err) | |||
| 189 | + | } | |||
| 190 | + | var serial uint32 | |||
| 191 | + | if !s.display.Do(func() { | |||
| 192 | + | for _, v := range s.surfaces { | |||
| 193 | + | if v.xdg != nil && v.xdg.window != nil && v.xdg.window.id == w.ID && len(v.xdg.serials) > 0 { | |||
| 194 | + | serial = v.xdg.serials[len(v.xdg.serials)-1] | |||
| 195 | + | } | |||
| 196 | + | } | |||
| 197 | + | }) { | |||
| 198 | + | t.Fatal("server stopped") | |||
| 199 | + | } | |||
| 200 | + | if serial == 0 { | |||
| 201 | + | t.Fatal("no remap configure") | |||
| 202 | + | } | |||
| 203 | + | requestProtocol(t, c, xdg, xdgshell.SurfaceRequestAckConfigure, serial) | |||
| 204 | + | buffer := shmBuffer(t, c) | |||
| 205 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, buffer, int32(0), int32(0)) | |||
| 206 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |||
| 207 | + | if err := c.Roundtrip(); err != nil { | |||
| 208 | + | t.Fatal(err) | |||
| 209 | + | } | |||
| 210 | + | if got := mapped(t, events, 2*time.Second); got.ID != w.ID { | |||
| 211 | + | t.Fatalf("remapped: %+v", got) | |||
| 212 | + | } | |||
| 213 | + | if got := inputChanged(t, events); got.ID != w.ID || got.All || len(got.Rects) != 1 || got.Rects[0] != (ports.Rect{W: 1, H: 1}) { | |||
| 214 | + | t.Fatalf("remap input: %+v", got) | |||
| 215 | + | } | |||
| 216 | + | } | |||
M internal/adapters/wayland/layer.go +1 -0
| @@ -163,6 +163,7 @@ func (l *layerSurface) unmap() { | |||
| 163 | 163 | s.changePointerFocus(0, 0, 0) | |
| 164 | 164 | } | |
| 165 | 165 | l.mapped = false | |
| 166 | + | l.surface.resetInputEmission() | |
| 166 | 167 | l.configured = false | |
| 167 | 168 | l.acked = false | |
| 168 | 169 | l.serials = nil | |
| @@ -163,6 +163,7 @@ func (l *layerSurface) unmap() { | |||||
| 163 | s.changePointerFocus(0, 0, 0) | 163 | s.changePointerFocus(0, 0, 0) | ||
| 164 | } | 164 | } | ||
| 165 | l.mapped = false | 165 | l.mapped = false | ||
| 166 | + | l.surface.resetInputEmission() | |||
| 166 | l.configured = false | 167 | l.configured = false | ||
| 167 | l.acked = false | 168 | l.acked = false | ||
| 168 | l.serials = nil | 169 | l.serials = nil | ||
M internal/adapters/wayland/xdg.go +1 -0
| @@ -178,6 +178,7 @@ func (w *window) unmap() { | |||
| 178 | 178 | return | |
| 179 | 179 | } | |
| 180 | 180 | w.mapped = false | |
| 181 | + | w.xdg.surface.resetInputEmission() | |
| 181 | 182 | w.xdg.configured = false | |
| 182 | 183 | w.xdg.acked = false | |
| 183 | 184 | w.xdg.serials = nil | |
| @@ -178,6 +178,7 @@ func (w *window) unmap() { | |||||
| 178 | return | 178 | return | ||
| 179 | } | 179 | } | ||
| 180 | w.mapped = false | 180 | w.mapped = false | ||
| 181 | + | w.xdg.surface.resetInputEmission() | |||
| 181 | w.xdg.configured = false | 182 | w.xdg.configured = false | ||
| 182 | w.xdg.acked = false | 183 | w.xdg.acked = false | ||
| 183 | w.xdg.serials = nil | 184 | w.xdg.serials = nil | ||
M internal/core/core.go +8 -0
| @@ -361,16 +361,24 @@ func (c *Core) keyboardFocus() WindowID { | |||
| 361 | 361 | // setLayers splits the mapped layer surfaces by output. A surface without an | |
| 362 | 362 | // output, or on an unknown one, goes to the focused output. | |
| 363 | 363 | func (c *Core) setLayers(all []ports.LayerSurface) { | |
| 364 | + | previous := make(map[WindowID]bool) | |
| 364 | 365 | for _, sc := range c.screens { | |
| 366 | + | for _, l := range sc.layers { | |
| 367 | + | previous[l.ID] = true | |
| 368 | + | } | |
| 365 | 369 | sc.layers = nil | |
| 366 | 370 | } | |
| 367 | 371 | for _, l := range all { | |
| 372 | + | delete(previous, l.ID) | |
| 368 | 373 | sc := c.cur() | |
| 369 | 374 | if i := c.screenIndex(l.Output); i >= 0 { | |
| 370 | 375 | sc = c.screens[i] | |
| 371 | 376 | } | |
| 372 | 377 | sc.layers = append(sc.layers, l) | |
| 373 | 378 | } | |
| 379 | + | for id := range previous { | |
| 380 | + | delete(c.inputRegions, id) | |
| 381 | + | } | |
| 374 | 382 | for _, sc := range c.screens { | |
| 375 | 383 | sc.arrange() | |
| 376 | 384 | } | |
| @@ -361,16 +361,24 @@ func (c *Core) keyboardFocus() WindowID { | |||||
| 361 | // setLayers splits the mapped layer surfaces by output. A surface without an | 361 | // setLayers splits the mapped layer surfaces by output. A surface without an | ||
| 362 | // output, or on an unknown one, goes to the focused output. | 362 | // output, or on an unknown one, goes to the focused output. | ||
| 363 | func (c *Core) setLayers(all []ports.LayerSurface) { | 363 | func (c *Core) setLayers(all []ports.LayerSurface) { | ||
| 364 | + | previous := make(map[WindowID]bool) | |||
| 364 | for _, sc := range c.screens { | 365 | for _, sc := range c.screens { | ||
| 366 | + | for _, l := range sc.layers { | |||
| 367 | + | previous[l.ID] = true | |||
| 368 | + | } | |||
| 365 | sc.layers = nil | 369 | sc.layers = nil | ||
| 366 | } | 370 | } | ||
| 367 | for _, l := range all { | 371 | for _, l := range all { | ||
| 372 | + | delete(previous, l.ID) | |||
| 368 | sc := c.cur() | 373 | sc := c.cur() | ||
| 369 | if i := c.screenIndex(l.Output); i >= 0 { | 374 | if i := c.screenIndex(l.Output); i >= 0 { | ||
| 370 | sc = c.screens[i] | 375 | sc = c.screens[i] | ||
| 371 | } | 376 | } | ||
| 372 | sc.layers = append(sc.layers, l) | 377 | sc.layers = append(sc.layers, l) | ||
| 373 | } | 378 | } | ||
| 379 | + | for id := range previous { | |||
| 380 | + | delete(c.inputRegions, id) | |||
| 381 | + | } | |||
| 374 | for _, sc := range c.screens { | 382 | for _, sc := range c.screens { | ||
| 375 | sc.arrange() | 383 | sc.arrange() | ||
| 376 | } | 384 | } | ||
M internal/core/layer_input_test.go +42 -0
| @@ -315,3 +315,45 @@ func TestLayerInputRegionPassThrough(t *testing.T) { | |||
| 315 | 315 | t.Fatalf("reset focus: %+v", v) | |
| 316 | 316 | } | |
| 317 | 317 | } | |
| 318 | + | ||
| 319 | + | func TestRemovedLayerInputRegionCleared(t *testing.T) { | |
| 320 | + | cfg := config.Defaults() | |
| 321 | + | cfg.Border.Width = 0 | |
| 322 | + | client := make(chan ports.ClientEvent, 16) | |
| 323 | + | input := make(chan ports.InputEvent, 16) | |
| 324 | + | output := make(chan ports.OutputEvent, 16) | |
| 325 | + | commands := make(chan ports.ClientCommand, 256) | |
| 326 | + | scenes := make(chan []ports.Scene, 1) | |
| 327 | + | c, err := core.New(cfg, core.Channels{Client: client, Input: input, Output: output, Commands: commands, Scenes: scenes}) | |
| 328 | + | if err != nil { | |
| 329 | + | t.Fatal(err) | |
| 330 | + | } | |
| 331 | + | ctx, cancel := context.WithCancel(context.Background()) | |
| 332 | + | defer cancel() | |
| 333 | + | go c.Run(ctx) | |
| 334 | + | output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}} | |
| 335 | + | scene(t, scenes) | |
| 336 | + | client <- ports.WindowMapped{ID: 1} | |
| 337 | + | scene(t, scenes) | |
| 338 | + | client <- ports.InputRegionChanged{ID: 1} | |
| 339 | + | scene(t, scenes) // window's empty region must survive layer changes | |
| 340 | + | layer := ports.LayerSurface{ID: 6, Layer: ports.LayerOverlay, Width: 100, Height: 80} | |
| 341 | + | client <- ports.LayerChanged{Layers: []ports.LayerSurface{layer}} | |
| 342 | + | scene(t, scenes) | |
| 343 | + | client <- ports.InputRegionChanged{ID: 6} | |
| 344 | + | scene(t, scenes) | |
| 345 | + | client <- ports.LayerChanged{} | |
| 346 | + | scene(t, scenes) | |
| 347 | + | client <- ports.LayerChanged{Layers: []ports.LayerSurface{layer}} | |
| 348 | + | scene(t, scenes) | |
| 349 | + | input <- ports.PointerMotion{X: 20, Y: 20} | |
| 350 | + | if v := next(t, commands, anyOf[ports.PointerFocus]); v.ID != 6 { | |
| 351 | + | t.Fatalf("remapped layer retains old region: %+v", v) | |
| 352 | + | } | |
| 353 | + | client <- ports.LayerChanged{} | |
| 354 | + | scene(t, scenes) | |
| 355 | + | input <- ports.PointerMotion{X: 21, Y: 21} | |
| 356 | + | if v := next(t, commands, anyOf[ports.PointerFocus]); v.ID != 0 { | |
| 357 | + | t.Fatalf("window region removed with layer: %+v", v) | |
| 358 | + | } | |
| 359 | + | } | |
| @@ -315,3 +315,45 @@ func TestLayerInputRegionPassThrough(t *testing.T) { | |||||
| 315 | t.Fatalf("reset focus: %+v", v) | 315 | t.Fatalf("reset focus: %+v", v) | ||
| 316 | } | 316 | } | ||
| 317 | } | 317 | } | ||
| 318 | + | ||||
| 319 | + | func TestRemovedLayerInputRegionCleared(t *testing.T) { | |||
| 320 | + | cfg := config.Defaults() | |||
| 321 | + | cfg.Border.Width = 0 | |||
| 322 | + | client := make(chan ports.ClientEvent, 16) | |||
| 323 | + | input := make(chan ports.InputEvent, 16) | |||
| 324 | + | output := make(chan ports.OutputEvent, 16) | |||
| 325 | + | commands := make(chan ports.ClientCommand, 256) | |||
| 326 | + | scenes := make(chan []ports.Scene, 1) | |||
| 327 | + | c, err := core.New(cfg, core.Channels{Client: client, Input: input, Output: output, Commands: commands, Scenes: scenes}) | |||
| 328 | + | if err != nil { | |||
| 329 | + | t.Fatal(err) | |||
| 330 | + | } | |||
| 331 | + | ctx, cancel := context.WithCancel(context.Background()) | |||
| 332 | + | defer cancel() | |||
| 333 | + | go c.Run(ctx) | |||
| 334 | + | output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}} | |||
| 335 | + | scene(t, scenes) | |||
| 336 | + | client <- ports.WindowMapped{ID: 1} | |||
| 337 | + | scene(t, scenes) | |||
| 338 | + | client <- ports.InputRegionChanged{ID: 1} | |||
| 339 | + | scene(t, scenes) // window's empty region must survive layer changes | |||
| 340 | + | layer := ports.LayerSurface{ID: 6, Layer: ports.LayerOverlay, Width: 100, Height: 80} | |||
| 341 | + | client <- ports.LayerChanged{Layers: []ports.LayerSurface{layer}} | |||
| 342 | + | scene(t, scenes) | |||
| 343 | + | client <- ports.InputRegionChanged{ID: 6} | |||
| 344 | + | scene(t, scenes) | |||
| 345 | + | client <- ports.LayerChanged{} | |||
| 346 | + | scene(t, scenes) | |||
| 347 | + | client <- ports.LayerChanged{Layers: []ports.LayerSurface{layer}} | |||
| 348 | + | scene(t, scenes) | |||
| 349 | + | input <- ports.PointerMotion{X: 20, Y: 20} | |||
| 350 | + | if v := next(t, commands, anyOf[ports.PointerFocus]); v.ID != 6 { | |||
| 351 | + | t.Fatalf("remapped layer retains old region: %+v", v) | |||
| 352 | + | } | |||
| 353 | + | client <- ports.LayerChanged{} | |||
| 354 | + | scene(t, scenes) | |||
| 355 | + | input <- ports.PointerMotion{X: 21, Y: 21} | |||
| 356 | + | if v := next(t, commands, anyOf[ports.PointerFocus]); v.ID != 0 { | |||
| 357 | + | t.Fatalf("window region removed with layer: %+v", v) | |||
| 358 | + | } | |||
| 359 | + | } | |||