detached f338421

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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) {
7878 if got, want := takeEvents(t, c, p.events), []string{"title editor - file", "done"}; !slices.Equal(got, want) {
7979 t.Fatalf("title = %q, want %q", got, want)
8080 }
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.
8583 requestProtocol(t, c, list, ext.ExtForeignToplevelListV1RequestStop)
8684 if err := c.Roundtrip(); err != nil {
8785 t.Fatal(err)
@@ -89,6 +87,14 @@ func TestExtForeignToplevelList(t *testing.T) {
8987 if ev := <-p.events; ev != "finished" {
9088 t.Fatalf("stop = %q", ev)
9189 }
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+ }
9298 }
9399
94100 // A handle of the list makes a window capture source: its session opens on
@@ -150,10 +156,10 @@ func TestWindowCaptureSource(t *testing.T) {
150156 requestProtocol(t, cc.c, surf, wayland.SurfaceRequestCommit)
151157 ackAndAttach(t, cc.c, surf, xdg, shmBuffer(t, cc.c), serials)
152158 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)
156161 }
162+ got := takeEvents(t, cc.c, p.events)
157163 if !slices.Contains(got, "identifier "+toplevelIdentifier(win.ID, 2)) {
158164 t.Fatalf("remap = %q", got)
159165 }
@@ -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 on100 // 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"
1111 // target, a workspace or a window, is rendered off screen at a time.
1212
1313 // 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).
1617 func (c *Core) windowSize(w *Workspace, id WindowID) Rect {
1718 for _, p := range w.Layout() {
1819 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 {
2021 return Rect{W: r.W, H: r.H}
2122 }
2223 }
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}
2926 }
3027 if v, ok := c.configures.sent[id]; ok && v.Width > 0 && v.Height > 0 {
3128 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 its13 // 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), the14+// 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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close