detached 3d41bc5

#2 fix(selection): freeze outputs wider than 4096 pixels

state
merged · 3d41bc5 · fast-forward
branch
fix/freeze-mode → main
opened
bnema ·
updated
7 files · +33 -15
7/7
M go.mod +1 -1
@@ -6,7 +6,7 @@ require (
66 github.com/bnema/go-wayland-bindings v0.1.0
77 github.com/bnema/kvconf v0.1.2
88 github.com/bnema/neferclient v0.3.0
9- github.com/bnema/nefergui v0.7.2
9+ github.com/bnema/nefergui v0.7.3
1010 github.com/bnema/purego-libwayland v0.8.0
1111 github.com/bnema/wlturbo v0.6.2
1212 github.com/bnema/zerowrap v1.4.1
@@ -6,7 +6,7 @@ require (
6 github.com/bnema/go-wayland-bindings v0.1.06 github.com/bnema/go-wayland-bindings v0.1.0
7 github.com/bnema/kvconf v0.1.27 github.com/bnema/kvconf v0.1.2
8 github.com/bnema/neferclient v0.3.08 github.com/bnema/neferclient v0.3.0
9- github.com/bnema/nefergui v0.7.29+ github.com/bnema/nefergui v0.7.3
10 github.com/bnema/purego-libwayland v0.8.010 github.com/bnema/purego-libwayland v0.8.0
11 github.com/bnema/wlturbo v0.6.211 github.com/bnema/wlturbo v0.6.2
12 github.com/bnema/zerowrap v1.4.112 github.com/bnema/zerowrap v1.4.1
M go.sum +2 -2
@@ -4,8 +4,8 @@ github.com/bnema/kvconf v0.1.2 h1:1hDKL9jkQSBdR9dSKbwTONhUUBfJgAdh5vu+yFHOb70=
44 github.com/bnema/kvconf v0.1.2/go.mod h1:+P2X9I/sLx+Lfir45s9a7WA0GnC22WgKbZWE9LK6lcU=
55 github.com/bnema/neferclient v0.3.0 h1:sEBphi11YpgMOPYHW1OcDtQYA8aD3WSaFILnb8fdp90=
66 github.com/bnema/neferclient v0.3.0/go.mod h1:CzZflJIvinLP8u0rYPjdmAnZjl4Ec2cvdnEGvqFO4Yw=
7-github.com/bnema/nefergui v0.7.2 h1:9gnYhVlMxpBp+cVE+9Uursc/HwPXe/Li16+m4HL/gkU=
8-github.com/bnema/nefergui v0.7.2/go.mod h1:G2iRRleA9bjSohU1RySwRqqxS0BbkPziK407HkuyEBc=
7+github.com/bnema/nefergui v0.7.3 h1:T6NamM17arkan3K38+YF1/flsQ/v4ROSytSNNMz+3M4=
8+github.com/bnema/nefergui v0.7.3/go.mod h1:G2iRRleA9bjSohU1RySwRqqxS0BbkPziK407HkuyEBc=
99 github.com/bnema/purego v0.13.0-bnema.1 h1:fKRdztnvTmRqAjANMoN+utEIH73dvPqVPJh2iZCCAO8=
1010 github.com/bnema/purego v0.13.0-bnema.1/go.mod h1:xS1iX3noypxnbOUEUAqOvkfnUzgHuu19VtL4+dfUMJM=
1111 github.com/bnema/purego-libwayland v0.8.0 h1:e82G1nMre4HvnR1Q8TvdnVNITUStSc+hD3eMwkYjxQY=
@@ -4,8 +4,8 @@ github.com/bnema/kvconf v0.1.2 h1:1hDKL9jkQSBdR9dSKbwTONhUUBfJgAdh5vu+yFHOb70=
4 github.com/bnema/kvconf v0.1.2/go.mod h1:+P2X9I/sLx+Lfir45s9a7WA0GnC22WgKbZWE9LK6lcU=4 github.com/bnema/kvconf v0.1.2/go.mod h1:+P2X9I/sLx+Lfir45s9a7WA0GnC22WgKbZWE9LK6lcU=
5 github.com/bnema/neferclient v0.3.0 h1:sEBphi11YpgMOPYHW1OcDtQYA8aD3WSaFILnb8fdp90=5 github.com/bnema/neferclient v0.3.0 h1:sEBphi11YpgMOPYHW1OcDtQYA8aD3WSaFILnb8fdp90=
6 github.com/bnema/neferclient v0.3.0/go.mod h1:CzZflJIvinLP8u0rYPjdmAnZjl4Ec2cvdnEGvqFO4Yw=6 github.com/bnema/neferclient v0.3.0/go.mod h1:CzZflJIvinLP8u0rYPjdmAnZjl4Ec2cvdnEGvqFO4Yw=
7-github.com/bnema/nefergui v0.7.2 h1:9gnYhVlMxpBp+cVE+9Uursc/HwPXe/Li16+m4HL/gkU=7+github.com/bnema/nefergui v0.7.3 h1:T6NamM17arkan3K38+YF1/flsQ/v4ROSytSNNMz+3M4=
8-github.com/bnema/nefergui v0.7.2/go.mod h1:G2iRRleA9bjSohU1RySwRqqxS0BbkPziK407HkuyEBc=8+github.com/bnema/nefergui v0.7.3/go.mod h1:G2iRRleA9bjSohU1RySwRqqxS0BbkPziK407HkuyEBc=
9 github.com/bnema/purego v0.13.0-bnema.1 h1:fKRdztnvTmRqAjANMoN+utEIH73dvPqVPJh2iZCCAO8=9 github.com/bnema/purego v0.13.0-bnema.1 h1:fKRdztnvTmRqAjANMoN+utEIH73dvPqVPJh2iZCCAO8=
10 github.com/bnema/purego v0.13.0-bnema.1/go.mod h1:xS1iX3noypxnbOUEUAqOvkfnUzgHuu19VtL4+dfUMJM=10 github.com/bnema/purego v0.13.0-bnema.1/go.mod h1:xS1iX3noypxnbOUEUAqOvkfnUzgHuu19VtL4+dfUMJM=
11 github.com/bnema/purego-libwayland v0.8.0 h1:e82G1nMre4HvnR1Q8TvdnVNITUStSc+hD3eMwkYjxQY=11 github.com/bnema/purego-libwayland v0.8.0 h1:e82G1nMre4HvnR1Q8TvdnVNITUStSc+hD3eMwkYjxQY=
M internal/adapters/selection/backdrop_test.go +11 -2
@@ -31,13 +31,22 @@ func TestWithBackdrops(t *testing.T) {
3131
3232 huge := ports.Frame{Width: maxBackdrop + 1, Height: 1, Stride: (maxBackdrop + 1) * 4, Format: ports.XRGB8888}
3333 _, err = New(ports.Screenshot, ports.VideoSettings{}, nil, false).WithBackdrops(map[uint32]ports.Frame{9: huge})
34- assert.ErrorContains(t, err, "at most 4096", "the caller freezes only outputs CanBackdrop accepts")
34+ assert.ErrorContains(t, err, "at most 8192", "the caller freezes only outputs CanBackdrop accepts")
35+
36+ huge = ports.Frame{Width: 7680, Height: 4320, Stride: 7680 * 4, Format: ports.XRGB8888}
37+ _, err = New(ports.Screenshot, ports.VideoSettings{}, nil, false).WithBackdrops(map[uint32]ports.Frame{9: huge})
38+ assert.ErrorContains(t, err, "67108864 bytes")
3539 }
3640
3741 func TestCanBackdrop(t *testing.T) {
3842 assert.True(t, CanBackdrop(ports.Output{Width: 3840, Height: 2160}))
3943 assert.True(t, CanBackdrop(ports.Output{Width: 2160, Height: 4096}))
40- assert.False(t, CanBackdrop(ports.Output{Width: 5120, Height: 2880}))
44+ assert.True(t, CanBackdrop(ports.Output{Width: 5120, Height: 2160}))
45+ assert.True(t, CanBackdrop(ports.Output{Width: 8192, Height: 2048}), "exact byte budget")
46+ assert.False(t, CanBackdrop(ports.Output{Width: 8192, Height: 2049}))
47+ assert.False(t, CanBackdrop(ports.Output{Width: 7680, Height: 4320}))
48+ assert.False(t, CanBackdrop(ports.Output{Width: 8193, Height: 1}))
49+ assert.False(t, CanBackdrop(ports.Output{Width: 1, Height: 8193}))
4150 assert.False(t, CanBackdrop(ports.Output{}), "an unknown mode stays live")
4251 }
4352
@@ -31,13 +31,22 @@ func TestWithBackdrops(t *testing.T) {
31 31
32 huge := ports.Frame{Width: maxBackdrop + 1, Height: 1, Stride: (maxBackdrop + 1) * 4, Format: ports.XRGB8888}32 huge := ports.Frame{Width: maxBackdrop + 1, Height: 1, Stride: (maxBackdrop + 1) * 4, Format: ports.XRGB8888}
33 _, err = New(ports.Screenshot, ports.VideoSettings{}, nil, false).WithBackdrops(map[uint32]ports.Frame{9: huge})33 _, err = New(ports.Screenshot, ports.VideoSettings{}, nil, false).WithBackdrops(map[uint32]ports.Frame{9: huge})
34- assert.ErrorContains(t, err, "at most 4096", "the caller freezes only outputs CanBackdrop accepts")34+ assert.ErrorContains(t, err, "at most 8192", "the caller freezes only outputs CanBackdrop accepts")
35+
36+ huge = ports.Frame{Width: 7680, Height: 4320, Stride: 7680 * 4, Format: ports.XRGB8888}
37+ _, err = New(ports.Screenshot, ports.VideoSettings{}, nil, false).WithBackdrops(map[uint32]ports.Frame{9: huge})
38+ assert.ErrorContains(t, err, "67108864 bytes")
35 }39 }
36 40
37 func TestCanBackdrop(t *testing.T) {41 func TestCanBackdrop(t *testing.T) {
38 assert.True(t, CanBackdrop(ports.Output{Width: 3840, Height: 2160}))42 assert.True(t, CanBackdrop(ports.Output{Width: 3840, Height: 2160}))
39 assert.True(t, CanBackdrop(ports.Output{Width: 2160, Height: 4096}))43 assert.True(t, CanBackdrop(ports.Output{Width: 2160, Height: 4096}))
40- assert.False(t, CanBackdrop(ports.Output{Width: 5120, Height: 2880}))44+ assert.True(t, CanBackdrop(ports.Output{Width: 5120, Height: 2160}))
45+ assert.True(t, CanBackdrop(ports.Output{Width: 8192, Height: 2048}), "exact byte budget")
46+ assert.False(t, CanBackdrop(ports.Output{Width: 8192, Height: 2049}))
47+ assert.False(t, CanBackdrop(ports.Output{Width: 7680, Height: 4320}))
48+ assert.False(t, CanBackdrop(ports.Output{Width: 8193, Height: 1}))
49+ assert.False(t, CanBackdrop(ports.Output{Width: 1, Height: 8193}))
41 assert.False(t, CanBackdrop(ports.Output{}), "an unknown mode stays live")50 assert.False(t, CanBackdrop(ports.Output{}), "an unknown mode stays live")
42 }51 }
43 52
M internal/adapters/selection/selector.go +11 -7
@@ -87,13 +87,17 @@ func (s *Selector) WithCapabilities(c ports.Capabilities) *Selector {
8787 return s
8888 }
8989
90-// maxBackdrop is the largest side NeferGUI accepts for one image.
91-const maxBackdrop = 4096
90+// These bounds mirror NeferGUI's image dimension and cache byte limits.
91+const (
92+ maxBackdrop = 8192
93+ maxBackdropBytes = 64 << 20
94+)
9295
93-// CanBackdrop reports whether an output of this physical mode can show a
94-// frozen frame. A larger output must stay live: freeze only the others.
96+// CanBackdrop reports whether an output's physical mode fits NeferGUI's image
97+// limits. Outputs exceeding either the side or byte limit stay live.
9598 func CanBackdrop(o ports.Output) bool {
96- return o.Width > 0 && o.Height > 0 && max(o.Width, o.Height) <= maxBackdrop
99+ return o.Width > 0 && o.Height > 0 && max(o.Width, o.Height) <= maxBackdrop &&
100+ o.Width <= maxBackdropBytes/ports.BytesPerPixel/o.Height
97101 }
98102
99103 // WithBackdrops draws each output's frozen frame under its overlay instead of
@@ -103,8 +107,8 @@ func CanBackdrop(o ports.Output) bool {
103107 func (s *Selector) WithBackdrops(frames map[uint32]ports.Frame) (*Selector, error) {
104108 s.backdrops = make(map[uint32]image.Image, len(frames))
105109 for id, f := range frames {
106- if f.Width > maxBackdrop || f.Height > maxBackdrop {
107- return s, fmt.Errorf("selection: frozen frame of output %d is %dx%d, at most %d per side", id, f.Width, f.Height, maxBackdrop)
110+ if !CanBackdrop(ports.Output{Width: f.Width, Height: f.Height}) {
111+ return s, fmt.Errorf("selection: frozen frame of output %d is %dx%d, at most %d per side and %d bytes", id, f.Width, f.Height, maxBackdrop, maxBackdropBytes)
108112 }
109113 s.backdrops[id] = backdropImage(f)
110114 }
@@ -87,13 +87,17 @@ func (s *Selector) WithCapabilities(c ports.Capabilities) *Selector {
87 return s87 return s
88 }88 }
89 89
90-// maxBackdrop is the largest side NeferGUI accepts for one image.90+// These bounds mirror NeferGUI's image dimension and cache byte limits.
91-const maxBackdrop = 409691+const (
92+ maxBackdrop = 8192
93+ maxBackdropBytes = 64 << 20
94+)
92 95
93-// CanBackdrop reports whether an output of this physical mode can show a96+// CanBackdrop reports whether an output's physical mode fits NeferGUI's image
94-// frozen frame. A larger output must stay live: freeze only the others.97+// limits. Outputs exceeding either the side or byte limit stay live.
95 func CanBackdrop(o ports.Output) bool {98 func CanBackdrop(o ports.Output) bool {
96- return o.Width > 0 && o.Height > 0 && max(o.Width, o.Height) <= maxBackdrop99+ return o.Width > 0 && o.Height > 0 && max(o.Width, o.Height) <= maxBackdrop &&
100+ o.Width <= maxBackdropBytes/ports.BytesPerPixel/o.Height
97 }101 }
98 102
99 // WithBackdrops draws each output's frozen frame under its overlay instead of103 // WithBackdrops draws each output's frozen frame under its overlay instead of
@@ -103,8 +107,8 @@ func CanBackdrop(o ports.Output) bool {
103 func (s *Selector) WithBackdrops(frames map[uint32]ports.Frame) (*Selector, error) {107 func (s *Selector) WithBackdrops(frames map[uint32]ports.Frame) (*Selector, error) {
104 s.backdrops = make(map[uint32]image.Image, len(frames))108 s.backdrops = make(map[uint32]image.Image, len(frames))
105 for id, f := range frames {109 for id, f := range frames {
106- if f.Width > maxBackdrop || f.Height > maxBackdrop {110+ if !CanBackdrop(ports.Output{Width: f.Width, Height: f.Height}) {
107- return s, fmt.Errorf("selection: frozen frame of output %d is %dx%d, at most %d per side", id, f.Width, f.Height, maxBackdrop)111+ return s, fmt.Errorf("selection: frozen frame of output %d is %dx%d, at most %d per side and %d bytes", id, f.Width, f.Height, maxBackdrop, maxBackdropBytes)
108 }112 }
109 s.backdrops[id] = backdropImage(f)113 s.backdrops[id] = backdropImage(f)
110 }114 }
M internal/adapters/wayland/extensions_test.go +2 -0
@@ -259,6 +259,8 @@ func TestRefusedExclusionDoesNotPinStoppedSession(t *testing.T) {
259259 target := ports.Target{OutputID: firstOutput(t, s).ID}
260260 _, err := s.BeginExclusion(context.Background(), target)
261261 require.ErrorIs(t, err, ErrExclusionUnavailable)
262+ // BeginExclusion sends destroy but does not wait for the server to read it.
263+ require.NoError(t, s.roundtrip())
262264 require.Equal(t, 1, srv.Stats().DestroyedExclusions, "the failed exclusion object is destroyed")
263265 srv.StopSessions()
264266 _, err = s.Capture(context.Background(), target)
@@ -259,6 +259,8 @@ func TestRefusedExclusionDoesNotPinStoppedSession(t *testing.T) {
259 target := ports.Target{OutputID: firstOutput(t, s).ID}259 target := ports.Target{OutputID: firstOutput(t, s).ID}
260 _, err := s.BeginExclusion(context.Background(), target)260 _, err := s.BeginExclusion(context.Background(), target)
261 require.ErrorIs(t, err, ErrExclusionUnavailable)261 require.ErrorIs(t, err, ErrExclusionUnavailable)
262+ // BeginExclusion sends destroy but does not wait for the server to read it.
263+ require.NoError(t, s.roundtrip())
262 require.Equal(t, 1, srv.Stats().DestroyedExclusions, "the failed exclusion object is destroyed")264 require.Equal(t, 1, srv.Stats().DestroyedExclusions, "the failed exclusion object is destroyed")
263 srv.StopSessions()265 srv.StopSessions()
264 _, err = s.Capture(context.Background(), target)266 _, err = s.Capture(context.Background(), target)
M internal/adapters/wayland/source_test.go +4 -1
@@ -171,8 +171,11 @@ func TestFailedUnknownRepeatsLastFrame(t *testing.T) {
171171 require.NoError(t, err, "recording continues")
172172 require.NotEqual(t, want, fresh.Pixels, "with a new frame")
173173
174+ // Capture prefetched the next frame. Wait for the server to answer it
175+ // before arming failures, so it cannot consume the first injected failure.
176+ require.NoError(t, s.roundtrip())
174177 srv.Fail(maxFrameAttempts, 0)
175- // The capture started after the previous frame was answered before Fail.
178+ // Consume the successful prefetched frame before counting failures.
176179 _, err = s.Capture(context.Background(), target)
177180 require.NoError(t, err)
178181 for range maxFrameAttempts - 1 {
@@ -171,8 +171,11 @@ func TestFailedUnknownRepeatsLastFrame(t *testing.T) {
171 require.NoError(t, err, "recording continues")171 require.NoError(t, err, "recording continues")
172 require.NotEqual(t, want, fresh.Pixels, "with a new frame")172 require.NotEqual(t, want, fresh.Pixels, "with a new frame")
173 173
174+ // Capture prefetched the next frame. Wait for the server to answer it
175+ // before arming failures, so it cannot consume the first injected failure.
176+ require.NoError(t, s.roundtrip())
174 srv.Fail(maxFrameAttempts, 0)177 srv.Fail(maxFrameAttempts, 0)
175- // The capture started after the previous frame was answered before Fail.178+ // Consume the successful prefetched frame before counting failures.
176 _, err = s.Capture(context.Background(), target)179 _, err = s.Capture(context.Background(), target)
177 require.NoError(t, err)180 require.NoError(t, err)
178 for range maxFrameAttempts - 1 {181 for range maxFrameAttempts - 1 {
M internal/app/freeze_test.go +2 -2
@@ -32,6 +32,6 @@ func TestFreezes(t *testing.T) {
3232 // Outputs too large for a backdrop are not frozen, so their screenshot is
3333 // live like their selector.
3434 func TestFrozenOutputs(t *testing.T) {
35- outputs := []ports.Output{{ID: 7, Width: 3840, Height: 2160}, {ID: 8, Width: 5120, Height: 2880}, {ID: 9, Width: 1080, Height: 1920}, {ID: 10}}
36- assert.Equal(t, []uint32{7, 9}, frozenOutputs(outputs))
35+ outputs := []ports.Output{{ID: 7, Width: 3840, Height: 2160}, {ID: 8, Width: 5120, Height: 2160}, {ID: 9, Width: 1080, Height: 1920}, {ID: 10}, {ID: 11, Width: 7680, Height: 4320}}
36+ assert.Equal(t, []uint32{7, 8, 9}, frozenOutputs(outputs))
3737 }
@@ -32,6 +32,6 @@ func TestFreezes(t *testing.T) {
32 // Outputs too large for a backdrop are not frozen, so their screenshot is32 // Outputs too large for a backdrop are not frozen, so their screenshot is
33 // live like their selector.33 // live like their selector.
34 func TestFrozenOutputs(t *testing.T) {34 func TestFrozenOutputs(t *testing.T) {
35- outputs := []ports.Output{{ID: 7, Width: 3840, Height: 2160}, {ID: 8, Width: 5120, Height: 2880}, {ID: 9, Width: 1080, Height: 1920}, {ID: 10}}35+ outputs := []ports.Output{{ID: 7, Width: 3840, Height: 2160}, {ID: 8, Width: 5120, Height: 2160}, {ID: 9, Width: 1080, Height: 1920}, {ID: 10}, {ID: 11, Width: 7680, Height: 4320}}
36- assert.Equal(t, []uint32{7, 9}, frozenOutputs(outputs))36+ assert.Equal(t, []uint32{7, 8, 9}, frozenOutputs(outputs))
37 }37 }

Comments3 open / 3

bnema outdated

Blocked on the NeferGUI v0.7.3 bump. NeferGUI v0.7.2 still rejects images wider than 4096 px, so merging without the bump would make the selector fail on 5120x2160 outputs. v0.7.3 is tagged on ermite; the module resolves from GitHub, which gets the mirror sync after 18:00. Then go.mod moves to v0.7.3.

bnema outdated

Now requires nefergui v0.7.3 (0012c9e). Local make check/race/mocks-check/staticcheck pass, and a headless 5120x2160 run confirms the freeze with F toggling. CI run 323 failed on TestRefusedExclusionDoesNotPinStoppedSession in internal/adapters/wayland. That package is unchanged here, and main shows the same flakiness: TestFailedUnknownRepeatsLastFrame fails 7 times out of 60 with go test -count=20 -cpu 1,2,4. Separate pre-existing issue.

bnema

Fixed the flaky wayland tests in 3d41bc5: added the missing Wayland roundtrips before the server-state assertions. CI run 327 passes.

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
l
merge request: the list of files (j k move, enter opens, esc back)
M
merge request: the merge menu; there 1–3 pick the mode, D deletes the branch, enter merges
R
merge request: mark the head read
esc
close a dialog, leave a field

Branches 2

Tags 9

↑↓ moveenter openesc close