fix(capture): size a stashed captured window from its stash configure
Drop the stash entries from the window-capture size fallback: their size is stale for a former free float. Assert the closed event on unmap and title updates after stop in the ext-foreign-toplevel tests.
- commit
- f338421dd643c2e20e4225e5c0183051357f62ba
- author
- brice · · 2026-10-03 08:33 UTC
- committer
- brice · · 2026-10-03 08:36 UTC
- parent
- d6ed4fd
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email - refs
- feat/window-screencast
2 files · +18 -15
M internal/adapters/wayland/extforeigntoplevel_test.go +13 -7
| @@ -78,10 +78,8 @@ func TestExtForeignToplevelList(t *testing.T) { | |||
| 78 | 78 | if got, want := takeEvents(t, c, p.events), []string{"title editor - file", "done"}; !slices.Equal(got, want) { | |
| 79 | 79 | t.Fatalf("title = %q, want %q", got, want) | |
| 80 | 80 | } | |
| 81 | - | requestProtocol(t, c, top, xdgshell.ToplevelRequestDestroy) | |
| 82 | - | if got := takeEvents(t, c, p.events); !slices.Equal(got, []string{"closed"}) { | |
| 83 | - | t.Fatalf("close = %q", got) | |
| 84 | - | } | |
| 81 | + | ||
| 82 | + | // After stop, a handle already sent still follows its window. | |
| 85 | 83 | requestProtocol(t, c, list, ext.ExtForeignToplevelListV1RequestStop) | |
| 86 | 84 | if err := c.Roundtrip(); err != nil { | |
| 87 | 85 | t.Fatal(err) | |
| @@ -89,6 +87,14 @@ func TestExtForeignToplevelList(t *testing.T) { | |||
| 89 | 87 | if ev := <-p.events; ev != "finished" { | |
| 90 | 88 | t.Fatalf("stop = %q", ev) | |
| 91 | 89 | } | |
| 90 | + | requestProtocol(t, c, top, xdgshell.ToplevelRequestSetTitle, "editor - other") | |
| 91 | + | if got, want := takeEvents(t, c, p.events), []string{"title editor - other", "done"}; !slices.Equal(got, want) { | |
| 92 | + | t.Fatalf("title after stop = %q, want %q", got, want) | |
| 93 | + | } | |
| 94 | + | requestProtocol(t, c, top, xdgshell.ToplevelRequestDestroy) | |
| 95 | + | if got := takeEvents(t, c, p.events); !slices.Equal(got, []string{"closed"}) { | |
| 96 | + | t.Fatalf("close = %q", got) | |
| 97 | + | } | |
| 92 | 98 | } | |
| 93 | 99 | ||
| 94 | 100 | // A handle of the list makes a window capture source: its session opens on | |
| @@ -150,10 +156,10 @@ func TestWindowCaptureSource(t *testing.T) { | |||
| 150 | 156 | requestProtocol(t, cc.c, surf, wayland.SurfaceRequestCommit) | |
| 151 | 157 | ackAndAttach(t, cc.c, surf, xdg, shmBuffer(t, cc.c), serials) | |
| 152 | 158 | clientEvent[ports.WindowMapped](t, h.events) | |
| 153 | - | got := takeEvents(t, cc.c, p.events) | |
| 154 | - | if slices.Equal(got, []string{"closed"}) { | |
| 155 | - | got = takeEvents(t, cc.c, p.events) | |
| 159 | + | if got := takeEvents(t, cc.c, p.events); !slices.Equal(got, []string{"closed"}) { | |
| 160 | + | t.Fatalf("unmap = %q, want closed", got) | |
| 156 | 161 | } | |
| 162 | + | got := takeEvents(t, cc.c, p.events) | |
| 157 | 163 | if !slices.Contains(got, "identifier "+toplevelIdentifier(win.ID, 2)) { | |
| 158 | 164 | t.Fatalf("remap = %q", got) | |
| 159 | 165 | } | |
| @@ -78,10 +78,8 @@ func TestExtForeignToplevelList(t *testing.T) { | |||||
| 78 | if got, want := takeEvents(t, c, p.events), []string{"title editor - file", "done"}; !slices.Equal(got, want) { | 78 | if got, want := takeEvents(t, c, p.events), []string{"title editor - file", "done"}; !slices.Equal(got, want) { | ||
| 79 | t.Fatalf("title = %q, want %q", got, want) | 79 | t.Fatalf("title = %q, want %q", got, want) | ||
| 80 | } | 80 | } | ||
| 81 | - | requestProtocol(t, c, top, xdgshell.ToplevelRequestDestroy) | 81 | + | |
| 82 | - | if got := takeEvents(t, c, p.events); !slices.Equal(got, []string{"closed"}) { | 82 | + | // After stop, a handle already sent still follows its window. |
| 83 | - | t.Fatalf("close = %q", got) | |||
| 84 | - | } | |||
| 85 | requestProtocol(t, c, list, ext.ExtForeignToplevelListV1RequestStop) | 83 | requestProtocol(t, c, list, ext.ExtForeignToplevelListV1RequestStop) | ||
| 86 | if err := c.Roundtrip(); err != nil { | 84 | if err := c.Roundtrip(); err != nil { | ||
| 87 | t.Fatal(err) | 85 | t.Fatal(err) | ||
| @@ -89,6 +87,14 @@ func TestExtForeignToplevelList(t *testing.T) { | |||||
| 89 | if ev := <-p.events; ev != "finished" { | 87 | if ev := <-p.events; ev != "finished" { | ||
| 90 | t.Fatalf("stop = %q", ev) | 88 | t.Fatalf("stop = %q", ev) | ||
| 91 | } | 89 | } | ||
| 90 | + | requestProtocol(t, c, top, xdgshell.ToplevelRequestSetTitle, "editor - other") | |||
| 91 | + | if got, want := takeEvents(t, c, p.events), []string{"title editor - other", "done"}; !slices.Equal(got, want) { | |||
| 92 | + | t.Fatalf("title after stop = %q, want %q", got, want) | |||
| 93 | + | } | |||
| 94 | + | requestProtocol(t, c, top, xdgshell.ToplevelRequestDestroy) | |||
| 95 | + | if got := takeEvents(t, c, p.events); !slices.Equal(got, []string{"closed"}) { | |||
| 96 | + | t.Fatalf("close = %q", got) | |||
| 97 | + | } | |||
| 92 | } | 98 | } | ||
| 93 | 99 | ||||
| 94 | // A handle of the list makes a window capture source: its session opens on | 100 | // A handle of the list makes a window capture source: its session opens on | ||
| @@ -150,10 +156,10 @@ func TestWindowCaptureSource(t *testing.T) { | |||||
| 150 | requestProtocol(t, cc.c, surf, wayland.SurfaceRequestCommit) | 156 | requestProtocol(t, cc.c, surf, wayland.SurfaceRequestCommit) | ||
| 151 | ackAndAttach(t, cc.c, surf, xdg, shmBuffer(t, cc.c), serials) | 157 | ackAndAttach(t, cc.c, surf, xdg, shmBuffer(t, cc.c), serials) | ||
| 152 | clientEvent[ports.WindowMapped](t, h.events) | 158 | clientEvent[ports.WindowMapped](t, h.events) | ||
| 153 | - | got := takeEvents(t, cc.c, p.events) | 159 | + | if got := takeEvents(t, cc.c, p.events); !slices.Equal(got, []string{"closed"}) { |
| 154 | - | if slices.Equal(got, []string{"closed"}) { | 160 | + | t.Fatalf("unmap = %q, want closed", got) |
| 155 | - | got = takeEvents(t, cc.c, p.events) | |||
| 156 | } | 161 | } | ||
| 162 | + | got := takeEvents(t, cc.c, p.events) | |||
| 157 | if !slices.Contains(got, "identifier "+toplevelIdentifier(win.ID, 2)) { | 163 | if !slices.Contains(got, "identifier "+toplevelIdentifier(win.ID, 2)) { | ||
| 158 | t.Fatalf("remap = %q", got) | 164 | t.Fatalf("remap = %q", got) | ||
| 159 | } | 165 | } | ||
M internal/core/capture_window.go +5 -8
| @@ -11,8 +11,9 @@ import "github.com/bnema/neferwl/internal/ports" | |||
| 11 | 11 | // target, a workspace or a window, is rendered off screen at a time. | |
| 12 | 12 | ||
| 13 | 13 | // windowSize is a window's client size, at the origin: its placement on its | |
| 14 | - | // own workspace or, when that hides it (a stashed or covered float), the | |
| 15 | - | // float's own size, else the size it was last configured to. | |
| 14 | + | // own workspace or, when that hides it (a covered float), the float's own | |
| 15 | + | // size, else the size it was last configured to (a stashed window keeps its | |
| 16 | + | // imposed stash size). | |
| 16 | 17 | func (c *Core) windowSize(w *Workspace, id WindowID) Rect { | |
| 17 | 18 | for _, p := range w.Layout() { | |
| 18 | 19 | if p.ID == id && !p.Hidden && p.Rect.W > 0 && p.Rect.H > 0 { | |
| @@ -20,12 +21,8 @@ func (c *Core) windowSize(w *Workspace, id WindowID) Rect { | |||
| 20 | 21 | return Rect{W: r.W, H: r.H} | |
| 21 | 22 | } | |
| 22 | 23 | } | |
| 23 | - | for _, fs := range [][]Float{w.Floats, w.Stash} { | |
| 24 | - | for _, f := range fs { | |
| 25 | - | if f.ID == id && f.W > 0 && f.H > 0 { | |
| 26 | - | return Rect{W: f.W, H: f.H} | |
| 27 | - | } | |
| 28 | - | } | |
| 24 | + | if i := w.floatIndex(id); i >= 0 && w.Floats[i].W > 0 && w.Floats[i].H > 0 { | |
| 25 | + | return Rect{W: w.Floats[i].W, H: w.Floats[i].H} | |
| 29 | 26 | } | |
| 30 | 27 | if v, ok := c.configures.sent[id]; ok && v.Width > 0 && v.Height > 0 { | |
| 31 | 28 | return Rect{W: v.Width, H: v.Height} | |
| @@ -11,8 +11,9 @@ import "github.com/bnema/neferwl/internal/ports" | |||||
| 11 | // target, a workspace or a window, is rendered off screen at a time. | 11 | // target, a workspace or a window, is rendered off screen at a time. | ||
| 12 | 12 | ||||
| 13 | // windowSize is a window's client size, at the origin: its placement on its | 13 | // windowSize is a window's client size, at the origin: its placement on its | ||
| 14 | - | // own workspace or, when that hides it (a stashed or covered float), the | 14 | + | // own workspace or, when that hides it (a covered float), the float's own |
| 15 | - | // float's own size, else the size it was last configured to. | 15 | + | // size, else the size it was last configured to (a stashed window keeps its |
| 16 | + | // imposed stash size). | |||
| 16 | func (c *Core) windowSize(w *Workspace, id WindowID) Rect { | 17 | func (c *Core) windowSize(w *Workspace, id WindowID) Rect { | ||
| 17 | for _, p := range w.Layout() { | 18 | for _, p := range w.Layout() { | ||
| 18 | if p.ID == id && !p.Hidden && p.Rect.W > 0 && p.Rect.H > 0 { | 19 | if p.ID == id && !p.Hidden && p.Rect.W > 0 && p.Rect.H > 0 { | ||
| @@ -20,12 +21,8 @@ func (c *Core) windowSize(w *Workspace, id WindowID) Rect { | |||||
| 20 | return Rect{W: r.W, H: r.H} | 21 | return Rect{W: r.W, H: r.H} | ||
| 21 | } | 22 | } | ||
| 22 | } | 23 | } | ||
| 23 | - | for _, fs := range [][]Float{w.Floats, w.Stash} { | 24 | + | if i := w.floatIndex(id); i >= 0 && w.Floats[i].W > 0 && w.Floats[i].H > 0 { |
| 24 | - | for _, f := range fs { | 25 | + | return Rect{W: w.Floats[i].W, H: w.Floats[i].H} |
| 25 | - | if f.ID == id && f.W > 0 && f.H > 0 { | |||
| 26 | - | return Rect{W: f.W, H: f.H} | |||
| 27 | - | } | |||
| 28 | - | } | |||
| 29 | } | 26 | } | ||
| 30 | if v, ok := c.configures.sent[id]; ok && v.Width > 0 && v.Height > 0 { | 27 | if v, ok := c.configures.sent[id]; ok && v.Width > 0 && v.Height > 0 { | ||
| 31 | return Rect{W: v.Width, H: v.Height} | 28 | return Rect{W: v.Width, H: v.Height} | ||