detached 812b032

feat(wayland): add wl_subcompositor so foot maps

commit
812b032bd3f7fdf13aa8deb807bde395c7c7e993
author
brice · · 2026-09-23 12:20 UTC
parent
e9aeb28
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +37 -2
M internal/adapters/wayland/globals.go +35 -0
@@ -12,6 +12,11 @@ func registerGlobals(d *server.Display, o Options, s *Server) error {
1212 func() error {
1313 return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{s}) })
1414 },
15+ func() error {
16+ return wayland.NewSubcompositorGlobal(d, 1, func(c server.Client, v, id uint32) {
17+ wayland.NewSubcompositor(c, int32(v), id, subcompositor{s})
18+ })
19+ },
1520 func() error {
1621 return wayland.NewShmGlobal(d, 1, func(c server.Client, v, id uint32) {
1722 r, e := wayland.NewShm(c, int32(v), id, shm{})
@@ -77,6 +82,36 @@ func (compositor) CreateRegion(r *wayland.Compositor, id uint32) {
7782 }
7883 func (compositor) Release(*wayland.Compositor) {}
7984
85+type subcompositor struct{ server *Server }
86+
87+func (subcompositor) Destroy(*wayland.Subcompositor) {}
88+func (c subcompositor) GetSubsurface(r *wayland.Subcompositor, id uint32, w, parent *wayland.Surface) {
89+ if w == nil || parent == nil {
90+ return
91+ }
92+ state := c.server.surfaces[w.Resource]
93+ if state == nil {
94+ return
95+ }
96+ if state.role != nil {
97+ r.PostError(uint32(wayland.SubcompositorErrorBadSurface), "surface already has role")
98+ return
99+ }
100+ if sub, err := wayland.NewSubsurface(r.Client(), 1, id, subsurface{state}); err == nil {
101+ state.role = func(bool) {}
102+ sub.OnDestroy = func() { state.role = nil }
103+ }
104+}
105+
106+type subsurface struct{ surface *surface }
107+
108+func (subsurface) Destroy(*wayland.Subsurface) {}
109+func (subsurface) SetPosition(*wayland.Subsurface, int32, int32) {}
110+func (subsurface) PlaceAbove(*wayland.Subsurface, *wayland.Surface) {}
111+func (subsurface) PlaceBelow(*wayland.Subsurface, *wayland.Surface) {}
112+func (subsurface) SetSync(*wayland.Subsurface) {}
113+func (subsurface) SetDesync(*wayland.Subsurface) {}
114+
80115 type region struct{}
81116
82117 func (region) Destroy(*wayland.Region) {}
@@ -12,6 +12,11 @@ func registerGlobals(d *server.Display, o Options, s *Server) error {
12 func() error {12 func() error {
13 return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{s}) })13 return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{s}) })
14 },14 },
15+ func() error {
16+ return wayland.NewSubcompositorGlobal(d, 1, func(c server.Client, v, id uint32) {
17+ wayland.NewSubcompositor(c, int32(v), id, subcompositor{s})
18+ })
19+ },
15 func() error {20 func() error {
16 return wayland.NewShmGlobal(d, 1, func(c server.Client, v, id uint32) {21 return wayland.NewShmGlobal(d, 1, func(c server.Client, v, id uint32) {
17 r, e := wayland.NewShm(c, int32(v), id, shm{})22 r, e := wayland.NewShm(c, int32(v), id, shm{})
@@ -77,6 +82,36 @@ func (compositor) CreateRegion(r *wayland.Compositor, id uint32) {
77 }82 }
78 func (compositor) Release(*wayland.Compositor) {}83 func (compositor) Release(*wayland.Compositor) {}
79 84
85+type subcompositor struct{ server *Server }
86+
87+func (subcompositor) Destroy(*wayland.Subcompositor) {}
88+func (c subcompositor) GetSubsurface(r *wayland.Subcompositor, id uint32, w, parent *wayland.Surface) {
89+ if w == nil || parent == nil {
90+ return
91+ }
92+ state := c.server.surfaces[w.Resource]
93+ if state == nil {
94+ return
95+ }
96+ if state.role != nil {
97+ r.PostError(uint32(wayland.SubcompositorErrorBadSurface), "surface already has role")
98+ return
99+ }
100+ if sub, err := wayland.NewSubsurface(r.Client(), 1, id, subsurface{state}); err == nil {
101+ state.role = func(bool) {}
102+ sub.OnDestroy = func() { state.role = nil }
103+ }
104+}
105+
106+type subsurface struct{ surface *surface }
107+
108+func (subsurface) Destroy(*wayland.Subsurface) {}
109+func (subsurface) SetPosition(*wayland.Subsurface, int32, int32) {}
110+func (subsurface) PlaceAbove(*wayland.Subsurface, *wayland.Surface) {}
111+func (subsurface) PlaceBelow(*wayland.Subsurface, *wayland.Surface) {}
112+func (subsurface) SetSync(*wayland.Subsurface) {}
113+func (subsurface) SetDesync(*wayland.Subsurface) {}
114+
80 type region struct{}115 type region struct{}
81 116
82 func (region) Destroy(*wayland.Region) {}117 func (region) Destroy(*wayland.Region) {}
M internal/adapters/wayland/server_test.go +2 -2
@@ -284,8 +284,8 @@ func TestFootWindow(t *testing.T) {
284284 t.Fatalf("app ID: %q", w.AppID)
285285 }
286286 case err := <-done:
287- t.Skipf("foot unavailable: %v: %s", err, output.String())
287+ t.Fatalf("foot exited: %v: %s", err, output.String())
288288 case <-time.After(10 * time.Second):
289- t.Skipf("foot did not map: %s", output.String())
289+ t.Fatalf("foot did not map: %s", output.String())
290290 }
291291 }
@@ -284,8 +284,8 @@ func TestFootWindow(t *testing.T) {
284 t.Fatalf("app ID: %q", w.AppID)284 t.Fatalf("app ID: %q", w.AppID)
285 }285 }
286 case err := <-done:286 case err := <-done:
287- t.Skipf("foot unavailable: %v: %s", err, output.String())287+ t.Fatalf("foot exited: %v: %s", err, output.String())
288 case <-time.After(10 * time.Second):288 case <-time.After(10 * time.Second):
289- t.Skipf("foot did not map: %s", output.String())289+ t.Fatalf("foot did not map: %s", output.String())
290 }290 }
291 }291 }

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 23

Tags 10

↑↓ moveenter openesc close