feat(wayland): draw subsurfaces, honour window geometry, offer KDE server decoration
Firefox and Zen draw their GPU content in a wl_subsurface: the surface tree is flattened into SurfaceContent.Children and drawn by the renderer. xdg window geometry places the visible window on its slot, clipping client shadows, and offsets pointer coordinates. GTK4 gets org_kde_kwin_server_decoration and stops drawing its own title bar. Subsurfaces follow their root's output and scale.
- commit
- 18b66fe131643337bf0b12bc01a2d67f41957fd7
- author
- brice · · 2026-09-25 08:04 UTC
- parent
- 1bc6715
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
10 files · +553 -60
M internal/adapters/vulkan/renderer.go +31 -21
| @@ -390,42 +390,52 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 390 | 390 | uploads = append(uploads, upload{rect: rect, offset: used, pixels: content.Pixels, stride: content.Stride, opaque: content.Opaque, dst: dst, src: src}) | |
| 391 | 391 | used += rect.Dx() * rect.Dy() * 4 | |
| 392 | 392 | } | |
| 393 | - | // place draws content with its top-left at (x, y) logical, clipped to w×h logical. | |
| 394 | - | place := func(content *ports.SurfaceContent, x, y, w, h int) { | |
| 393 | + | // drawSurface draws one surface buffer with its origin at (x, y) | |
| 394 | + | // logical, clipped to clip (logical). | |
| 395 | + | drawSurface := func(content *ports.SurfaceContent, x, y int, clip image.Rectangle) { | |
| 395 | 396 | lw, lh := content.LogicalW, content.LogicalH | |
| 396 | 397 | if lw <= 0 || lh <= 0 { | |
| 397 | 398 | lw, lh = content.Width, content.Height | |
| 398 | 399 | } | |
| 399 | - | if content.Empty() || lw <= 0 || lh <= 0 { | |
| 400 | + | if content.Pixels == nil && content.DMABuf == nil || lw <= 0 || lh <= 0 || content.Width <= 0 || content.Height <= 0 { | |
| 400 | 401 | return | |
| 401 | 402 | } | |
| 402 | 403 | if content.DMABuf == nil && (content.Stride < content.Width*4 || len(content.Pixels) < (content.Height-1)*content.Stride+content.Width*4) { | |
| 403 | 404 | return | |
| 404 | 405 | } | |
| 405 | - | vw, vh := min(lw, w), min(lh, h) | |
| 406 | - | if vw <= 0 || vh <= 0 { | |
| 407 | - | return | |
| 408 | - | } | |
| 409 | - | // Buffer pixels per logical pixel on each axis. | |
| 410 | - | src := image.Rect(0, 0, vw*content.Width/lw, vh*content.Height/lh) | |
| 411 | - | dst := physRect(x, y, vw, vh) | |
| 406 | + | full := physRect(x, y, lw, lh) | |
| 412 | 407 | // A buffer drawn at the physical size (fractional-scale clients round | |
| 413 | 408 | // w*scale, we round per edge) is copied 1:1, never resampled for 1px. | |
| 414 | 409 | near := func(a, b int) bool { return a-b <= 1 && b-a <= 1 } | |
| 415 | - | if near(src.Dx(), dst.Dx()) && near(src.Dy(), dst.Dy()) { | |
| 416 | - | dst.Max = dst.Min.Add(src.Size()) | |
| 417 | - | // Clip to the body: the buffer may be 1px wider than the slot. | |
| 418 | - | clip := physRect(x, y, w, h) | |
| 419 | - | if dst.Max.X > clip.Max.X { | |
| 420 | - | src.Max.X -= dst.Max.X - clip.Max.X | |
| 421 | - | dst.Max.X = clip.Max.X | |
| 410 | + | if near(full.Dx(), content.Width) && near(full.Dy(), content.Height) { | |
| 411 | + | full.Max = full.Min.Add(image.Pt(content.Width, content.Height)) | |
| 412 | + | } | |
| 413 | + | dst := full.Intersect(physRect(clip.Min.X, clip.Min.Y, clip.Dx(), clip.Dy())) | |
| 414 | + | if dst.Empty() { | |
| 415 | + | return | |
| 416 | + | } | |
| 417 | + | // The visible part of dst in buffer pixels. | |
| 418 | + | bx := func(v int) int { return (v - full.Min.X) * content.Width / full.Dx() } | |
| 419 | + | by := func(v int) int { return (v - full.Min.Y) * content.Height / full.Dy() } | |
| 420 | + | src := image.Rect(bx(dst.Min.X), by(dst.Min.Y), bx(dst.Max.X), by(dst.Max.Y)) | |
| 421 | + | addContent(dst, src, content) | |
| 422 | + | } | |
| 423 | + | // place draws a surface tree with its window geometry at (x, y) | |
| 424 | + | // logical, clipped to w×h logical: client shadows fall outside. | |
| 425 | + | place := func(content *ports.SurfaceContent, x, y, w, h int) { | |
| 426 | + | clip := image.Rect(x, y, x+w, y+h) | |
| 427 | + | ox, oy := x-content.Geometry.X, y-content.Geometry.Y | |
| 428 | + | for i := range content.Children { | |
| 429 | + | if ch := &content.Children[i]; ch.Below { | |
| 430 | + | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip) | |
| 422 | 431 | } | |
| 423 | - | if dst.Max.Y > clip.Max.Y { | |
| 424 | - | src.Max.Y -= dst.Max.Y - clip.Max.Y | |
| 425 | - | dst.Max.Y = clip.Max.Y | |
| 432 | + | } | |
| 433 | + | drawSurface(content, ox, oy, clip) | |
| 434 | + | for i := range content.Children { | |
| 435 | + | if ch := &content.Children[i]; !ch.Below { | |
| 436 | + | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip) | |
| 426 | 437 | } | |
| 427 | 438 | } | |
| 428 | - | addContent(dst, src, content) | |
| 429 | 439 | } | |
| 430 | 440 | fullscreen := false | |
| 431 | 441 | for _, w := range s.Windows { | |
| @@ -390,42 +390,52 @@ func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 390 | uploads = append(uploads, upload{rect: rect, offset: used, pixels: content.Pixels, stride: content.Stride, opaque: content.Opaque, dst: dst, src: src}) | 390 | uploads = append(uploads, upload{rect: rect, offset: used, pixels: content.Pixels, stride: content.Stride, opaque: content.Opaque, dst: dst, src: src}) | ||
| 391 | used += rect.Dx() * rect.Dy() * 4 | 391 | used += rect.Dx() * rect.Dy() * 4 | ||
| 392 | } | 392 | } | ||
| 393 | - | // place draws content with its top-left at (x, y) logical, clipped to w×h logical. | 393 | + | // drawSurface draws one surface buffer with its origin at (x, y) |
| 394 | - | place := func(content *ports.SurfaceContent, x, y, w, h int) { | 394 | + | // logical, clipped to clip (logical). |
| 395 | + | drawSurface := func(content *ports.SurfaceContent, x, y int, clip image.Rectangle) { | |||
| 395 | lw, lh := content.LogicalW, content.LogicalH | 396 | lw, lh := content.LogicalW, content.LogicalH | ||
| 396 | if lw <= 0 || lh <= 0 { | 397 | if lw <= 0 || lh <= 0 { | ||
| 397 | lw, lh = content.Width, content.Height | 398 | lw, lh = content.Width, content.Height | ||
| 398 | } | 399 | } | ||
| 399 | - | if content.Empty() || lw <= 0 || lh <= 0 { | 400 | + | if content.Pixels == nil && content.DMABuf == nil || lw <= 0 || lh <= 0 || content.Width <= 0 || content.Height <= 0 { |
| 400 | return | 401 | return | ||
| 401 | } | 402 | } | ||
| 402 | if content.DMABuf == nil && (content.Stride < content.Width*4 || len(content.Pixels) < (content.Height-1)*content.Stride+content.Width*4) { | 403 | if content.DMABuf == nil && (content.Stride < content.Width*4 || len(content.Pixels) < (content.Height-1)*content.Stride+content.Width*4) { | ||
| 403 | return | 404 | return | ||
| 404 | } | 405 | } | ||
| 405 | - | vw, vh := min(lw, w), min(lh, h) | 406 | + | full := physRect(x, y, lw, lh) |
| 406 | - | if vw <= 0 || vh <= 0 { | |||
| 407 | - | return | |||
| 408 | - | } | |||
| 409 | - | // Buffer pixels per logical pixel on each axis. | |||
| 410 | - | src := image.Rect(0, 0, vw*content.Width/lw, vh*content.Height/lh) | |||
| 411 | - | dst := physRect(x, y, vw, vh) | |||
| 412 | // A buffer drawn at the physical size (fractional-scale clients round | 407 | // A buffer drawn at the physical size (fractional-scale clients round | ||
| 413 | // w*scale, we round per edge) is copied 1:1, never resampled for 1px. | 408 | // w*scale, we round per edge) is copied 1:1, never resampled for 1px. | ||
| 414 | near := func(a, b int) bool { return a-b <= 1 && b-a <= 1 } | 409 | near := func(a, b int) bool { return a-b <= 1 && b-a <= 1 } | ||
| 415 | - | if near(src.Dx(), dst.Dx()) && near(src.Dy(), dst.Dy()) { | 410 | + | if near(full.Dx(), content.Width) && near(full.Dy(), content.Height) { |
| 416 | - | dst.Max = dst.Min.Add(src.Size()) | 411 | + | full.Max = full.Min.Add(image.Pt(content.Width, content.Height)) |
| 417 | - | // Clip to the body: the buffer may be 1px wider than the slot. | 412 | + | } |
| 418 | - | clip := physRect(x, y, w, h) | 413 | + | dst := full.Intersect(physRect(clip.Min.X, clip.Min.Y, clip.Dx(), clip.Dy())) |
| 419 | - | if dst.Max.X > clip.Max.X { | 414 | + | if dst.Empty() { |
| 420 | - | src.Max.X -= dst.Max.X - clip.Max.X | 415 | + | return |
| 421 | - | dst.Max.X = clip.Max.X | 416 | + | } |
| 417 | + | // The visible part of dst in buffer pixels. | |||
| 418 | + | bx := func(v int) int { return (v - full.Min.X) * content.Width / full.Dx() } | |||
| 419 | + | by := func(v int) int { return (v - full.Min.Y) * content.Height / full.Dy() } | |||
| 420 | + | src := image.Rect(bx(dst.Min.X), by(dst.Min.Y), bx(dst.Max.X), by(dst.Max.Y)) | |||
| 421 | + | addContent(dst, src, content) | |||
| 422 | + | } | |||
| 423 | + | // place draws a surface tree with its window geometry at (x, y) | |||
| 424 | + | // logical, clipped to w×h logical: client shadows fall outside. | |||
| 425 | + | place := func(content *ports.SurfaceContent, x, y, w, h int) { | |||
| 426 | + | clip := image.Rect(x, y, x+w, y+h) | |||
| 427 | + | ox, oy := x-content.Geometry.X, y-content.Geometry.Y | |||
| 428 | + | for i := range content.Children { | |||
| 429 | + | if ch := &content.Children[i]; ch.Below { | |||
| 430 | + | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip) | |||
| 422 | } | 431 | } | ||
| 423 | - | if dst.Max.Y > clip.Max.Y { | 432 | + | } |
| 424 | - | src.Max.Y -= dst.Max.Y - clip.Max.Y | 433 | + | drawSurface(content, ox, oy, clip) |
| 425 | - | dst.Max.Y = clip.Max.Y | 434 | + | for i := range content.Children { |
| 435 | + | if ch := &content.Children[i]; !ch.Below { | |||
| 436 | + | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip) | |||
| 426 | } | 437 | } | ||
| 427 | } | 438 | } | ||
| 428 | - | addContent(dst, src, content) | |||
| 429 | } | 439 | } | ||
| 430 | fullscreen := false | 440 | fullscreen := false | ||
| 431 | for _, w := range s.Windows { | 441 | for _, w := range s.Windows { | ||
M internal/adapters/vulkan/renderer_test.go +56 -0
| @@ -247,3 +247,59 @@ func TestRendererScale(t *testing.T) { | |||
| 247 | 247 | t.Errorf("clipped column = %v", got) | |
| 248 | 248 | } | |
| 249 | 249 | } | |
| 250 | + | ||
| 251 | + | // solidContent is a w×h B8G8R8A8 buffer of one color. | |
| 252 | + | func solidContent(w, h int, c color.RGBA) ports.SurfaceContent { | |
| 253 | + | px := make([]byte, w*h*4) | |
| 254 | + | for i := 0; i < len(px); i += 4 { | |
| 255 | + | px[i], px[i+1], px[i+2], px[i+3] = c.B, c.G, c.R, 255 | |
| 256 | + | } | |
| 257 | + | return ports.SurfaceContent{Width: w, Height: h, Stride: w * 4, Pixels: px} | |
| 258 | + | } | |
| 259 | + | ||
| 260 | + | // The window geometry lands on the window rect and the client shadow | |
| 261 | + | // around it is clipped; subsurfaces draw from the root origin, above or | |
| 262 | + | // below it. | |
| 263 | + | func TestRendererGeometryAndSubsurfaces(t *testing.T) { | |
| 264 | + | r, err := New(64, 48) | |
| 265 | + | if err != nil { | |
| 266 | + | t.Skipf("Vulkan unavailable: %v", err) | |
| 267 | + | } | |
| 268 | + | defer r.Close() | |
| 269 | + | red, green, blue := color.RGBA{255, 0, 0, 255}, color.RGBA{0, 255, 0, 255}, color.RGBA{0, 0, 255, 255} | |
| 270 | + | bg := color.RGBA{16, 32, 48, 255} | |
| 271 | + | scene := ports.Scene{Background: "#102030", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{X: 10, Y: 10, W: 20, H: 10}, Borderless: true}}} | |
| 272 | + | // A 30×20 surface whose window is the 20×10 at (5, 5): a 5px shadow. | |
| 273 | + | root := solidContent(30, 20, red) | |
| 274 | + | root.Geometry = ports.Rect{X: 5, Y: 5, W: 20, H: 10} | |
| 275 | + | // Green covers the window's top-left 4×4 corner; blue sits below the | |
| 276 | + | // root, hidden by it. | |
| 277 | + | root.Children = []ports.Subsurface{ | |
| 278 | + | {X: 3, Y: 3, Below: true, SurfaceContent: solidContent(30, 20, blue)}, | |
| 279 | + | {X: 5, Y: 5, SurfaceContent: solidContent(4, 4, green)}, | |
| 280 | + | } | |
| 281 | + | if err := r.Render(scene, map[ports.WindowID]ports.SurfaceContent{1: root}); err != nil { | |
| 282 | + | t.Fatal(err) | |
| 283 | + | } | |
| 284 | + | px := r.Pixels() | |
| 285 | + | for _, c := range []struct { | |
| 286 | + | x, y int | |
| 287 | + | want color.RGBA | |
| 288 | + | }{ | |
| 289 | + | {10, 10, green}, {13, 13, green}, {14, 14, red}, {29, 19, red}, | |
| 290 | + | // The shadow and the child reaching past it are clipped. | |
| 291 | + | {9, 10, bg}, {30, 19, bg}, {29, 20, bg}, | |
| 292 | + | } { | |
| 293 | + | if got := px.At(c.x, c.y); got != c.want { | |
| 294 | + | t.Errorf("At(%d,%d)=%v want %v", c.x, c.y, got, c.want) | |
| 295 | + | } | |
| 296 | + | } | |
| 297 | + | // A root with no buffer of its own still shows its children. | |
| 298 | + | only := ports.SurfaceContent{Children: []ports.Subsurface{{SurfaceContent: solidContent(20, 10, blue)}}} | |
| 299 | + | if err := r.Render(scene, map[ports.WindowID]ports.SurfaceContent{1: only}); err != nil { | |
| 300 | + | t.Fatal(err) | |
| 301 | + | } | |
| 302 | + | if got := r.Pixels().At(20, 15); got != blue { | |
| 303 | + | t.Errorf("child only: %v", got) | |
| 304 | + | } | |
| 305 | + | } | |
| @@ -247,3 +247,59 @@ func TestRendererScale(t *testing.T) { | |||||
| 247 | t.Errorf("clipped column = %v", got) | 247 | t.Errorf("clipped column = %v", got) | ||
| 248 | } | 248 | } | ||
| 249 | } | 249 | } | ||
| 250 | + | ||||
| 251 | + | // solidContent is a w×h B8G8R8A8 buffer of one color. | |||
| 252 | + | func solidContent(w, h int, c color.RGBA) ports.SurfaceContent { | |||
| 253 | + | px := make([]byte, w*h*4) | |||
| 254 | + | for i := 0; i < len(px); i += 4 { | |||
| 255 | + | px[i], px[i+1], px[i+2], px[i+3] = c.B, c.G, c.R, 255 | |||
| 256 | + | } | |||
| 257 | + | return ports.SurfaceContent{Width: w, Height: h, Stride: w * 4, Pixels: px} | |||
| 258 | + | } | |||
| 259 | + | ||||
| 260 | + | // The window geometry lands on the window rect and the client shadow | |||
| 261 | + | // around it is clipped; subsurfaces draw from the root origin, above or | |||
| 262 | + | // below it. | |||
| 263 | + | func TestRendererGeometryAndSubsurfaces(t *testing.T) { | |||
| 264 | + | r, err := New(64, 48) | |||
| 265 | + | if err != nil { | |||
| 266 | + | t.Skipf("Vulkan unavailable: %v", err) | |||
| 267 | + | } | |||
| 268 | + | defer r.Close() | |||
| 269 | + | red, green, blue := color.RGBA{255, 0, 0, 255}, color.RGBA{0, 255, 0, 255}, color.RGBA{0, 0, 255, 255} | |||
| 270 | + | bg := color.RGBA{16, 32, 48, 255} | |||
| 271 | + | scene := ports.Scene{Background: "#102030", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{X: 10, Y: 10, W: 20, H: 10}, Borderless: true}}} | |||
| 272 | + | // A 30×20 surface whose window is the 20×10 at (5, 5): a 5px shadow. | |||
| 273 | + | root := solidContent(30, 20, red) | |||
| 274 | + | root.Geometry = ports.Rect{X: 5, Y: 5, W: 20, H: 10} | |||
| 275 | + | // Green covers the window's top-left 4×4 corner; blue sits below the | |||
| 276 | + | // root, hidden by it. | |||
| 277 | + | root.Children = []ports.Subsurface{ | |||
| 278 | + | {X: 3, Y: 3, Below: true, SurfaceContent: solidContent(30, 20, blue)}, | |||
| 279 | + | {X: 5, Y: 5, SurfaceContent: solidContent(4, 4, green)}, | |||
| 280 | + | } | |||
| 281 | + | if err := r.Render(scene, map[ports.WindowID]ports.SurfaceContent{1: root}); err != nil { | |||
| 282 | + | t.Fatal(err) | |||
| 283 | + | } | |||
| 284 | + | px := r.Pixels() | |||
| 285 | + | for _, c := range []struct { | |||
| 286 | + | x, y int | |||
| 287 | + | want color.RGBA | |||
| 288 | + | }{ | |||
| 289 | + | {10, 10, green}, {13, 13, green}, {14, 14, red}, {29, 19, red}, | |||
| 290 | + | // The shadow and the child reaching past it are clipped. | |||
| 291 | + | {9, 10, bg}, {30, 19, bg}, {29, 20, bg}, | |||
| 292 | + | } { | |||
| 293 | + | if got := px.At(c.x, c.y); got != c.want { | |||
| 294 | + | t.Errorf("At(%d,%d)=%v want %v", c.x, c.y, got, c.want) | |||
| 295 | + | } | |||
| 296 | + | } | |||
| 297 | + | // A root with no buffer of its own still shows its children. | |||
| 298 | + | only := ports.SurfaceContent{Children: []ports.Subsurface{{SurfaceContent: solidContent(20, 10, blue)}}} | |||
| 299 | + | if err := r.Render(scene, map[ports.WindowID]ports.SurfaceContent{1: only}); err != nil { | |||
| 300 | + | t.Fatal(err) | |||
| 301 | + | } | |||
| 302 | + | if got := r.Pixels().At(20, 15); got != blue { | |||
| 303 | + | t.Errorf("child only: %v", got) | |||
| 304 | + | } | |||
| 305 | + | } | |||
M internal/adapters/wayland/decoration.go +31 -1
| @@ -1,6 +1,8 @@ | |||
| 1 | 1 | package wayland | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "github.com/bnema/purego-libwayland/protocol/kdedecoration" | |
| 5 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 4 | 6 | "github.com/bnema/purego-libwayland/protocol/xdgdecoration" | |
| 5 | 7 | "github.com/bnema/purego-libwayland/protocol/xdgshell" | |
| 6 | 8 | "github.com/bnema/purego-libwayland/server" | |
| @@ -11,9 +13,37 @@ import ( | |||
| 11 | 13 | type decorationManager struct{ s *Server } | |
| 12 | 14 | ||
| 13 | 15 | func registerDecoration(d *server.Display, s *Server) error { | |
| 14 | - | return xdgdecoration.NewZxdgDecorationManagerV1Global(d, 1, func(c server.Client, v, id uint32) { | |
| 16 | + | err := xdgdecoration.NewZxdgDecorationManagerV1Global(d, 1, func(c server.Client, v, id uint32) { | |
| 15 | 17 | _, _ = xdgdecoration.NewZxdgDecorationManagerV1(c, int32(v), id, decorationManager{s}) | |
| 16 | 18 | }) | |
| 19 | + | if err != nil { | |
| 20 | + | return err | |
| 21 | + | } | |
| 22 | + | // GTK only speaks KDE's older protocol: without it GTK4 draws its own | |
| 23 | + | // title bar. | |
| 24 | + | return kdedecoration.NewOrgKdeKwinServerDecorationManagerGlobal(d, 1, func(c server.Client, v, id uint32) { | |
| 25 | + | m, err := kdedecoration.NewOrgKdeKwinServerDecorationManager(c, int32(v), id, kdeManager{}) | |
| 26 | + | if err == nil { | |
| 27 | + | m.SendDefaultMode(uint32(kdedecoration.OrgKdeKwinServerDecorationManagerModeServer)) | |
| 28 | + | } | |
| 29 | + | }) | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | type kdeManager struct{} | |
| 33 | + | ||
| 34 | + | func (kdeManager) Create(r *kdedecoration.OrgKdeKwinServerDecorationManager, id uint32, _ *wayland.Surface) { | |
| 35 | + | if d, err := kdedecoration.NewOrgKdeKwinServerDecoration(r.Client(), r.Version(), id, kdeDecoration{}); err == nil { | |
| 36 | + | d.SendMode(uint32(kdedecoration.OrgKdeKwinServerDecorationModeServer)) | |
| 37 | + | } | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | type kdeDecoration struct{} | |
| 41 | + | ||
| 42 | + | func (kdeDecoration) Release(*kdedecoration.OrgKdeKwinServerDecoration) {} | |
| 43 | + | ||
| 44 | + | // Any requested mode is answered with server-side, like xdg-decoration. | |
| 45 | + | func (kdeDecoration) RequestMode(r *kdedecoration.OrgKdeKwinServerDecoration, _ uint32) { | |
| 46 | + | r.SendMode(uint32(kdedecoration.OrgKdeKwinServerDecorationModeServer)) | |
| 17 | 47 | } | |
| 18 | 48 | ||
| 19 | 49 | func (decorationManager) Destroy(*xdgdecoration.ZxdgDecorationManagerV1) {} | |
| @@ -1,6 +1,8 @@ | |||||
| 1 | package wayland | 1 | package wayland | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "github.com/bnema/purego-libwayland/protocol/kdedecoration" | |||
| 5 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 4 | "github.com/bnema/purego-libwayland/protocol/xdgdecoration" | 6 | "github.com/bnema/purego-libwayland/protocol/xdgdecoration" | ||
| 5 | "github.com/bnema/purego-libwayland/protocol/xdgshell" | 7 | "github.com/bnema/purego-libwayland/protocol/xdgshell" | ||
| 6 | "github.com/bnema/purego-libwayland/server" | 8 | "github.com/bnema/purego-libwayland/server" | ||
| @@ -11,9 +13,37 @@ import ( | |||||
| 11 | type decorationManager struct{ s *Server } | 13 | type decorationManager struct{ s *Server } | ||
| 12 | 14 | ||||
| 13 | func registerDecoration(d *server.Display, s *Server) error { | 15 | func registerDecoration(d *server.Display, s *Server) error { | ||
| 14 | - | return xdgdecoration.NewZxdgDecorationManagerV1Global(d, 1, func(c server.Client, v, id uint32) { | 16 | + | err := xdgdecoration.NewZxdgDecorationManagerV1Global(d, 1, func(c server.Client, v, id uint32) { |
| 15 | _, _ = xdgdecoration.NewZxdgDecorationManagerV1(c, int32(v), id, decorationManager{s}) | 17 | _, _ = xdgdecoration.NewZxdgDecorationManagerV1(c, int32(v), id, decorationManager{s}) | ||
| 16 | }) | 18 | }) | ||
| 19 | + | if err != nil { | |||
| 20 | + | return err | |||
| 21 | + | } | |||
| 22 | + | // GTK only speaks KDE's older protocol: without it GTK4 draws its own | |||
| 23 | + | // title bar. | |||
| 24 | + | return kdedecoration.NewOrgKdeKwinServerDecorationManagerGlobal(d, 1, func(c server.Client, v, id uint32) { | |||
| 25 | + | m, err := kdedecoration.NewOrgKdeKwinServerDecorationManager(c, int32(v), id, kdeManager{}) | |||
| 26 | + | if err == nil { | |||
| 27 | + | m.SendDefaultMode(uint32(kdedecoration.OrgKdeKwinServerDecorationManagerModeServer)) | |||
| 28 | + | } | |||
| 29 | + | }) | |||
| 30 | + | } | |||
| 31 | + | ||||
| 32 | + | type kdeManager struct{} | |||
| 33 | + | ||||
| 34 | + | func (kdeManager) Create(r *kdedecoration.OrgKdeKwinServerDecorationManager, id uint32, _ *wayland.Surface) { | |||
| 35 | + | if d, err := kdedecoration.NewOrgKdeKwinServerDecoration(r.Client(), r.Version(), id, kdeDecoration{}); err == nil { | |||
| 36 | + | d.SendMode(uint32(kdedecoration.OrgKdeKwinServerDecorationModeServer)) | |||
| 37 | + | } | |||
| 38 | + | } | |||
| 39 | + | ||||
| 40 | + | type kdeDecoration struct{} | |||
| 41 | + | ||||
| 42 | + | func (kdeDecoration) Release(*kdedecoration.OrgKdeKwinServerDecoration) {} | |||
| 43 | + | ||||
| 44 | + | // Any requested mode is answered with server-side, like xdg-decoration. | |||
| 45 | + | func (kdeDecoration) RequestMode(r *kdedecoration.OrgKdeKwinServerDecoration, _ uint32) { | |||
| 46 | + | r.SendMode(uint32(kdedecoration.OrgKdeKwinServerDecorationModeServer)) | |||
| 17 | } | 47 | } | ||
| 18 | 48 | ||||
| 19 | func (decorationManager) Destroy(*xdgdecoration.ZxdgDecorationManagerV1) {} | 49 | func (decorationManager) Destroy(*xdgdecoration.ZxdgDecorationManagerV1) {} | ||
M internal/adapters/wayland/globals.go +106 -14
| @@ -82,29 +82,120 @@ func (c subcompositor) GetSubsurface(r *wayland.Subcompositor, id uint32, w, par | |||
| 82 | 82 | if w == nil || parent == nil { | |
| 83 | 83 | return | |
| 84 | 84 | } | |
| 85 | - | state := c.server.surfaces[w.Resource] | |
| 86 | - | if state == nil { | |
| 85 | + | state, up := c.server.surfaces[w.Resource], c.server.surfaces[parent.Resource] | |
| 86 | + | if state == nil || up == nil { | |
| 87 | 87 | return | |
| 88 | 88 | } | |
| 89 | - | if state.kind != roleNone { | |
| 89 | + | if state.kind != roleNone && state.kind != roleSubsurface || state.sub.role != nil { | |
| 90 | 90 | r.PostError(uint32(wayland.SubcompositorErrorBadSurface), "surface already has role") | |
| 91 | 91 | return | |
| 92 | 92 | } | |
| 93 | - | if sub, err := wayland.NewSubsurface(r.Client(), 1, id, subsurface{state}); err == nil { | |
| 94 | - | state.kind = roleSubsurface | |
| 95 | - | state.role = func(bool) {} | |
| 96 | - | sub.OnDestroy = func() { state.role = nil } | |
| 93 | + | // A surface cannot be its own ancestor. | |
| 94 | + | for p := up; p != nil; p = p.sub.parent { | |
| 95 | + | if p == state { | |
| 96 | + | r.PostError(uint32(wayland.SubcompositorErrorBadParent), "parent is a descendant") | |
| 97 | + | return | |
| 98 | + | } | |
| 99 | + | } | |
| 100 | + | sub, err := wayland.NewSubsurface(r.Client(), 1, id, subsurface{state}) | |
| 101 | + | if err != nil { | |
| 102 | + | return | |
| 103 | + | } | |
| 104 | + | state.kind = roleSubsurface | |
| 105 | + | state.role = func(bool) {} | |
| 106 | + | state.sub = subState{role: sub, parent: up, children: state.sub.children} | |
| 107 | + | up.sub.children = append(up.sub.children, state) | |
| 108 | + | // The child follows its root window's output and scale. | |
| 109 | + | state.sendScale() | |
| 110 | + | sub.OnDestroy = func() { | |
| 111 | + | if state.sub.role != sub { | |
| 112 | + | return | |
| 113 | + | } | |
| 114 | + | state.sub.role = nil | |
| 115 | + | state.role = nil | |
| 116 | + | state.detach() | |
| 97 | 117 | } | |
| 98 | 118 | } | |
| 99 | 119 | ||
| 120 | + | // Subsurfaces are drawn as their own commits arrive (desynchronized): the | |
| 121 | + | // synchronized mode's cached state is not implemented. | |
| 100 | 122 | type subsurface struct{ surface *surface } | |
| 101 | 123 | ||
| 102 | - | func (subsurface) Destroy(*wayland.Subsurface) {} | |
| 103 | - | func (subsurface) SetPosition(*wayland.Subsurface, int32, int32) {} | |
| 104 | - | func (subsurface) PlaceAbove(*wayland.Subsurface, *wayland.Surface) {} | |
| 105 | - | func (subsurface) PlaceBelow(*wayland.Subsurface, *wayland.Surface) {} | |
| 106 | - | func (subsurface) SetSync(*wayland.Subsurface) {} | |
| 107 | - | func (subsurface) SetDesync(*wayland.Subsurface) {} | |
| 124 | + | func (subsurface) Destroy(*wayland.Subsurface) {} | |
| 125 | + | func (s subsurface) SetPosition(_ *wayland.Subsurface, x, y int32) { | |
| 126 | + | s.surface.sub.pendX, s.surface.sub.pendY, s.surface.sub.moved = int(x), int(y), true | |
| 127 | + | } | |
| 128 | + | func (s subsurface) PlaceAbove(r *wayland.Subsurface, sibling *wayland.Surface) { | |
| 129 | + | s.restack(r, sibling, true) | |
| 130 | + | } | |
| 131 | + | func (s subsurface) PlaceBelow(r *wayland.Subsurface, sibling *wayland.Surface) { | |
| 132 | + | s.restack(r, sibling, false) | |
| 133 | + | } | |
| 134 | + | func (subsurface) SetSync(*wayland.Subsurface) {} | |
| 135 | + | func (subsurface) SetDesync(*wayland.Subsurface) {} | |
| 136 | + | ||
| 137 | + | // restack moves the surface next to a sibling or its parent. The order | |
| 138 | + | // applies at once rather than on the parent commit. | |
| 139 | + | func (s subsurface) restack(r *wayland.Subsurface, sibling *wayland.Surface, above bool) { | |
| 140 | + | me := s.surface | |
| 141 | + | p := me.sub.parent | |
| 142 | + | if p == nil || sibling == nil { | |
| 143 | + | return | |
| 144 | + | } | |
| 145 | + | other := me.server.surfaces[sibling.Resource] | |
| 146 | + | if other == me || other == nil || other != p && other.sub.parent != p { | |
| 147 | + | r.PostError(uint32(wayland.SubsurfaceErrorBadSurface), "not a sibling or the parent") | |
| 148 | + | return | |
| 149 | + | } | |
| 150 | + | list := p.sub.children | |
| 151 | + | for i, ch := range list { | |
| 152 | + | if ch == me { | |
| 153 | + | list = append(list[:i:i], list[i+1:]...) | |
| 154 | + | break | |
| 155 | + | } | |
| 156 | + | } | |
| 157 | + | if other == p { | |
| 158 | + | // Relative to the parent: just above it or just below it. | |
| 159 | + | me.sub.below = !above | |
| 160 | + | if above { | |
| 161 | + | list = append([]*surface{me}, list...) | |
| 162 | + | } else { | |
| 163 | + | list = append(list, me) | |
| 164 | + | } | |
| 165 | + | // Keep below children first so the flattening stays ordered. | |
| 166 | + | sortBelowFirst(list) | |
| 167 | + | } else { | |
| 168 | + | me.sub.below = other.sub.below | |
| 169 | + | at := len(list) | |
| 170 | + | for i, ch := range list { | |
| 171 | + | if ch == other { | |
| 172 | + | at = i | |
| 173 | + | if above { | |
| 174 | + | at++ | |
| 175 | + | } | |
| 176 | + | break | |
| 177 | + | } | |
| 178 | + | } | |
| 179 | + | list = append(list[:at:at], append([]*surface{me}, list[at:]...)...) | |
| 180 | + | } | |
| 181 | + | p.sub.children = list | |
| 182 | + | me.redraw() | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | // sortBelowFirst moves children below their parent ahead of those above, | |
| 186 | + | // keeping each group's order. | |
| 187 | + | func sortBelowFirst(list []*surface) { | |
| 188 | + | below := make([]*surface, 0, len(list)) | |
| 189 | + | above := make([]*surface, 0, len(list)) | |
| 190 | + | for _, ch := range list { | |
| 191 | + | if ch.sub.below { | |
| 192 | + | below = append(below, ch) | |
| 193 | + | } else { | |
| 194 | + | above = append(above, ch) | |
| 195 | + | } | |
| 196 | + | } | |
| 197 | + | copy(list, append(below, above...)) | |
| 198 | + | } | |
| 108 | 199 | ||
| 109 | 200 | type region struct{} | |
| 110 | 201 | ||
| @@ -268,7 +359,8 @@ func (h seat) GetPointer(r *wayland.Seat, id uint32) { | |||
| 268 | 359 | } | |
| 269 | 360 | if w := s.windows[s.pointerFocus]; w != nil && w.mapped && w.xdg.resource.Client() == r.Client() { | |
| 270 | 361 | s.serial++ | |
| 271 | - | p.SendEnter(s.serial, w.xdg.surfaceResource(), server.FixedFromFloat(s.pointerX), server.FixedFromFloat(s.pointerY)) | |
| 362 | + | x, y := w.surfacePoint(s.pointerX, s.pointerY) | |
| 363 | + | p.SendEnter(s.serial, w.xdg.surfaceResource(), server.FixedFromFloat(x), server.FixedFromFloat(y)) | |
| 272 | 364 | pointerFrame(p) | |
| 273 | 365 | } | |
| 274 | 366 | } | |
| @@ -82,29 +82,120 @@ func (c subcompositor) GetSubsurface(r *wayland.Subcompositor, id uint32, w, par | |||||
| 82 | if w == nil || parent == nil { | 82 | if w == nil || parent == nil { | ||
| 83 | return | 83 | return | ||
| 84 | } | 84 | } | ||
| 85 | - | state := c.server.surfaces[w.Resource] | 85 | + | state, up := c.server.surfaces[w.Resource], c.server.surfaces[parent.Resource] |
| 86 | - | if state == nil { | 86 | + | if state == nil || up == nil { |
| 87 | return | 87 | return | ||
| 88 | } | 88 | } | ||
| 89 | - | if state.kind != roleNone { | 89 | + | if state.kind != roleNone && state.kind != roleSubsurface || state.sub.role != nil { |
| 90 | r.PostError(uint32(wayland.SubcompositorErrorBadSurface), "surface already has role") | 90 | r.PostError(uint32(wayland.SubcompositorErrorBadSurface), "surface already has role") | ||
| 91 | return | 91 | return | ||
| 92 | } | 92 | } | ||
| 93 | - | if sub, err := wayland.NewSubsurface(r.Client(), 1, id, subsurface{state}); err == nil { | 93 | + | // A surface cannot be its own ancestor. |
| 94 | - | state.kind = roleSubsurface | 94 | + | for p := up; p != nil; p = p.sub.parent { |
| 95 | - | state.role = func(bool) {} | 95 | + | if p == state { |
| 96 | - | sub.OnDestroy = func() { state.role = nil } | 96 | + | r.PostError(uint32(wayland.SubcompositorErrorBadParent), "parent is a descendant") |
| 97 | + | return | |||
| 98 | + | } | |||
| 99 | + | } | |||
| 100 | + | sub, err := wayland.NewSubsurface(r.Client(), 1, id, subsurface{state}) | |||
| 101 | + | if err != nil { | |||
| 102 | + | return | |||
| 103 | + | } | |||
| 104 | + | state.kind = roleSubsurface | |||
| 105 | + | state.role = func(bool) {} | |||
| 106 | + | state.sub = subState{role: sub, parent: up, children: state.sub.children} | |||
| 107 | + | up.sub.children = append(up.sub.children, state) | |||
| 108 | + | // The child follows its root window's output and scale. | |||
| 109 | + | state.sendScale() | |||
| 110 | + | sub.OnDestroy = func() { | |||
| 111 | + | if state.sub.role != sub { | |||
| 112 | + | return | |||
| 113 | + | } | |||
| 114 | + | state.sub.role = nil | |||
| 115 | + | state.role = nil | |||
| 116 | + | state.detach() | |||
| 97 | } | 117 | } | ||
| 98 | } | 118 | } | ||
| 99 | 119 | ||||
| 120 | + | // Subsurfaces are drawn as their own commits arrive (desynchronized): the | |||
| 121 | + | // synchronized mode's cached state is not implemented. | |||
| 100 | type subsurface struct{ surface *surface } | 122 | type subsurface struct{ surface *surface } | ||
| 101 | 123 | ||||
| 102 | - | func (subsurface) Destroy(*wayland.Subsurface) {} | 124 | + | func (subsurface) Destroy(*wayland.Subsurface) {} |
| 103 | - | func (subsurface) SetPosition(*wayland.Subsurface, int32, int32) {} | 125 | + | func (s subsurface) SetPosition(_ *wayland.Subsurface, x, y int32) { |
| 104 | - | func (subsurface) PlaceAbove(*wayland.Subsurface, *wayland.Surface) {} | 126 | + | s.surface.sub.pendX, s.surface.sub.pendY, s.surface.sub.moved = int(x), int(y), true |
| 105 | - | func (subsurface) PlaceBelow(*wayland.Subsurface, *wayland.Surface) {} | 127 | + | } |
| 106 | - | func (subsurface) SetSync(*wayland.Subsurface) {} | 128 | + | func (s subsurface) PlaceAbove(r *wayland.Subsurface, sibling *wayland.Surface) { |
| 107 | - | func (subsurface) SetDesync(*wayland.Subsurface) {} | 129 | + | s.restack(r, sibling, true) |
| 130 | + | } | |||
| 131 | + | func (s subsurface) PlaceBelow(r *wayland.Subsurface, sibling *wayland.Surface) { | |||
| 132 | + | s.restack(r, sibling, false) | |||
| 133 | + | } | |||
| 134 | + | func (subsurface) SetSync(*wayland.Subsurface) {} | |||
| 135 | + | func (subsurface) SetDesync(*wayland.Subsurface) {} | |||
| 136 | + | ||||
| 137 | + | // restack moves the surface next to a sibling or its parent. The order | |||
| 138 | + | // applies at once rather than on the parent commit. | |||
| 139 | + | func (s subsurface) restack(r *wayland.Subsurface, sibling *wayland.Surface, above bool) { | |||
| 140 | + | me := s.surface | |||
| 141 | + | p := me.sub.parent | |||
| 142 | + | if p == nil || sibling == nil { | |||
| 143 | + | return | |||
| 144 | + | } | |||
| 145 | + | other := me.server.surfaces[sibling.Resource] | |||
| 146 | + | if other == me || other == nil || other != p && other.sub.parent != p { | |||
| 147 | + | r.PostError(uint32(wayland.SubsurfaceErrorBadSurface), "not a sibling or the parent") | |||
| 148 | + | return | |||
| 149 | + | } | |||
| 150 | + | list := p.sub.children | |||
| 151 | + | for i, ch := range list { | |||
| 152 | + | if ch == me { | |||
| 153 | + | list = append(list[:i:i], list[i+1:]...) | |||
| 154 | + | break | |||
| 155 | + | } | |||
| 156 | + | } | |||
| 157 | + | if other == p { | |||
| 158 | + | // Relative to the parent: just above it or just below it. | |||
| 159 | + | me.sub.below = !above | |||
| 160 | + | if above { | |||
| 161 | + | list = append([]*surface{me}, list...) | |||
| 162 | + | } else { | |||
| 163 | + | list = append(list, me) | |||
| 164 | + | } | |||
| 165 | + | // Keep below children first so the flattening stays ordered. | |||
| 166 | + | sortBelowFirst(list) | |||
| 167 | + | } else { | |||
| 168 | + | me.sub.below = other.sub.below | |||
| 169 | + | at := len(list) | |||
| 170 | + | for i, ch := range list { | |||
| 171 | + | if ch == other { | |||
| 172 | + | at = i | |||
| 173 | + | if above { | |||
| 174 | + | at++ | |||
| 175 | + | } | |||
| 176 | + | break | |||
| 177 | + | } | |||
| 178 | + | } | |||
| 179 | + | list = append(list[:at:at], append([]*surface{me}, list[at:]...)...) | |||
| 180 | + | } | |||
| 181 | + | p.sub.children = list | |||
| 182 | + | me.redraw() | |||
| 183 | + | } | |||
| 184 | + | ||||
| 185 | + | // sortBelowFirst moves children below their parent ahead of those above, | |||
| 186 | + | // keeping each group's order. | |||
| 187 | + | func sortBelowFirst(list []*surface) { | |||
| 188 | + | below := make([]*surface, 0, len(list)) | |||
| 189 | + | above := make([]*surface, 0, len(list)) | |||
| 190 | + | for _, ch := range list { | |||
| 191 | + | if ch.sub.below { | |||
| 192 | + | below = append(below, ch) | |||
| 193 | + | } else { | |||
| 194 | + | above = append(above, ch) | |||
| 195 | + | } | |||
| 196 | + | } | |||
| 197 | + | copy(list, append(below, above...)) | |||
| 198 | + | } | |||
| 108 | 199 | ||||
| 109 | type region struct{} | 200 | type region struct{} | ||
| 110 | 201 | ||||
| @@ -268,7 +359,8 @@ func (h seat) GetPointer(r *wayland.Seat, id uint32) { | |||||
| 268 | } | 359 | } | ||
| 269 | if w := s.windows[s.pointerFocus]; w != nil && w.mapped && w.xdg.resource.Client() == r.Client() { | 360 | if w := s.windows[s.pointerFocus]; w != nil && w.mapped && w.xdg.resource.Client() == r.Client() { | ||
| 270 | s.serial++ | 361 | s.serial++ | ||
| 271 | - | p.SendEnter(s.serial, w.xdg.surfaceResource(), server.FixedFromFloat(s.pointerX), server.FixedFromFloat(s.pointerY)) | 362 | + | x, y := w.surfacePoint(s.pointerX, s.pointerY) |
| 363 | + | p.SendEnter(s.serial, w.xdg.surfaceResource(), server.FixedFromFloat(x), server.FixedFromFloat(y)) | |||
| 272 | pointerFrame(p) | 364 | pointerFrame(p) | ||
| 273 | } | 365 | } | ||
| 274 | } | 366 | } | ||
M internal/adapters/wayland/scale.go +10 -0
| @@ -16,6 +16,8 @@ import ( | |||
| 16 | 16 | // outputOfSurface is the output a surface is on: its window's configured | |
| 17 | 17 | // output, its layer output, or the focused one. | |
| 18 | 18 | func (s *Server) outputOfSurface(surf *surface) *output { | |
| 19 | + | // Subsurfaces are on their root's output. | |
| 20 | + | surf = surf.root() | |
| 19 | 21 | name := "" | |
| 20 | 22 | switch { | |
| 21 | 23 | case surf.xdg != nil && surf.xdg.window != nil: | |
| @@ -26,6 +28,14 @@ func (s *Server) outputOfSurface(surf *surface) *output { | |||
| 26 | 28 | return s.outputByName(name) | |
| 27 | 29 | } | |
| 28 | 30 | ||
| 31 | + | // sendTreeScale sends the scale to a surface and its subsurfaces. | |
| 32 | + | func (surf *surface) sendTreeScale() { | |
| 33 | + | surf.sendScale() | |
| 34 | + | for _, ch := range surf.sub.children { | |
| 35 | + | ch.sendTreeScale() | |
| 36 | + | } | |
| 37 | + | } | |
| 38 | + | ||
| 29 | 39 | // sendScale tells the surface about its output: enter/leave, the integer | |
| 30 | 40 | // buffer scale (wl_surface v6+) and the fractional scale. | |
| 31 | 41 | func (surf *surface) sendScale() { | |
| @@ -16,6 +16,8 @@ import ( | |||||
| 16 | // outputOfSurface is the output a surface is on: its window's configured | 16 | // outputOfSurface is the output a surface is on: its window's configured | ||
| 17 | // output, its layer output, or the focused one. | 17 | // output, its layer output, or the focused one. | ||
| 18 | func (s *Server) outputOfSurface(surf *surface) *output { | 18 | func (s *Server) outputOfSurface(surf *surface) *output { | ||
| 19 | + | // Subsurfaces are on their root's output. | |||
| 20 | + | surf = surf.root() | |||
| 19 | name := "" | 21 | name := "" | ||
| 20 | switch { | 22 | switch { | ||
| 21 | case surf.xdg != nil && surf.xdg.window != nil: | 23 | case surf.xdg != nil && surf.xdg.window != nil: | ||
| @@ -26,6 +28,14 @@ func (s *Server) outputOfSurface(surf *surface) *output { | |||||
| 26 | return s.outputByName(name) | 28 | return s.outputByName(name) | ||
| 27 | } | 29 | } | ||
| 28 | 30 | ||||
| 31 | + | // sendTreeScale sends the scale to a surface and its subsurfaces. | |||
| 32 | + | func (surf *surface) sendTreeScale() { | |||
| 33 | + | surf.sendScale() | |||
| 34 | + | for _, ch := range surf.sub.children { | |||
| 35 | + | ch.sendTreeScale() | |||
| 36 | + | } | |||
| 37 | + | } | |||
| 38 | + | ||||
| 29 | // sendScale tells the surface about its output: enter/leave, the integer | 39 | // sendScale tells the surface about its output: enter/leave, the integer | ||
| 30 | // buffer scale (wl_surface v6+) and the fractional scale. | 40 | // buffer scale (wl_surface v6+) and the fractional scale. | ||
| 31 | func (surf *surface) sendScale() { | 41 | func (surf *surface) sendScale() { | ||
M internal/adapters/wayland/server.go +6 -4
| @@ -345,9 +345,10 @@ func (s *Server) apply(cmd ports.ClientCommand) { | |||
| 345 | 345 | case ports.PointerFocus: | |
| 346 | 346 | s.changePointerFocus(c.ID, c.X, c.Y) | |
| 347 | 347 | case ports.PointerMotionTo: | |
| 348 | - | if c.ID == s.pointerFocus { | |
| 349 | - | for _, p := range s.windowPointers(s.windows[c.ID]) { | |
| 350 | - | p.SendMotion(c.TimeMsec, server.FixedFromFloat(c.X), server.FixedFromFloat(c.Y)) | |
| 348 | + | if w := s.windows[c.ID]; w != nil && c.ID == s.pointerFocus { | |
| 349 | + | x, y := w.surfacePoint(c.X, c.Y) | |
| 350 | + | for _, p := range s.windowPointers(w) { | |
| 351 | + | p.SendMotion(c.TimeMsec, server.FixedFromFloat(x), server.FixedFromFloat(y)) | |
| 351 | 352 | pointerFrame(p) | |
| 352 | 353 | } | |
| 353 | 354 | } | |
| @@ -409,7 +410,7 @@ func (s *Server) apply(cmd ports.ClientCommand) { | |||
| 409 | 410 | w.last, w.hasLast = c, true | |
| 410 | 411 | w.sendConfigure() | |
| 411 | 412 | if surf := w.xdg.surface; surf != nil && c.Output != "" { | |
| 412 | - | surf.sendScale() | |
| 413 | + | surf.sendTreeScale() | |
| 413 | 414 | } | |
| 414 | 415 | case ports.SetOutputs: | |
| 415 | 416 | s.setOutputs(c) | |
| @@ -585,6 +586,7 @@ func (s *Server) changePointerFocus(id ports.WindowID, x, y float64) { | |||
| 585 | 586 | if w := s.windows[id]; w != nil { | |
| 586 | 587 | if surface := w.xdg.surfaceResource(); surface != nil && surface.Resource.Alive() { | |
| 587 | 588 | s.pointerFocus, s.pointerX, s.pointerY = id, x, y | |
| 589 | + | x, y := w.surfacePoint(x, y) | |
| 588 | 590 | for _, p := range s.windowPointers(w) { | |
| 589 | 591 | s.serial++ | |
| 590 | 592 | p.SendEnter(s.serial, surface, server.FixedFromFloat(x), server.FixedFromFloat(y)) | |
| @@ -345,9 +345,10 @@ func (s *Server) apply(cmd ports.ClientCommand) { | |||||
| 345 | case ports.PointerFocus: | 345 | case ports.PointerFocus: | ||
| 346 | s.changePointerFocus(c.ID, c.X, c.Y) | 346 | s.changePointerFocus(c.ID, c.X, c.Y) | ||
| 347 | case ports.PointerMotionTo: | 347 | case ports.PointerMotionTo: | ||
| 348 | - | if c.ID == s.pointerFocus { | 348 | + | if w := s.windows[c.ID]; w != nil && c.ID == s.pointerFocus { |
| 349 | - | for _, p := range s.windowPointers(s.windows[c.ID]) { | 349 | + | x, y := w.surfacePoint(c.X, c.Y) |
| 350 | - | p.SendMotion(c.TimeMsec, server.FixedFromFloat(c.X), server.FixedFromFloat(c.Y)) | 350 | + | for _, p := range s.windowPointers(w) { |
| 351 | + | p.SendMotion(c.TimeMsec, server.FixedFromFloat(x), server.FixedFromFloat(y)) | |||
| 351 | pointerFrame(p) | 352 | pointerFrame(p) | ||
| 352 | } | 353 | } | ||
| 353 | } | 354 | } | ||
| @@ -409,7 +410,7 @@ func (s *Server) apply(cmd ports.ClientCommand) { | |||||
| 409 | w.last, w.hasLast = c, true | 410 | w.last, w.hasLast = c, true | ||
| 410 | w.sendConfigure() | 411 | w.sendConfigure() | ||
| 411 | if surf := w.xdg.surface; surf != nil && c.Output != "" { | 412 | if surf := w.xdg.surface; surf != nil && c.Output != "" { | ||
| 412 | - | surf.sendScale() | 413 | + | surf.sendTreeScale() |
| 413 | } | 414 | } | ||
| 414 | case ports.SetOutputs: | 415 | case ports.SetOutputs: | ||
| 415 | s.setOutputs(c) | 416 | s.setOutputs(c) | ||
| @@ -585,6 +586,7 @@ func (s *Server) changePointerFocus(id ports.WindowID, x, y float64) { | |||||
| 585 | if w := s.windows[id]; w != nil { | 586 | if w := s.windows[id]; w != nil { | ||
| 586 | if surface := w.xdg.surfaceResource(); surface != nil && surface.Resource.Alive() { | 587 | if surface := w.xdg.surfaceResource(); surface != nil && surface.Resource.Alive() { | ||
| 587 | s.pointerFocus, s.pointerX, s.pointerY = id, x, y | 588 | s.pointerFocus, s.pointerX, s.pointerY = id, x, y | ||
| 589 | + | x, y := w.surfacePoint(x, y) | |||
| 588 | for _, p := range s.windowPointers(w) { | 590 | for _, p := range s.windowPointers(w) { | ||
| 589 | s.serial++ | 591 | s.serial++ | ||
| 590 | p.SendEnter(s.serial, surface, server.FixedFromFloat(x), server.FixedFromFloat(y)) | 592 | p.SendEnter(s.serial, surface, server.FixedFromFloat(x), server.FixedFromFloat(y)) | ||
A internal/adapters/wayland/subsurface_test.go +150 -0
| @@ -0,0 +1,150 @@ | |||
| 1 | + | package wayland | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | "time" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/nefertty/internal/ports" | |
| 8 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 9 | + | "github.com/bnema/purego-libwayland/protocol/xdgshell" | |
| 10 | + | "golang.org/x/sys/unix" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // A toplevel with a subsurface (how Firefox draws with the GPU) sends one | |
| 14 | + | // content: its own buffer, the child at its committed position and the | |
| 15 | + | // window geometry. | |
| 16 | + | func TestSubsurfaceContent(t *testing.T) { | |
| 17 | + | s, events, contents, dir := dmabufServer(t, ports.DMABufSupport{}) | |
| 18 | + | c := protocolClient(t, s, dir) | |
| 19 | + | comp := bindProtocol(t, c, "wl_compositor") | |
| 20 | + | subc := bindProtocol(t, c, "wl_subcompositor") | |
| 21 | + | wm := bindProtocol(t, c, "xdg_wm_base") | |
| 22 | + | shm := bindProtocol(t, c, "wl_shm") | |
| 23 | + | registerProtocol(t, c, shm) | |
| 24 | + | fd, err := unix.MemfdCreate("subsurface", 0) | |
| 25 | + | if err != nil { | |
| 26 | + | t.Fatal(err) | |
| 27 | + | } | |
| 28 | + | defer unix.Close(fd) | |
| 29 | + | if err := unix.Ftruncate(fd, 4*4*4); err != nil { | |
| 30 | + | t.Fatal(err) | |
| 31 | + | } | |
| 32 | + | pool, big, small := c.AllocateID(), c.AllocateID(), c.AllocateID() | |
| 33 | + | registerProtocol(t, c, pool) | |
| 34 | + | registerProtocol(t, c, big) | |
| 35 | + | registerProtocol(t, c, small) | |
| 36 | + | if err := c.SendRequestWithFDs(shm, uint16(wayland.ShmRequestCreatePool), []int{fd}, pool, int32(64)); err != nil { | |
| 37 | + | t.Fatal(err) | |
| 38 | + | } | |
| 39 | + | requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, big, int32(0), int32(4), int32(4), int32(16), uint32(0)) | |
| 40 | + | requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, small, int32(0), int32(2), int32(2), int32(16), uint32(0)) | |
| 41 | + | ||
| 42 | + | root, child := c.AllocateID(), c.AllocateID() | |
| 43 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, root) | |
| 44 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child) | |
| 45 | + | registerProtocol(t, c, root) | |
| 46 | + | registerProtocol(t, c, child) | |
| 47 | + | sub := c.AllocateID() | |
| 48 | + | registerProtocol(t, c, sub) | |
| 49 | + | requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root) | |
| 50 | + | xdg := c.AllocateID() | |
| 51 | + | requestProtocol(t, c, wm, xdgshell.WmBaseRequestGetXdgSurface, xdg, root) | |
| 52 | + | serials := make(chan uint32, 4) | |
| 53 | + | xp := &configureProxy{serial: serials} | |
| 54 | + | xp.SetID(xdg) | |
| 55 | + | c.Context().Register(xp) | |
| 56 | + | top := c.AllocateID() | |
| 57 | + | registerProtocol(t, c, top) | |
| 58 | + | requestProtocol(t, c, xdg, xdgshell.SurfaceRequestGetToplevel, top) | |
| 59 | + | requestProtocol(t, c, root, wayland.SurfaceRequestCommit) | |
| 60 | + | if err := c.Roundtrip(); err != nil { | |
| 61 | + | t.Fatal(err) | |
| 62 | + | } | |
| 63 | + | select { | |
| 64 | + | case serial := <-serials: | |
| 65 | + | requestProtocol(t, c, xdg, xdgshell.SurfaceRequestAckConfigure, serial) | |
| 66 | + | case <-time.After(2 * time.Second): | |
| 67 | + | t.Fatal("configure timeout") | |
| 68 | + | } | |
| 69 | + | // Child first: nothing to draw until the root maps. | |
| 70 | + | requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetPosition, int32(1), int32(2)) | |
| 71 | + | requestProtocol(t, c, child, wayland.SurfaceRequestAttach, small, int32(0), int32(0)) | |
| 72 | + | requestProtocol(t, c, child, wayland.SurfaceRequestCommit) | |
| 73 | + | requestProtocol(t, c, xdg, xdgshell.SurfaceRequestSetWindowGeometry, int32(1), int32(1), int32(2), int32(2)) | |
| 74 | + | requestProtocol(t, c, root, wayland.SurfaceRequestAttach, big, int32(0), int32(0)) | |
| 75 | + | requestProtocol(t, c, root, wayland.SurfaceRequestCommit) | |
| 76 | + | if err := c.Roundtrip(); err != nil { | |
| 77 | + | t.Fatal(err) | |
| 78 | + | } | |
| 79 | + | w := mapped(t, events, 2*time.Second) | |
| 80 | + | var got ports.SurfaceContent | |
| 81 | + | deadline := time.After(2 * time.Second) | |
| 82 | + | for got.ID == 0 { | |
| 83 | + | select { | |
| 84 | + | case got = <-contents: | |
| 85 | + | case <-deadline: | |
| 86 | + | t.Fatal("no content") | |
| 87 | + | } | |
| 88 | + | } | |
| 89 | + | if got.ID != w.ID || got.Width != 4 || got.Geometry != (ports.Rect{X: 1, Y: 1, W: 2, H: 2}) { | |
| 90 | + | t.Fatalf("root %+v", got) | |
| 91 | + | } | |
| 92 | + | if len(got.Children) != 1 || got.Children[0].X != 1 || got.Children[0].Y != 2 || got.Children[0].Width != 2 || got.Children[0].Below { | |
| 93 | + | t.Fatalf("children %+v", got.Children) | |
| 94 | + | } | |
| 95 | + | // Destroying the subsurface redraws the root without it. | |
| 96 | + | requestProtocol(t, c, sub, wayland.SubsurfaceRequestDestroy) | |
| 97 | + | if err := c.Roundtrip(); err != nil { | |
| 98 | + | t.Fatal(err) | |
| 99 | + | } | |
| 100 | + | deadline = time.After(2 * time.Second) | |
| 101 | + | for { | |
| 102 | + | select { | |
| 103 | + | case got = <-contents: | |
| 104 | + | if len(got.Children) == 0 { | |
| 105 | + | return | |
| 106 | + | } | |
| 107 | + | case <-deadline: | |
| 108 | + | t.Fatalf("child kept: %+v", got.Children) | |
| 109 | + | } | |
| 110 | + | } | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | // A surface cannot become a subsurface of its own child. | |
| 114 | + | func TestSubsurfaceLoop(t *testing.T) { | |
| 115 | + | s, _, _, dir := dmabufServer(t, ports.DMABufSupport{}) | |
| 116 | + | c := protocolClient(t, s, dir) | |
| 117 | + | comp := bindProtocol(t, c, "wl_compositor") | |
| 118 | + | subc := bindProtocol(t, c, "wl_subcompositor") | |
| 119 | + | a, b := c.AllocateID(), c.AllocateID() | |
| 120 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, a) | |
| 121 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, b) | |
| 122 | + | s1 := c.AllocateID() | |
| 123 | + | registerProtocol(t, c, s1) | |
| 124 | + | requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s1, b, a) | |
| 125 | + | s2 := c.AllocateID() | |
| 126 | + | requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s2, a, b) | |
| 127 | + | expectProtocolError(t, c, subc, uint32(wayland.SubcompositorErrorBadParent)) | |
| 128 | + | } | |
| 129 | + | ||
| 130 | + | // A wl_subsurface outlives its parent: the surface keeps its role, so a | |
| 131 | + | // second get_subsurface is bad_surface. | |
| 132 | + | func TestSubsurfaceRoleOutlivesParent(t *testing.T) { | |
| 133 | + | s, _, _, dir := dmabufServer(t, ports.DMABufSupport{}) | |
| 134 | + | c := protocolClient(t, s, dir) | |
| 135 | + | comp := bindProtocol(t, c, "wl_compositor") | |
| 136 | + | subc := bindProtocol(t, c, "wl_subcompositor") | |
| 137 | + | a, b := c.AllocateID(), c.AllocateID() | |
| 138 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, a) | |
| 139 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, b) | |
| 140 | + | registerProtocol(t, c, a) | |
| 141 | + | s1 := c.AllocateID() | |
| 142 | + | registerProtocol(t, c, s1) | |
| 143 | + | requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s1, b, a) | |
| 144 | + | requestProtocol(t, c, a, wayland.SurfaceRequestDestroy) | |
| 145 | + | p := c.AllocateID() | |
| 146 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, p) | |
| 147 | + | s2 := c.AllocateID() | |
| 148 | + | requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s2, b, p) | |
| 149 | + | expectProtocolError(t, c, subc, uint32(wayland.SubcompositorErrorBadSurface)) | |
| 150 | + | } | |
| @@ -0,0 +1,150 @@ | |||||
| 1 | + | package wayland | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | "time" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/nefertty/internal/ports" | |||
| 8 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 9 | + | "github.com/bnema/purego-libwayland/protocol/xdgshell" | |||
| 10 | + | "golang.org/x/sys/unix" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // A toplevel with a subsurface (how Firefox draws with the GPU) sends one | |||
| 14 | + | // content: its own buffer, the child at its committed position and the | |||
| 15 | + | // window geometry. | |||
| 16 | + | func TestSubsurfaceContent(t *testing.T) { | |||
| 17 | + | s, events, contents, dir := dmabufServer(t, ports.DMABufSupport{}) | |||
| 18 | + | c := protocolClient(t, s, dir) | |||
| 19 | + | comp := bindProtocol(t, c, "wl_compositor") | |||
| 20 | + | subc := bindProtocol(t, c, "wl_subcompositor") | |||
| 21 | + | wm := bindProtocol(t, c, "xdg_wm_base") | |||
| 22 | + | shm := bindProtocol(t, c, "wl_shm") | |||
| 23 | + | registerProtocol(t, c, shm) | |||
| 24 | + | fd, err := unix.MemfdCreate("subsurface", 0) | |||
| 25 | + | if err != nil { | |||
| 26 | + | t.Fatal(err) | |||
| 27 | + | } | |||
| 28 | + | defer unix.Close(fd) | |||
| 29 | + | if err := unix.Ftruncate(fd, 4*4*4); err != nil { | |||
| 30 | + | t.Fatal(err) | |||
| 31 | + | } | |||
| 32 | + | pool, big, small := c.AllocateID(), c.AllocateID(), c.AllocateID() | |||
| 33 | + | registerProtocol(t, c, pool) | |||
| 34 | + | registerProtocol(t, c, big) | |||
| 35 | + | registerProtocol(t, c, small) | |||
| 36 | + | if err := c.SendRequestWithFDs(shm, uint16(wayland.ShmRequestCreatePool), []int{fd}, pool, int32(64)); err != nil { | |||
| 37 | + | t.Fatal(err) | |||
| 38 | + | } | |||
| 39 | + | requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, big, int32(0), int32(4), int32(4), int32(16), uint32(0)) | |||
| 40 | + | requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, small, int32(0), int32(2), int32(2), int32(16), uint32(0)) | |||
| 41 | + | ||||
| 42 | + | root, child := c.AllocateID(), c.AllocateID() | |||
| 43 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, root) | |||
| 44 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child) | |||
| 45 | + | registerProtocol(t, c, root) | |||
| 46 | + | registerProtocol(t, c, child) | |||
| 47 | + | sub := c.AllocateID() | |||
| 48 | + | registerProtocol(t, c, sub) | |||
| 49 | + | requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root) | |||
| 50 | + | xdg := c.AllocateID() | |||
| 51 | + | requestProtocol(t, c, wm, xdgshell.WmBaseRequestGetXdgSurface, xdg, root) | |||
| 52 | + | serials := make(chan uint32, 4) | |||
| 53 | + | xp := &configureProxy{serial: serials} | |||
| 54 | + | xp.SetID(xdg) | |||
| 55 | + | c.Context().Register(xp) | |||
| 56 | + | top := c.AllocateID() | |||
| 57 | + | registerProtocol(t, c, top) | |||
| 58 | + | requestProtocol(t, c, xdg, xdgshell.SurfaceRequestGetToplevel, top) | |||
| 59 | + | requestProtocol(t, c, root, wayland.SurfaceRequestCommit) | |||
| 60 | + | if err := c.Roundtrip(); err != nil { | |||
| 61 | + | t.Fatal(err) | |||
| 62 | + | } | |||
| 63 | + | select { | |||
| 64 | + | case serial := <-serials: | |||
| 65 | + | requestProtocol(t, c, xdg, xdgshell.SurfaceRequestAckConfigure, serial) | |||
| 66 | + | case <-time.After(2 * time.Second): | |||
| 67 | + | t.Fatal("configure timeout") | |||
| 68 | + | } | |||
| 69 | + | // Child first: nothing to draw until the root maps. | |||
| 70 | + | requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetPosition, int32(1), int32(2)) | |||
| 71 | + | requestProtocol(t, c, child, wayland.SurfaceRequestAttach, small, int32(0), int32(0)) | |||
| 72 | + | requestProtocol(t, c, child, wayland.SurfaceRequestCommit) | |||
| 73 | + | requestProtocol(t, c, xdg, xdgshell.SurfaceRequestSetWindowGeometry, int32(1), int32(1), int32(2), int32(2)) | |||
| 74 | + | requestProtocol(t, c, root, wayland.SurfaceRequestAttach, big, int32(0), int32(0)) | |||
| 75 | + | requestProtocol(t, c, root, wayland.SurfaceRequestCommit) | |||
| 76 | + | if err := c.Roundtrip(); err != nil { | |||
| 77 | + | t.Fatal(err) | |||
| 78 | + | } | |||
| 79 | + | w := mapped(t, events, 2*time.Second) | |||
| 80 | + | var got ports.SurfaceContent | |||
| 81 | + | deadline := time.After(2 * time.Second) | |||
| 82 | + | for got.ID == 0 { | |||
| 83 | + | select { | |||
| 84 | + | case got = <-contents: | |||
| 85 | + | case <-deadline: | |||
| 86 | + | t.Fatal("no content") | |||
| 87 | + | } | |||
| 88 | + | } | |||
| 89 | + | if got.ID != w.ID || got.Width != 4 || got.Geometry != (ports.Rect{X: 1, Y: 1, W: 2, H: 2}) { | |||
| 90 | + | t.Fatalf("root %+v", got) | |||
| 91 | + | } | |||
| 92 | + | if len(got.Children) != 1 || got.Children[0].X != 1 || got.Children[0].Y != 2 || got.Children[0].Width != 2 || got.Children[0].Below { | |||
| 93 | + | t.Fatalf("children %+v", got.Children) | |||
| 94 | + | } | |||
| 95 | + | // Destroying the subsurface redraws the root without it. | |||
| 96 | + | requestProtocol(t, c, sub, wayland.SubsurfaceRequestDestroy) | |||
| 97 | + | if err := c.Roundtrip(); err != nil { | |||
| 98 | + | t.Fatal(err) | |||
| 99 | + | } | |||
| 100 | + | deadline = time.After(2 * time.Second) | |||
| 101 | + | for { | |||
| 102 | + | select { | |||
| 103 | + | case got = <-contents: | |||
| 104 | + | if len(got.Children) == 0 { | |||
| 105 | + | return | |||
| 106 | + | } | |||
| 107 | + | case <-deadline: | |||
| 108 | + | t.Fatalf("child kept: %+v", got.Children) | |||
| 109 | + | } | |||
| 110 | + | } | |||
| 111 | + | } | |||
| 112 | + | ||||
| 113 | + | // A surface cannot become a subsurface of its own child. | |||
| 114 | + | func TestSubsurfaceLoop(t *testing.T) { | |||
| 115 | + | s, _, _, dir := dmabufServer(t, ports.DMABufSupport{}) | |||
| 116 | + | c := protocolClient(t, s, dir) | |||
| 117 | + | comp := bindProtocol(t, c, "wl_compositor") | |||
| 118 | + | subc := bindProtocol(t, c, "wl_subcompositor") | |||
| 119 | + | a, b := c.AllocateID(), c.AllocateID() | |||
| 120 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, a) | |||
| 121 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, b) | |||
| 122 | + | s1 := c.AllocateID() | |||
| 123 | + | registerProtocol(t, c, s1) | |||
| 124 | + | requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s1, b, a) | |||
| 125 | + | s2 := c.AllocateID() | |||
| 126 | + | requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s2, a, b) | |||
| 127 | + | expectProtocolError(t, c, subc, uint32(wayland.SubcompositorErrorBadParent)) | |||
| 128 | + | } | |||
| 129 | + | ||||
| 130 | + | // A wl_subsurface outlives its parent: the surface keeps its role, so a | |||
| 131 | + | // second get_subsurface is bad_surface. | |||
| 132 | + | func TestSubsurfaceRoleOutlivesParent(t *testing.T) { | |||
| 133 | + | s, _, _, dir := dmabufServer(t, ports.DMABufSupport{}) | |||
| 134 | + | c := protocolClient(t, s, dir) | |||
| 135 | + | comp := bindProtocol(t, c, "wl_compositor") | |||
| 136 | + | subc := bindProtocol(t, c, "wl_subcompositor") | |||
| 137 | + | a, b := c.AllocateID(), c.AllocateID() | |||
| 138 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, a) | |||
| 139 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, b) | |||
| 140 | + | registerProtocol(t, c, a) | |||
| 141 | + | s1 := c.AllocateID() | |||
| 142 | + | registerProtocol(t, c, s1) | |||
| 143 | + | requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s1, b, a) | |||
| 144 | + | requestProtocol(t, c, a, wayland.SurfaceRequestDestroy) | |||
| 145 | + | p := c.AllocateID() | |||
| 146 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, p) | |||
| 147 | + | s2 := c.AllocateID() | |||
| 148 | + | requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s2, b, p) | |||
| 149 | + | expectProtocolError(t, c, subc, uint32(wayland.SubcompositorErrorBadSurface)) | |||
| 150 | + | } | |||
M internal/adapters/wayland/surface.go +125 -16
| @@ -35,10 +35,109 @@ type surface struct { | |||
| 35 | 35 | // on is the output the surface entered; scale is the last scale sent. | |
| 36 | 36 | on *output | |
| 37 | 37 | scale float64 | |
| 38 | + | // content is the last content built from this surface's own buffer; | |
| 39 | + | // has is false while no buffer is attached. | |
| 40 | + | content ports.SurfaceContent | |
| 41 | + | has bool | |
| 42 | + | sub subState | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | // subState is the subsurface tree: parent is set on subsurfaces, children | |
| 46 | + | // are ordered bottom to top, below marks children under their parent. | |
| 47 | + | type subState struct { | |
| 48 | + | // role is the live wl_subsurface, nil when the surface has none. | |
| 49 | + | role *wayland.Subsurface | |
| 50 | + | parent *surface | |
| 51 | + | children []*surface | |
| 52 | + | x, y int // committed position, from the parent origin | |
| 53 | + | pendX, pendY int | |
| 54 | + | moved bool // a position waits for the parent commit | |
| 55 | + | below bool | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | // root is the top of the surface's subsurface tree. | |
| 59 | + | func (s *surface) root() *surface { | |
| 60 | + | for s.sub.parent != nil { | |
| 61 | + | s = s.sub.parent | |
| 62 | + | } | |
| 63 | + | return s | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | // windowID is the mapped window or layer the surface draws, or 0. | |
| 67 | + | func (s *surface) windowID() ports.WindowID { | |
| 68 | + | if s.xdg != nil && s.xdg.window != nil && s.xdg.window.mapped { | |
| 69 | + | return s.xdg.window.id | |
| 70 | + | } | |
| 71 | + | if s.layer != nil && s.layer.mapped { | |
| 72 | + | return s.layer.id | |
| 73 | + | } | |
| 74 | + | return 0 | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | // tree is the root's content with its subsurfaces flattened. | |
| 78 | + | func (s *surface) tree(id ports.WindowID) ports.SurfaceContent { | |
| 79 | + | c := s.content | |
| 80 | + | c.ID = id | |
| 81 | + | c.Children = nil | |
| 82 | + | for _, ch := range s.sub.children { | |
| 83 | + | ch.appendTree(&c.Children, ch.sub.x, ch.sub.y, ch.sub.below) | |
| 84 | + | } | |
| 85 | + | if s.xdg != nil { | |
| 86 | + | c.Geometry = s.xdg.geometry | |
| 87 | + | } | |
| 88 | + | return c | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | // appendTree flattens a subsurface at (x, y) from the root: its children | |
| 92 | + | // below it, itself, then its children above. | |
| 93 | + | func (s *surface) appendTree(out *[]ports.Subsurface, x, y int, below bool) { | |
| 94 | + | for _, ch := range s.sub.children { | |
| 95 | + | if ch.sub.below { | |
| 96 | + | ch.appendTree(out, x+ch.sub.x, y+ch.sub.y, below) | |
| 97 | + | } | |
| 98 | + | } | |
| 99 | + | if s.has { | |
| 100 | + | *out = append(*out, ports.Subsurface{X: x, Y: y, Below: below, SurfaceContent: s.content}) | |
| 101 | + | } | |
| 102 | + | for _, ch := range s.sub.children { | |
| 103 | + | if !ch.sub.below { | |
| 104 | + | ch.appendTree(out, x+ch.sub.x, y+ch.sub.y, below) | |
| 105 | + | } | |
| 106 | + | } | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | // redraw sends the tree of a mapped root to the outputs. | |
| 110 | + | func (s *surface) redraw() { | |
| 111 | + | r := s.root() | |
| 112 | + | if id := r.windowID(); id != 0 && s.server.channels.Contents != nil { | |
| 113 | + | s.server.emitContent(r.tree(id)) | |
| 114 | + | } | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | // detach removes a subsurface from its parent and redraws the parent. | |
| 118 | + | func (s *surface) detach() { | |
| 119 | + | p := s.sub.parent | |
| 120 | + | if p == nil { | |
| 121 | + | return | |
| 122 | + | } | |
| 123 | + | for i, ch := range p.sub.children { | |
| 124 | + | if ch == s { | |
| 125 | + | p.sub.children = append(p.sub.children[:i:i], p.sub.children[i+1:]...) | |
| 126 | + | break | |
| 127 | + | } | |
| 128 | + | } | |
| 129 | + | s.sub.parent = nil | |
| 130 | + | p.redraw() | |
| 131 | + | s.sendScale() | |
| 38 | 132 | } | |
| 39 | 133 | ||
| 40 | 134 | func (s *surface) Destroy(*wayland.Surface) { | |
| 41 | 135 | s.destroyed = true | |
| 136 | + | s.detach() | |
| 137 | + | for _, ch := range s.sub.children { | |
| 138 | + | ch.sub.parent = nil | |
| 139 | + | } | |
| 140 | + | s.sub.children = nil | |
| 42 | 141 | for _, cb := range s.callbacks { | |
| 43 | 142 | cb.Destroy() | |
| 44 | 143 | } | |
| @@ -90,36 +189,46 @@ func (s *surface) Commit(*wayland.Surface) { | |||
| 90 | 189 | if s.role != nil { | |
| 91 | 190 | s.role(s.current != nil) | |
| 92 | 191 | } | |
| 93 | - | id := ports.WindowID(0) | |
| 94 | - | if s.xdg != nil && s.xdg.window != nil && s.xdg.window.mapped { | |
| 95 | - | id = s.xdg.window.id | |
| 96 | - | } | |
| 97 | - | if s.layer != nil && s.layer.mapped { | |
| 98 | - | id = s.layer.id | |
| 99 | - | } | |
| 100 | - | if fresh && id != 0 && s.server.channels.Contents != nil { | |
| 101 | - | b := s.current | |
| 102 | - | if state, ok := s.server.buffers[b.Resource]; ok { | |
| 103 | - | if c, ok := state.content(id); ok { | |
| 192 | + | if fresh { | |
| 193 | + | if state, ok := s.server.buffers[s.current.Resource]; ok { | |
| 194 | + | if c, ok := state.content(0); ok { | |
| 104 | 195 | c.LogicalW, c.LogicalH = s.logicalSize(c.Width, c.Height) | |
| 105 | - | if c.Width != s.lastW || c.Height != s.lastH { | |
| 196 | + | if s.sub.parent == nil && (c.Width != s.lastW || c.Height != s.lastH) { | |
| 106 | 197 | s.lastW, s.lastH = c.Width, c.Height | |
| 107 | - | s.server.log.Info().Uint64("id", uint64(id)).Int("w", c.Width).Int("h", c.Height).Msg("buffer size") | |
| 198 | + | s.server.log.Info().Uint64("id", uint64(s.windowID())).Int("w", c.Width).Int("h", c.Height).Msg("buffer size") | |
| 108 | 199 | } | |
| 109 | - | s.server.emitContent(c) | |
| 200 | + | s.content, s.has = c, true | |
| 110 | 201 | } else if shm, ok := state.(*buffer); ok { | |
| 111 | 202 | shm.pool.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "SHM backing file truncated") | |
| 112 | 203 | } | |
| 113 | 204 | // wl_shm pixels were copied: the client may reuse the buffer. | |
| 114 | 205 | // A dmabuf is read in place until the next buffer replaces it. | |
| 115 | 206 | if _, gpu := state.(*dmabufBuffer); !gpu { | |
| 116 | - | if b.Resource.Alive() { | |
| 117 | - | b.SendRelease() | |
| 207 | + | if s.current.Resource.Alive() { | |
| 208 | + | s.current.SendRelease() | |
| 118 | 209 | } | |
| 119 | 210 | s.released = true | |
| 120 | 211 | } | |
| 121 | 212 | } | |
| 122 | 213 | } | |
| 214 | + | if s.current == nil { | |
| 215 | + | s.content, s.has = ports.SurfaceContent{}, false | |
| 216 | + | } | |
| 217 | + | // Subsurface positions apply on the parent commit. | |
| 218 | + | moved := false | |
| 219 | + | for _, ch := range s.sub.children { | |
| 220 | + | if ch.sub.moved { | |
| 221 | + | ch.sub.x, ch.sub.y, ch.sub.moved = ch.sub.pendX, ch.sub.pendY, false | |
| 222 | + | moved = true | |
| 223 | + | } | |
| 224 | + | } | |
| 225 | + | geometry := false | |
| 226 | + | if s.xdg != nil && s.xdg.pendingGeometry != s.xdg.geometry { | |
| 227 | + | s.xdg.geometry, geometry = s.xdg.pendingGeometry, true | |
| 228 | + | } | |
| 229 | + | if fresh || moved || geometry || s.sub.parent != nil { | |
| 230 | + | s.redraw() | |
| 231 | + | } | |
| 123 | 232 | } | |
| 124 | 233 | func (*surface) Damage(*wayland.Surface, int32, int32, int32, int32) {} | |
| 125 | 234 | func (*surface) DamageBuffer(*wayland.Surface, int32, int32, int32, int32) {} | |
| @@ -35,10 +35,109 @@ type surface struct { | |||||
| 35 | // on is the output the surface entered; scale is the last scale sent. | 35 | // on is the output the surface entered; scale is the last scale sent. | ||
| 36 | on *output | 36 | on *output | ||
| 37 | scale float64 | 37 | scale float64 | ||
| 38 | + | // content is the last content built from this surface's own buffer; | |||
| 39 | + | // has is false while no buffer is attached. | |||
| 40 | + | content ports.SurfaceContent | |||
| 41 | + | has bool | |||
| 42 | + | sub subState | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | // subState is the subsurface tree: parent is set on subsurfaces, children | |||
| 46 | + | // are ordered bottom to top, below marks children under their parent. | |||
| 47 | + | type subState struct { | |||
| 48 | + | // role is the live wl_subsurface, nil when the surface has none. | |||
| 49 | + | role *wayland.Subsurface | |||
| 50 | + | parent *surface | |||
| 51 | + | children []*surface | |||
| 52 | + | x, y int // committed position, from the parent origin | |||
| 53 | + | pendX, pendY int | |||
| 54 | + | moved bool // a position waits for the parent commit | |||
| 55 | + | below bool | |||
| 56 | + | } | |||
| 57 | + | ||||
| 58 | + | // root is the top of the surface's subsurface tree. | |||
| 59 | + | func (s *surface) root() *surface { | |||
| 60 | + | for s.sub.parent != nil { | |||
| 61 | + | s = s.sub.parent | |||
| 62 | + | } | |||
| 63 | + | return s | |||
| 64 | + | } | |||
| 65 | + | ||||
| 66 | + | // windowID is the mapped window or layer the surface draws, or 0. | |||
| 67 | + | func (s *surface) windowID() ports.WindowID { | |||
| 68 | + | if s.xdg != nil && s.xdg.window != nil && s.xdg.window.mapped { | |||
| 69 | + | return s.xdg.window.id | |||
| 70 | + | } | |||
| 71 | + | if s.layer != nil && s.layer.mapped { | |||
| 72 | + | return s.layer.id | |||
| 73 | + | } | |||
| 74 | + | return 0 | |||
| 75 | + | } | |||
| 76 | + | ||||
| 77 | + | // tree is the root's content with its subsurfaces flattened. | |||
| 78 | + | func (s *surface) tree(id ports.WindowID) ports.SurfaceContent { | |||
| 79 | + | c := s.content | |||
| 80 | + | c.ID = id | |||
| 81 | + | c.Children = nil | |||
| 82 | + | for _, ch := range s.sub.children { | |||
| 83 | + | ch.appendTree(&c.Children, ch.sub.x, ch.sub.y, ch.sub.below) | |||
| 84 | + | } | |||
| 85 | + | if s.xdg != nil { | |||
| 86 | + | c.Geometry = s.xdg.geometry | |||
| 87 | + | } | |||
| 88 | + | return c | |||
| 89 | + | } | |||
| 90 | + | ||||
| 91 | + | // appendTree flattens a subsurface at (x, y) from the root: its children | |||
| 92 | + | // below it, itself, then its children above. | |||
| 93 | + | func (s *surface) appendTree(out *[]ports.Subsurface, x, y int, below bool) { | |||
| 94 | + | for _, ch := range s.sub.children { | |||
| 95 | + | if ch.sub.below { | |||
| 96 | + | ch.appendTree(out, x+ch.sub.x, y+ch.sub.y, below) | |||
| 97 | + | } | |||
| 98 | + | } | |||
| 99 | + | if s.has { | |||
| 100 | + | *out = append(*out, ports.Subsurface{X: x, Y: y, Below: below, SurfaceContent: s.content}) | |||
| 101 | + | } | |||
| 102 | + | for _, ch := range s.sub.children { | |||
| 103 | + | if !ch.sub.below { | |||
| 104 | + | ch.appendTree(out, x+ch.sub.x, y+ch.sub.y, below) | |||
| 105 | + | } | |||
| 106 | + | } | |||
| 107 | + | } | |||
| 108 | + | ||||
| 109 | + | // redraw sends the tree of a mapped root to the outputs. | |||
| 110 | + | func (s *surface) redraw() { | |||
| 111 | + | r := s.root() | |||
| 112 | + | if id := r.windowID(); id != 0 && s.server.channels.Contents != nil { | |||
| 113 | + | s.server.emitContent(r.tree(id)) | |||
| 114 | + | } | |||
| 115 | + | } | |||
| 116 | + | ||||
| 117 | + | // detach removes a subsurface from its parent and redraws the parent. | |||
| 118 | + | func (s *surface) detach() { | |||
| 119 | + | p := s.sub.parent | |||
| 120 | + | if p == nil { | |||
| 121 | + | return | |||
| 122 | + | } | |||
| 123 | + | for i, ch := range p.sub.children { | |||
| 124 | + | if ch == s { | |||
| 125 | + | p.sub.children = append(p.sub.children[:i:i], p.sub.children[i+1:]...) | |||
| 126 | + | break | |||
| 127 | + | } | |||
| 128 | + | } | |||
| 129 | + | s.sub.parent = nil | |||
| 130 | + | p.redraw() | |||
| 131 | + | s.sendScale() | |||
| 38 | } | 132 | } | ||
| 39 | 133 | ||||
| 40 | func (s *surface) Destroy(*wayland.Surface) { | 134 | func (s *surface) Destroy(*wayland.Surface) { | ||
| 41 | s.destroyed = true | 135 | s.destroyed = true | ||
| 136 | + | s.detach() | |||
| 137 | + | for _, ch := range s.sub.children { | |||
| 138 | + | ch.sub.parent = nil | |||
| 139 | + | } | |||
| 140 | + | s.sub.children = nil | |||
| 42 | for _, cb := range s.callbacks { | 141 | for _, cb := range s.callbacks { | ||
| 43 | cb.Destroy() | 142 | cb.Destroy() | ||
| 44 | } | 143 | } | ||
| @@ -90,36 +189,46 @@ func (s *surface) Commit(*wayland.Surface) { | |||||
| 90 | if s.role != nil { | 189 | if s.role != nil { | ||
| 91 | s.role(s.current != nil) | 190 | s.role(s.current != nil) | ||
| 92 | } | 191 | } | ||
| 93 | - | id := ports.WindowID(0) | 192 | + | if fresh { |
| 94 | - | if s.xdg != nil && s.xdg.window != nil && s.xdg.window.mapped { | 193 | + | if state, ok := s.server.buffers[s.current.Resource]; ok { |
| 95 | - | id = s.xdg.window.id | 194 | + | if c, ok := state.content(0); ok { |
| 96 | - | } | |||
| 97 | - | if s.layer != nil && s.layer.mapped { | |||
| 98 | - | id = s.layer.id | |||
| 99 | - | } | |||
| 100 | - | if fresh && id != 0 && s.server.channels.Contents != nil { | |||
| 101 | - | b := s.current | |||
| 102 | - | if state, ok := s.server.buffers[b.Resource]; ok { | |||
| 103 | - | if c, ok := state.content(id); ok { | |||
| 104 | c.LogicalW, c.LogicalH = s.logicalSize(c.Width, c.Height) | 195 | c.LogicalW, c.LogicalH = s.logicalSize(c.Width, c.Height) | ||
| 105 | - | if c.Width != s.lastW || c.Height != s.lastH { | 196 | + | if s.sub.parent == nil && (c.Width != s.lastW || c.Height != s.lastH) { |
| 106 | s.lastW, s.lastH = c.Width, c.Height | 197 | s.lastW, s.lastH = c.Width, c.Height | ||
| 107 | - | s.server.log.Info().Uint64("id", uint64(id)).Int("w", c.Width).Int("h", c.Height).Msg("buffer size") | 198 | + | s.server.log.Info().Uint64("id", uint64(s.windowID())).Int("w", c.Width).Int("h", c.Height).Msg("buffer size") |
| 108 | } | 199 | } | ||
| 109 | - | s.server.emitContent(c) | 200 | + | s.content, s.has = c, true |
| 110 | } else if shm, ok := state.(*buffer); ok { | 201 | } else if shm, ok := state.(*buffer); ok { | ||
| 111 | shm.pool.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "SHM backing file truncated") | 202 | shm.pool.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "SHM backing file truncated") | ||
| 112 | } | 203 | } | ||
| 113 | // wl_shm pixels were copied: the client may reuse the buffer. | 204 | // wl_shm pixels were copied: the client may reuse the buffer. | ||
| 114 | // A dmabuf is read in place until the next buffer replaces it. | 205 | // A dmabuf is read in place until the next buffer replaces it. | ||
| 115 | if _, gpu := state.(*dmabufBuffer); !gpu { | 206 | if _, gpu := state.(*dmabufBuffer); !gpu { | ||
| 116 | - | if b.Resource.Alive() { | 207 | + | if s.current.Resource.Alive() { |
| 117 | - | b.SendRelease() | 208 | + | s.current.SendRelease() |
| 118 | } | 209 | } | ||
| 119 | s.released = true | 210 | s.released = true | ||
| 120 | } | 211 | } | ||
| 121 | } | 212 | } | ||
| 122 | } | 213 | } | ||
| 214 | + | if s.current == nil { | |||
| 215 | + | s.content, s.has = ports.SurfaceContent{}, false | |||
| 216 | + | } | |||
| 217 | + | // Subsurface positions apply on the parent commit. | |||
| 218 | + | moved := false | |||
| 219 | + | for _, ch := range s.sub.children { | |||
| 220 | + | if ch.sub.moved { | |||
| 221 | + | ch.sub.x, ch.sub.y, ch.sub.moved = ch.sub.pendX, ch.sub.pendY, false | |||
| 222 | + | moved = true | |||
| 223 | + | } | |||
| 224 | + | } | |||
| 225 | + | geometry := false | |||
| 226 | + | if s.xdg != nil && s.xdg.pendingGeometry != s.xdg.geometry { | |||
| 227 | + | s.xdg.geometry, geometry = s.xdg.pendingGeometry, true | |||
| 228 | + | } | |||
| 229 | + | if fresh || moved || geometry || s.sub.parent != nil { | |||
| 230 | + | s.redraw() | |||
| 231 | + | } | |||
| 123 | } | 232 | } | ||
| 124 | func (*surface) Damage(*wayland.Surface, int32, int32, int32, int32) {} | 233 | func (*surface) Damage(*wayland.Surface, int32, int32, int32, int32) {} | ||
| 125 | func (*surface) DamageBuffer(*wayland.Surface, int32, int32, int32, int32) {} | 234 | func (*surface) DamageBuffer(*wayland.Surface, int32, int32, int32, int32) {} | ||
M internal/adapters/wayland/xdg.go +20 -3
| @@ -64,6 +64,9 @@ type xdgSurface struct { | |||
| 64 | 64 | window *window | |
| 65 | 65 | popup *xdgshell.Popup | |
| 66 | 66 | serials []uint32 | |
| 67 | + | // geometry is the committed window geometry: the visible window inside | |
| 68 | + | // client shadows. Empty means the whole surface. | |
| 69 | + | geometry, pendingGeometry ports.Rect | |
| 67 | 70 | } | |
| 68 | 71 | ||
| 69 | 72 | func (x *xdgSurface) Destroy(r *xdgshell.Surface) { | |
| @@ -167,6 +170,7 @@ func (x *xdgSurface) GetToplevel(r *xdgshell.Surface, id uint32) { | |||
| 167 | 170 | x.configured = false | |
| 168 | 171 | x.acked = false | |
| 169 | 172 | x.serials = nil | |
| 173 | + | x.geometry, x.pendingGeometry = ports.Rect{}, ports.Rect{} | |
| 170 | 174 | } | |
| 171 | 175 | } | |
| 172 | 176 | func (x *xdgSurface) GetPopup(r *xdgshell.Surface, id uint32, _ *xdgshell.Surface, _ *xdgshell.Positioner) { | |
| @@ -181,13 +185,19 @@ func (x *xdgSurface) GetPopup(r *xdgshell.Surface, id uint32, _ *xdgshell.Surfac | |||
| 181 | 185 | } | |
| 182 | 186 | } | |
| 183 | 187 | ||
| 184 | - | // SetWindowGeometry is logged only: placement uses the full buffer for now. | |
| 185 | - | func (x *xdgSurface) SetWindowGeometry(_ *xdgshell.Surface, gx, gy, gw, gh int32) { | |
| 188 | + | // SetWindowGeometry sets the visible window inside the surface; it | |
| 189 | + | // applies on the next commit. | |
| 190 | + | func (x *xdgSurface) SetWindowGeometry(r *xdgshell.Surface, gx, gy, gw, gh int32) { | |
| 191 | + | if gw <= 0 || gh <= 0 { | |
| 192 | + | r.PostError(uint32(xdgshell.SurfaceErrorInvalidSize), "invalid window geometry") | |
| 193 | + | return | |
| 194 | + | } | |
| 195 | + | x.pendingGeometry = ports.Rect{X: int(gx), Y: int(gy), W: int(gw), H: int(gh)} | |
| 186 | 196 | id := uint64(0) | |
| 187 | 197 | if x.window != nil { | |
| 188 | 198 | id = uint64(x.window.id) | |
| 189 | 199 | } | |
| 190 | - | x.server.log.Info().Uint64("id", id).Int32("x", gx).Int32("y", gy).Int32("w", gw).Int32("h", gh).Msg("window geometry") | |
| 200 | + | x.server.log.Debug().Uint64("id", id).Int32("x", gx).Int32("y", gy).Int32("w", gw).Int32("h", gh).Msg("window geometry") | |
| 191 | 201 | } | |
| 192 | 202 | func (x *xdgSurface) AckConfigure(r *xdgshell.Surface, serial uint32) { | |
| 193 | 203 | for i, issued := range x.serials { | |
| @@ -239,6 +249,13 @@ func (popup) Destroy(*xdgshell.Popup) {} | |||
| 239 | 249 | func (popup) Grab(*xdgshell.Popup, *wayland.Seat, uint32) {} | |
| 240 | 250 | func (popup) Reposition(*xdgshell.Popup, *xdgshell.Positioner, uint32) {} | |
| 241 | 251 | ||
| 252 | + | // surfacePoint turns window coordinates (core's, from the geometry origin) | |
| 253 | + | // into surface coordinates. | |
| 254 | + | func (w *window) surfacePoint(x, y float64) (float64, float64) { | |
| 255 | + | g := w.xdg.geometry | |
| 256 | + | return x + float64(g.X), y + float64(g.Y) | |
| 257 | + | } | |
| 258 | + | ||
| 242 | 259 | func (x *xdgSurface) surfaceResource() *wayland.Surface { | |
| 243 | 260 | for resource, state := range x.server.surfaces { | |
| 244 | 261 | if state == x.surface && resource.Alive() { | |
| @@ -64,6 +64,9 @@ type xdgSurface struct { | |||||
| 64 | window *window | 64 | window *window | ||
| 65 | popup *xdgshell.Popup | 65 | popup *xdgshell.Popup | ||
| 66 | serials []uint32 | 66 | serials []uint32 | ||
| 67 | + | // geometry is the committed window geometry: the visible window inside | |||
| 68 | + | // client shadows. Empty means the whole surface. | |||
| 69 | + | geometry, pendingGeometry ports.Rect | |||
| 67 | } | 70 | } | ||
| 68 | 71 | ||||
| 69 | func (x *xdgSurface) Destroy(r *xdgshell.Surface) { | 72 | func (x *xdgSurface) Destroy(r *xdgshell.Surface) { | ||
| @@ -167,6 +170,7 @@ func (x *xdgSurface) GetToplevel(r *xdgshell.Surface, id uint32) { | |||||
| 167 | x.configured = false | 170 | x.configured = false | ||
| 168 | x.acked = false | 171 | x.acked = false | ||
| 169 | x.serials = nil | 172 | x.serials = nil | ||
| 173 | + | x.geometry, x.pendingGeometry = ports.Rect{}, ports.Rect{} | |||
| 170 | } | 174 | } | ||
| 171 | } | 175 | } | ||
| 172 | func (x *xdgSurface) GetPopup(r *xdgshell.Surface, id uint32, _ *xdgshell.Surface, _ *xdgshell.Positioner) { | 176 | func (x *xdgSurface) GetPopup(r *xdgshell.Surface, id uint32, _ *xdgshell.Surface, _ *xdgshell.Positioner) { | ||
| @@ -181,13 +185,19 @@ func (x *xdgSurface) GetPopup(r *xdgshell.Surface, id uint32, _ *xdgshell.Surfac | |||||
| 181 | } | 185 | } | ||
| 182 | } | 186 | } | ||
| 183 | 187 | ||||
| 184 | - | // SetWindowGeometry is logged only: placement uses the full buffer for now. | 188 | + | // SetWindowGeometry sets the visible window inside the surface; it |
| 185 | - | func (x *xdgSurface) SetWindowGeometry(_ *xdgshell.Surface, gx, gy, gw, gh int32) { | 189 | + | // applies on the next commit. |
| 190 | + | func (x *xdgSurface) SetWindowGeometry(r *xdgshell.Surface, gx, gy, gw, gh int32) { | |||
| 191 | + | if gw <= 0 || gh <= 0 { | |||
| 192 | + | r.PostError(uint32(xdgshell.SurfaceErrorInvalidSize), "invalid window geometry") | |||
| 193 | + | return | |||
| 194 | + | } | |||
| 195 | + | x.pendingGeometry = ports.Rect{X: int(gx), Y: int(gy), W: int(gw), H: int(gh)} | |||
| 186 | id := uint64(0) | 196 | id := uint64(0) | ||
| 187 | if x.window != nil { | 197 | if x.window != nil { | ||
| 188 | id = uint64(x.window.id) | 198 | id = uint64(x.window.id) | ||
| 189 | } | 199 | } | ||
| 190 | - | x.server.log.Info().Uint64("id", id).Int32("x", gx).Int32("y", gy).Int32("w", gw).Int32("h", gh).Msg("window geometry") | 200 | + | x.server.log.Debug().Uint64("id", id).Int32("x", gx).Int32("y", gy).Int32("w", gw).Int32("h", gh).Msg("window geometry") |
| 191 | } | 201 | } | ||
| 192 | func (x *xdgSurface) AckConfigure(r *xdgshell.Surface, serial uint32) { | 202 | func (x *xdgSurface) AckConfigure(r *xdgshell.Surface, serial uint32) { | ||
| 193 | for i, issued := range x.serials { | 203 | for i, issued := range x.serials { | ||
| @@ -239,6 +249,13 @@ func (popup) Destroy(*xdgshell.Popup) {} | |||||
| 239 | func (popup) Grab(*xdgshell.Popup, *wayland.Seat, uint32) {} | 249 | func (popup) Grab(*xdgshell.Popup, *wayland.Seat, uint32) {} | ||
| 240 | func (popup) Reposition(*xdgshell.Popup, *xdgshell.Positioner, uint32) {} | 250 | func (popup) Reposition(*xdgshell.Popup, *xdgshell.Positioner, uint32) {} | ||
| 241 | 251 | ||||
| 252 | + | // surfacePoint turns window coordinates (core's, from the geometry origin) | |||
| 253 | + | // into surface coordinates. | |||
| 254 | + | func (w *window) surfacePoint(x, y float64) (float64, float64) { | |||
| 255 | + | g := w.xdg.geometry | |||
| 256 | + | return x + float64(g.X), y + float64(g.Y) | |||
| 257 | + | } | |||
| 258 | + | ||||
| 242 | func (x *xdgSurface) surfaceResource() *wayland.Surface { | 259 | func (x *xdgSurface) surfaceResource() *wayland.Surface { | ||
| 243 | for resource, state := range x.server.surfaces { | 260 | for resource, state := range x.server.surfaces { | ||
| 244 | if state == x.surface && resource.Alive() { | 261 | if state == x.surface && resource.Alive() { | ||
M internal/ports/messages.go +18 -1
| @@ -346,6 +346,8 @@ type SceneLayer struct { | |||
| 346 | 346 | // GPU buffer the renderer reads in place. Receivers never modify either. | |
| 347 | 347 | // Empty means the window has no content. LogicalW and LogicalH are the | |
| 348 | 348 | // surface size in logical pixels (buffer scale and viewport applied). | |
| 349 | + | // Subsurfaces come in Children; Geometry is the part of the surface that | |
| 350 | + | // is the window (xdg window geometry), the rest being client shadows. | |
| 349 | 351 | type SurfaceContent struct { | |
| 350 | 352 | ID WindowID | |
| 351 | 353 | Width, Height int | |
| @@ -354,10 +356,25 @@ type SurfaceContent struct { | |||
| 354 | 356 | Opaque bool // x formats: ignore the alpha byte | |
| 355 | 357 | Pixels []byte | |
| 356 | 358 | DMABuf *DMABuf | |
| 359 | + | // Children are the subsurfaces, bottom to top, flattened. | |
| 360 | + | Children []Subsurface | |
| 361 | + | // Geometry is in logical pixels from the surface origin; empty means | |
| 362 | + | // the whole surface. | |
| 363 | + | Geometry Rect | |
| 364 | + | } | |
| 365 | + | ||
| 366 | + | // Subsurface is a child surface at X, Y logical pixels from the root | |
| 367 | + | // surface origin. Below children are drawn under the root surface. | |
| 368 | + | type Subsurface struct { | |
| 369 | + | X, Y int | |
| 370 | + | Below bool | |
| 371 | + | SurfaceContent | |
| 357 | 372 | } | |
| 358 | 373 | ||
| 359 | 374 | // Empty reports whether the content has nothing to draw. | |
| 360 | - | func (c SurfaceContent) Empty() bool { return c.Pixels == nil && c.DMABuf == nil } | |
| 375 | + | func (c SurfaceContent) Empty() bool { | |
| 376 | + | return c.Pixels == nil && c.DMABuf == nil && len(c.Children) == 0 | |
| 377 | + | } | |
| 361 | 378 | ||
| 362 | 379 | // DMABuf is a client GPU buffer (linux-dmabuf). Its files stay open while | |
| 363 | 380 | // the client's buffer exists; a renderer that keeps it must duplicate the | |
| @@ -346,6 +346,8 @@ type SceneLayer struct { | |||||
| 346 | // GPU buffer the renderer reads in place. Receivers never modify either. | 346 | // GPU buffer the renderer reads in place. Receivers never modify either. | ||
| 347 | // Empty means the window has no content. LogicalW and LogicalH are the | 347 | // Empty means the window has no content. LogicalW and LogicalH are the | ||
| 348 | // surface size in logical pixels (buffer scale and viewport applied). | 348 | // surface size in logical pixels (buffer scale and viewport applied). | ||
| 349 | + | // Subsurfaces come in Children; Geometry is the part of the surface that | |||
| 350 | + | // is the window (xdg window geometry), the rest being client shadows. | |||
| 349 | type SurfaceContent struct { | 351 | type SurfaceContent struct { | ||
| 350 | ID WindowID | 352 | ID WindowID | ||
| 351 | Width, Height int | 353 | Width, Height int | ||
| @@ -354,10 +356,25 @@ type SurfaceContent struct { | |||||
| 354 | Opaque bool // x formats: ignore the alpha byte | 356 | Opaque bool // x formats: ignore the alpha byte | ||
| 355 | Pixels []byte | 357 | Pixels []byte | ||
| 356 | DMABuf *DMABuf | 358 | DMABuf *DMABuf | ||
| 359 | + | // Children are the subsurfaces, bottom to top, flattened. | |||
| 360 | + | Children []Subsurface | |||
| 361 | + | // Geometry is in logical pixels from the surface origin; empty means | |||
| 362 | + | // the whole surface. | |||
| 363 | + | Geometry Rect | |||
| 364 | + | } | |||
| 365 | + | ||||
| 366 | + | // Subsurface is a child surface at X, Y logical pixels from the root | |||
| 367 | + | // surface origin. Below children are drawn under the root surface. | |||
| 368 | + | type Subsurface struct { | |||
| 369 | + | X, Y int | |||
| 370 | + | Below bool | |||
| 371 | + | SurfaceContent | |||
| 357 | } | 372 | } | ||
| 358 | 373 | ||||
| 359 | // Empty reports whether the content has nothing to draw. | 374 | // Empty reports whether the content has nothing to draw. | ||
| 360 | - | func (c SurfaceContent) Empty() bool { return c.Pixels == nil && c.DMABuf == nil } | 375 | + | func (c SurfaceContent) Empty() bool { |
| 376 | + | return c.Pixels == nil && c.DMABuf == nil && len(c.Children) == 0 | |||
| 377 | + | } | |||
| 361 | 378 | ||||
| 362 | // DMABuf is a client GPU buffer (linux-dmabuf). Its files stay open while | 379 | // DMABuf is a client GPU buffer (linux-dmabuf). Its files stay open while | ||
| 363 | // the client's buffer exists; a renderer that keeps it must duplicate the | 380 | // the client's buffer exists; a renderer that keeps it must duplicate the | ||