detached 56b8a1f

fix(wayland): document workspace frame clipping and HUD placement, check gofmt in make check

commit
56b8a1f118e6cb57a528a78b9b73c454cba72b92
author
brice · · 2026-10-01 02:55 UTC
committer
brice · · 2026-10-01 03:03 UTC
parent
a558f49
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
7 files · +21 -8
M Makefile +5 -2
@@ -1,4 +1,4 @@
1-.PHONY: bin build test vet race mod-check mocks mocks-check fakes-check arch perf-check check
1+.PHONY: bin build test vet race fmt-check mod-check mocks mocks-check fakes-check arch perf-check check
22
33 GOBIN_DIR := $(or $(shell go env GOBIN),$(firstword $(subst :, ,$(shell go env GOPATH)))/bin)
44 MOCKERY ?= $(GOBIN_DIR)/mockery
@@ -16,6 +16,9 @@ vet:
1616 race:
1717 # cgo is enabled only for the race test binary.
1818 NEFERCAP_REQUIRE_WAYLAND=1 CGO_ENABLED=1 go test -race ./...
19+# Go sources must be gofmt-clean (lists the offending files and fails otherwise).
20+fmt-check:
21+ test -z "$$(gofmt -l ./internal ./cmd)" || { gofmt -l ./internal ./cmd >&2; exit 1; }
1922 # go.mod and go.sum must be tidy (prints the diff and fails otherwise).
2023 mod-check:
2124 go mod tidy -diff
@@ -56,4 +59,4 @@ perf-check:
5659 printf '%s\n' "$$out" | grep -qx "$$t" || { echo "perf-check: test $$t not found in $(PERF_PKGS)" >&2; exit 1; }; \
5760 done
5861 NEFERCAP_REQUIRE_WAYLAND=1 CGO_ENABLED=0 go test $(PERF_PKGS) -run '$(PERF_RE)' -count=1
59-check: mod-check vet test arch fakes-check perf-check
62+check: fmt-check mod-check vet test arch fakes-check perf-check
@@ -1,4 +1,4 @@
1-.PHONY: bin build test vet race mod-check mocks mocks-check fakes-check arch perf-check check1+.PHONY: bin build test vet race fmt-check mod-check mocks mocks-check fakes-check arch perf-check check
2 2
3 GOBIN_DIR := $(or $(shell go env GOBIN),$(firstword $(subst :, ,$(shell go env GOPATH)))/bin)3 GOBIN_DIR := $(or $(shell go env GOBIN),$(firstword $(subst :, ,$(shell go env GOPATH)))/bin)
4 MOCKERY ?= $(GOBIN_DIR)/mockery4 MOCKERY ?= $(GOBIN_DIR)/mockery
@@ -16,6 +16,9 @@ vet:
16 race:16 race:
17 # cgo is enabled only for the race test binary.17 # cgo is enabled only for the race test binary.
18 NEFERCAP_REQUIRE_WAYLAND=1 CGO_ENABLED=1 go test -race ./...18 NEFERCAP_REQUIRE_WAYLAND=1 CGO_ENABLED=1 go test -race ./...
19+# Go sources must be gofmt-clean (lists the offending files and fails otherwise).
20+fmt-check:
21+ test -z "$$(gofmt -l ./internal ./cmd)" || { gofmt -l ./internal ./cmd >&2; exit 1; }
19 # go.mod and go.sum must be tidy (prints the diff and fails otherwise).22 # go.mod and go.sum must be tidy (prints the diff and fails otherwise).
20 mod-check:23 mod-check:
21 go mod tidy -diff24 go mod tidy -diff
@@ -56,4 +59,4 @@ perf-check:
56 printf '%s\n' "$$out" | grep -qx "$$t" || { echo "perf-check: test $$t not found in $(PERF_PKGS)" >&2; exit 1; }; \59 printf '%s\n' "$$out" | grep -qx "$$t" || { echo "perf-check: test $$t not found in $(PERF_PKGS)" >&2; exit 1; }; \
57 done60 done
58 NEFERCAP_REQUIRE_WAYLAND=1 CGO_ENABLED=0 go test $(PERF_PKGS) -run '$(PERF_RE)' -count=161 NEFERCAP_REQUIRE_WAYLAND=1 CGO_ENABLED=0 go test $(PERF_PKGS) -run '$(PERF_RE)' -count=1
59-check: mod-check vet test arch fakes-check perf-check62+check: fmt-check mod-check vet test arch fakes-check perf-check
M README.md +1 -1
@@ -108,7 +108,7 @@ NeferWL's optional capture extension adds workspace and region sources and keeps
108108 | KDE Plasma | assumed | no | no | no |
109109 | GNOME | unsupported | no | no | no |
110110
111-Workspaces need `ext-workspace-v1`; with NeferWL's extension the selector outlines the workspace frame (a workspace smaller than its monitor), and the HUD sits in it. Without the extension `W` outlines the whole monitor and captures the workspace currently shown, as its monitor. Without the extension a region is cropped from the monitor frame. *Assumed* means it follows from the protocols the compositor advertises; only NeferWL is tested.
111+Workspaces need `ext-workspace-v1`; with NeferWL's extension the selector outlines the workspace frame (a workspace smaller than its monitor), and the HUD sits in it (placed when the recording starts: if the workspace frame changes during the recording, the video follows it and the HUD stays in place). Without the extension `W` outlines the whole monitor and captures the workspace currently shown, as its monitor. Without the extension a region is cropped from the monitor frame. *Assumed* means it follows from the protocols the compositor advertises; only NeferWL is tested.
112112
113113 A compositor may refuse a capture client; the capture then ends with an error.
114114
@@ -108,7 +108,7 @@ NeferWL's optional capture extension adds workspace and region sources and keeps
108 | KDE Plasma | assumed | no | no | no |108 | KDE Plasma | assumed | no | no | no |
109 | GNOME | unsupported | no | no | no |109 | GNOME | unsupported | no | no | no |
110 110
111-Workspaces need `ext-workspace-v1`; with NeferWL's extension the selector outlines the workspace frame (a workspace smaller than its monitor), and the HUD sits in it. Without the extension `W` outlines the whole monitor and captures the workspace currently shown, as its monitor. Without the extension a region is cropped from the monitor frame. *Assumed* means it follows from the protocols the compositor advertises; only NeferWL is tested.111+Workspaces need `ext-workspace-v1`; with NeferWL's extension the selector outlines the workspace frame (a workspace smaller than its monitor), and the HUD sits in it (placed when the recording starts: if the workspace frame changes during the recording, the video follows it and the HUD stays in place). Without the extension `W` outlines the whole monitor and captures the workspace currently shown, as its monitor. Without the extension a region is cropped from the monitor frame. *Assumed* means it follows from the protocols the compositor advertises; only NeferWL is tested.
112 112
113 A compositor may refuse a capture client; the capture then ends with an error.113 A compositor may refuse a capture client; the capture then ends with an error.
114 114
M internal/adapters/indicator/indicator.go +3 -1
@@ -62,7 +62,9 @@ type Indicator struct {
6262 // Frame optionally is the part of the output that is recorded, in
6363 // output-local logical pixels (a workspace smaller than its output): the
6464 // HUD sits at the top centre of it. The zero value is the whole output.
65- // Set it before Run.
65+ // The position is fixed when the recording starts: if the workspace frame
66+ // changes during the recording, the video follows it and the HUD stays in
67+ // place. Set it before Run.
6668 Frame ports.Region
6769
6870 authorize func(context.Context, nefergui.WaylandSurface) error
@@ -62,7 +62,9 @@ type Indicator struct {
62 // Frame optionally is the part of the output that is recorded, in62 // Frame optionally is the part of the output that is recorded, in
63 // output-local logical pixels (a workspace smaller than its output): the63 // output-local logical pixels (a workspace smaller than its output): the
64 // HUD sits at the top centre of it. The zero value is the whole output.64 // HUD sits at the top centre of it. The zero value is the whole output.
65- // Set it before Run.65+ // The position is fixed when the recording starts: if the workspace frame
66+ // changes during the recording, the video follows it and the HUD stays in
67+ // place. Set it before Run.
66 Frame ports.Region68 Frame ports.Region
67 69
68 authorize func(context.Context, nefergui.WaylandSurface) error70 authorize func(context.Context, nefergui.WaylandSurface) error
M internal/adapters/wayland/extensions.go +1 -1
@@ -42,7 +42,7 @@ const (
4242 reqNeferwlWorkspaceSource = 1
4343 reqNeferwlRegionSource = 2
4444 reqGetWorkspaceFrame = 3
45- reqWorkspaceFrameDestroy = 0
45+ reqWorkspaceFrameDestroy = 0
4646 reqGetExclusion = 1
4747 reqAttachSurface = 2
4848 reqManagerDestroy = 0
@@ -42,7 +42,7 @@ const (
42 reqNeferwlWorkspaceSource = 142 reqNeferwlWorkspaceSource = 1
43 reqNeferwlRegionSource = 243 reqNeferwlRegionSource = 2
44 reqGetWorkspaceFrame = 344 reqGetWorkspaceFrame = 3
45- reqWorkspaceFrameDestroy = 045+ reqWorkspaceFrameDestroy = 0
46 reqGetExclusion = 146 reqGetExclusion = 1
47 reqAttachSurface = 247 reqAttachSurface = 2
48 reqManagerDestroy = 048 reqManagerDestroy = 0
M internal/adapters/wayland/testserver/neferwl/neferwl-image-capture-v1.xml +4 -2
@@ -67,7 +67,8 @@
6767 <description summary="learn the frame of a workspace">
6868 ext-workspace-v1 carries no geometry. The frame object reports the
6969 rectangle that a workspace source of the same handle captures. For a
70- handle already removed, the object never sends frame.
70+ handle already removed, the object never sends frame. It needs no
71+ capture authorization: the frame is layout information, not pixels.
7172 </description>
7273 <arg name="id" type="new_id" interface="neferwl_workspace_frame_v1"/>
7374 <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/>
@@ -89,7 +90,8 @@
8990 <description summary="the workspace frame">
9091 The frame in output-local logical pixels. Sent when the object is
9192 created and again whenever the frame changes. Not sent once the
92- workspace is removed.
93+ workspace is removed. Not sent while the session is locked; the
94+ current frame is sent after unlock if it changed.
9395 </description>
9496 <arg name="x" type="int"/>
9597 <arg name="y" type="int"/>
@@ -67,7 +67,8 @@
67 <description summary="learn the frame of a workspace">67 <description summary="learn the frame of a workspace">
68 ext-workspace-v1 carries no geometry. The frame object reports the68 ext-workspace-v1 carries no geometry. The frame object reports the
69 rectangle that a workspace source of the same handle captures. For a69 rectangle that a workspace source of the same handle captures. For a
70- handle already removed, the object never sends frame.70+ handle already removed, the object never sends frame. It needs no
71+ capture authorization: the frame is layout information, not pixels.
71 </description>72 </description>
72 <arg name="id" type="new_id" interface="neferwl_workspace_frame_v1"/>73 <arg name="id" type="new_id" interface="neferwl_workspace_frame_v1"/>
73 <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/>74 <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/>
@@ -89,7 +90,8 @@
89 <description summary="the workspace frame">90 <description summary="the workspace frame">
90 The frame in output-local logical pixels. Sent when the object is91 The frame in output-local logical pixels. Sent when the object is
91 created and again whenever the frame changes. Not sent once the92 created and again whenever the frame changes. Not sent once the
92- workspace is removed.93+ workspace is removed. Not sent while the session is locked; the
94+ current frame is sent after unlock if it changed.
93 </description>95 </description>
94 <arg name="x" type="int"/>96 <arg name="x" type="int"/>
95 <arg name="y" type="int"/>97 <arg name="y" type="int"/>
M internal/adapters/wayland/workspaces.go +4 -1
@@ -274,7 +274,10 @@ func (s *Source) workspaceList() []ports.Workspace {
274274
275275 // workspaceRegion is where the workspace is on o, in output-local logical
276276 // pixels: its frame from NeferWL's extension, clipped to the output, else the
277-// whole output (ext-workspace carries no geometry).
277+// whole output (ext-workspace carries no geometry). NeferWL computes the frame
278+// clamped to the output and centred, so it never lies outside it; the clip and
279+// the whole-output fallback are safeguards only (core's Picker.clip is the
280+// single clip applied to the selection).
278281 func workspaceRegion(w *workspace, o *output) ports.Region {
279282 lw, lh := o.logicalSize()
280283 whole := ports.Region{Width: lw, Height: lh}
@@ -274,7 +274,10 @@ func (s *Source) workspaceList() []ports.Workspace {
274 274
275 // workspaceRegion is where the workspace is on o, in output-local logical275 // workspaceRegion is where the workspace is on o, in output-local logical
276 // pixels: its frame from NeferWL's extension, clipped to the output, else the276 // pixels: its frame from NeferWL's extension, clipped to the output, else the
277-// whole output (ext-workspace carries no geometry).277+// whole output (ext-workspace carries no geometry). NeferWL computes the frame
278+// clamped to the output and centred, so it never lies outside it; the clip and
279+// the whole-output fallback are safeguards only (core's Picker.clip is the
280+// single clip applied to the selection).
278 func workspaceRegion(w *workspace, o *output) ports.Region {281 func workspaceRegion(w *workspace, o *output) ports.Region {
279 lw, lh := o.logicalSize()282 lw, lh := o.logicalSize()
280 whole := ports.Region{Width: lw, Height: lh}283 whole := ports.Region{Width: lw, Height: lh}
M internal/app/record_interactive.go +3 -0
@@ -184,6 +184,9 @@ func recordedFrame(ctx context.Context, source *wayland.Source, t ports.Target)
184184 }
185185 list, err := source.Workspaces(ctx)
186186 if err != nil {
187+ if ctx.Err() != nil {
188+ return ports.Region{}
189+ }
187190 log := logging.For(ctx, "app")
188191 log.Warn().Err(err).Msg("workspace frame unavailable: HUD on the whole output")
189192 return ports.Region{}
@@ -184,6 +184,9 @@ func recordedFrame(ctx context.Context, source *wayland.Source, t ports.Target)
184 }184 }
185 list, err := source.Workspaces(ctx)185 list, err := source.Workspaces(ctx)
186 if err != nil {186 if err != nil {
187+ if ctx.Err() != nil {
188+ return ports.Region{}
189+ }
187 log := logging.For(ctx, "app")190 log := logging.For(ctx, "app")
188 log.Warn().Err(err).Msg("workspace frame unavailable: HUD on the whole output")191 log.Warn().Err(err).Msg("workspace frame unavailable: HUD on the whole output")
189 return ports.Region{}192 return ports.Region{}

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 2

Tags 9

↑↓ moveenter openesc close