fix(wayland): enforce layer role rules and xdg-output done semantics
- commit
- c2ef3a907c7534c039966ead0f3879a975e972a7
- author
- brice · · 2026-09-23 16:19 UTC
- parent
- aba8c9f
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +104 -13
M internal/adapters/wayland/layer.go +12 -10
| @@ -31,11 +31,7 @@ type layerSurface struct { | |||
| 31 | 31 | serials []uint32 | |
| 32 | 32 | } | |
| 33 | 33 | ||
| 34 | - | // Layer objects belong to the display goroutine, like xdg windows. | |
| 35 | - | var layerSets = map[*Server]map[ports.WindowID]*layerSurface{} | |
| 36 | - | ||
| 37 | 34 | func registerLayer(d *server.Display, o Options, s *Server) error { | |
| 38 | - | layerSets[s] = make(map[ports.WindowID]*layerSurface) | |
| 39 | 35 | return wlrlayershell.NewZwlrLayerShellV1Global(d, 4, func(c server.Client, v, id uint32) { | |
| 40 | 36 | _, _ = wlrlayershell.NewZwlrLayerShellV1(c, int32(v), id, layerShell{s, uint32(o.OutputWidth), uint32(o.OutputHeight)}) | |
| 41 | 37 | }) | |
| @@ -46,8 +42,8 @@ func (h layerShell) GetLayerSurface(r *wlrlayershell.ZwlrLayerShellV1, id uint32 | |||
| 46 | 42 | return | |
| 47 | 43 | } | |
| 48 | 44 | state := h.server.surfaces[w.Resource] | |
| 49 | - | if state.kind != roleNone { | |
| 50 | - | r.PostError(uint32(wlrlayershell.ZwlrLayerShellV1ErrorRole), "surface already has role") | |
| 45 | + | if state.kind != roleNone || state.attached || state.current != nil || state.committed { | |
| 46 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerShellV1ErrorAlreadyConstructed), "surface already constructed") | |
| 51 | 47 | return | |
| 52 | 48 | } | |
| 53 | 49 | if layer > 3 { | |
| @@ -65,10 +61,10 @@ func (h layerShell) GetLayerSurface(r *wlrlayershell.ZwlrLayerShellV1, id uint32 | |||
| 65 | 61 | state.kind = roleLayer | |
| 66 | 62 | state.layer = l | |
| 67 | 63 | state.role = l.commit | |
| 68 | - | layerSets[h.server][l.id] = l | |
| 64 | + | h.server.layers[l.id] = l | |
| 69 | 65 | resource.OnDestroy = func() { | |
| 70 | 66 | l.unmap() | |
| 71 | - | delete(layerSets[h.server], l.id) | |
| 67 | + | delete(h.server.layers, l.id) | |
| 72 | 68 | state.layer = nil | |
| 73 | 69 | state.role = nil | |
| 74 | 70 | state.current, state.pending = nil, nil | |
| @@ -91,6 +87,8 @@ func (l *layerSurface) SetExclusiveZone(_ *wlrlayershell.ZwlrLayerSurfaceV1, z i | |||
| 91 | 87 | func (l *layerSurface) SetMargin(_ *wlrlayershell.ZwlrLayerSurfaceV1, t, r, b, left int32) { | |
| 92 | 88 | l.pending.margin = [4]int32{t, r, b, left} | |
| 93 | 89 | } | |
| 90 | + | ||
| 91 | + | // Keyboard interactivity is deferred; layer-shell is capped at v4. | |
| 94 | 92 | func (l *layerSurface) SetKeyboardInteractivity(r *wlrlayershell.ZwlrLayerSurfaceV1, k uint32) { | |
| 95 | 93 | if k > 2 { | |
| 96 | 94 | r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity), "invalid keyboard interactivity") | |
| @@ -98,6 +96,8 @@ func (l *layerSurface) SetKeyboardInteractivity(r *wlrlayershell.ZwlrLayerSurfac | |||
| 98 | 96 | } | |
| 99 | 97 | l.pending.keyboard = k | |
| 100 | 98 | } | |
| 99 | + | ||
| 100 | + | // Layer popups are deferred; layer-shell is capped at v4. | |
| 101 | 101 | func (*layerSurface) GetPopup(*wlrlayershell.ZwlrLayerSurfaceV1, *xdgshell.Popup) {} | |
| 102 | 102 | func (l *layerSurface) AckConfigure(r *wlrlayershell.ZwlrLayerSurfaceV1, serial uint32) { | |
| 103 | 103 | for i, v := range l.serials { | |
| @@ -117,6 +117,8 @@ func (l *layerSurface) SetLayer(r *wlrlayershell.ZwlrLayerSurfaceV1, v uint32) { | |||
| 117 | 117 | } | |
| 118 | 118 | l.pending.layer = ports.Layer(v) | |
| 119 | 119 | } | |
| 120 | + | ||
| 121 | + | // Exclusive edge is not implemented; layer-shell is capped at v4. | |
| 120 | 122 | func (*layerSurface) SetExclusiveEdge(*wlrlayershell.ZwlrLayerSurfaceV1, uint32) {} | |
| 121 | 123 | func (l *layerSurface) unmap() { | |
| 122 | 124 | if !l.mapped { | |
| @@ -131,7 +133,7 @@ func (l *layerSurface) unmap() { | |||
| 131 | 133 | } | |
| 132 | 134 | func (s *Server) layerChanged() { | |
| 133 | 135 | list := make([]ports.LayerSurface, 0) | |
| 134 | - | for _, l := range layerSets[s] { | |
| 136 | + | for _, l := range s.layers { | |
| 135 | 137 | if !l.mapped { | |
| 136 | 138 | continue | |
| 137 | 139 | } | |
| @@ -149,7 +151,7 @@ func (s *Server) layerChanged() { | |||
| 149 | 151 | func (l *layerSurface) commit(buffer bool) { | |
| 150 | 152 | if l.surface.destroyed { | |
| 151 | 153 | l.unmap() | |
| 152 | - | delete(layerSets[l.shell.server], l.id) | |
| 154 | + | delete(l.shell.server.layers, l.id) | |
| 153 | 155 | return | |
| 154 | 156 | } | |
| 155 | 157 | p := l.pending | |
| @@ -31,11 +31,7 @@ type layerSurface struct { | |||||
| 31 | serials []uint32 | 31 | serials []uint32 | ||
| 32 | } | 32 | } | ||
| 33 | 33 | ||||
| 34 | - | // Layer objects belong to the display goroutine, like xdg windows. | |||
| 35 | - | var layerSets = map[*Server]map[ports.WindowID]*layerSurface{} | |||
| 36 | - | ||||
| 37 | func registerLayer(d *server.Display, o Options, s *Server) error { | 34 | func registerLayer(d *server.Display, o Options, s *Server) error { | ||
| 38 | - | layerSets[s] = make(map[ports.WindowID]*layerSurface) | |||
| 39 | return wlrlayershell.NewZwlrLayerShellV1Global(d, 4, func(c server.Client, v, id uint32) { | 35 | return wlrlayershell.NewZwlrLayerShellV1Global(d, 4, func(c server.Client, v, id uint32) { | ||
| 40 | _, _ = wlrlayershell.NewZwlrLayerShellV1(c, int32(v), id, layerShell{s, uint32(o.OutputWidth), uint32(o.OutputHeight)}) | 36 | _, _ = wlrlayershell.NewZwlrLayerShellV1(c, int32(v), id, layerShell{s, uint32(o.OutputWidth), uint32(o.OutputHeight)}) | ||
| 41 | }) | 37 | }) | ||
| @@ -46,8 +42,8 @@ func (h layerShell) GetLayerSurface(r *wlrlayershell.ZwlrLayerShellV1, id uint32 | |||||
| 46 | return | 42 | return | ||
| 47 | } | 43 | } | ||
| 48 | state := h.server.surfaces[w.Resource] | 44 | state := h.server.surfaces[w.Resource] | ||
| 49 | - | if state.kind != roleNone { | 45 | + | if state.kind != roleNone || state.attached || state.current != nil || state.committed { |
| 50 | - | r.PostError(uint32(wlrlayershell.ZwlrLayerShellV1ErrorRole), "surface already has role") | 46 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerShellV1ErrorAlreadyConstructed), "surface already constructed") |
| 51 | return | 47 | return | ||
| 52 | } | 48 | } | ||
| 53 | if layer > 3 { | 49 | if layer > 3 { | ||
| @@ -65,10 +61,10 @@ func (h layerShell) GetLayerSurface(r *wlrlayershell.ZwlrLayerShellV1, id uint32 | |||||
| 65 | state.kind = roleLayer | 61 | state.kind = roleLayer | ||
| 66 | state.layer = l | 62 | state.layer = l | ||
| 67 | state.role = l.commit | 63 | state.role = l.commit | ||
| 68 | - | layerSets[h.server][l.id] = l | 64 | + | h.server.layers[l.id] = l |
| 69 | resource.OnDestroy = func() { | 65 | resource.OnDestroy = func() { | ||
| 70 | l.unmap() | 66 | l.unmap() | ||
| 71 | - | delete(layerSets[h.server], l.id) | 67 | + | delete(h.server.layers, l.id) |
| 72 | state.layer = nil | 68 | state.layer = nil | ||
| 73 | state.role = nil | 69 | state.role = nil | ||
| 74 | state.current, state.pending = nil, nil | 70 | state.current, state.pending = nil, nil | ||
| @@ -91,6 +87,8 @@ func (l *layerSurface) SetExclusiveZone(_ *wlrlayershell.ZwlrLayerSurfaceV1, z i | |||||
| 91 | func (l *layerSurface) SetMargin(_ *wlrlayershell.ZwlrLayerSurfaceV1, t, r, b, left int32) { | 87 | func (l *layerSurface) SetMargin(_ *wlrlayershell.ZwlrLayerSurfaceV1, t, r, b, left int32) { | ||
| 92 | l.pending.margin = [4]int32{t, r, b, left} | 88 | l.pending.margin = [4]int32{t, r, b, left} | ||
| 93 | } | 89 | } | ||
| 90 | + | ||||
| 91 | + | // Keyboard interactivity is deferred; layer-shell is capped at v4. | |||
| 94 | func (l *layerSurface) SetKeyboardInteractivity(r *wlrlayershell.ZwlrLayerSurfaceV1, k uint32) { | 92 | func (l *layerSurface) SetKeyboardInteractivity(r *wlrlayershell.ZwlrLayerSurfaceV1, k uint32) { | ||
| 95 | if k > 2 { | 93 | if k > 2 { | ||
| 96 | r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity), "invalid keyboard interactivity") | 94 | r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity), "invalid keyboard interactivity") | ||
| @@ -98,6 +96,8 @@ func (l *layerSurface) SetKeyboardInteractivity(r *wlrlayershell.ZwlrLayerSurfac | |||||
| 98 | } | 96 | } | ||
| 99 | l.pending.keyboard = k | 97 | l.pending.keyboard = k | ||
| 100 | } | 98 | } | ||
| 99 | + | ||||
| 100 | + | // Layer popups are deferred; layer-shell is capped at v4. | |||
| 101 | func (*layerSurface) GetPopup(*wlrlayershell.ZwlrLayerSurfaceV1, *xdgshell.Popup) {} | 101 | func (*layerSurface) GetPopup(*wlrlayershell.ZwlrLayerSurfaceV1, *xdgshell.Popup) {} | ||
| 102 | func (l *layerSurface) AckConfigure(r *wlrlayershell.ZwlrLayerSurfaceV1, serial uint32) { | 102 | func (l *layerSurface) AckConfigure(r *wlrlayershell.ZwlrLayerSurfaceV1, serial uint32) { | ||
| 103 | for i, v := range l.serials { | 103 | for i, v := range l.serials { | ||
| @@ -117,6 +117,8 @@ func (l *layerSurface) SetLayer(r *wlrlayershell.ZwlrLayerSurfaceV1, v uint32) { | |||||
| 117 | } | 117 | } | ||
| 118 | l.pending.layer = ports.Layer(v) | 118 | l.pending.layer = ports.Layer(v) | ||
| 119 | } | 119 | } | ||
| 120 | + | ||||
| 121 | + | // Exclusive edge is not implemented; layer-shell is capped at v4. | |||
| 120 | func (*layerSurface) SetExclusiveEdge(*wlrlayershell.ZwlrLayerSurfaceV1, uint32) {} | 122 | func (*layerSurface) SetExclusiveEdge(*wlrlayershell.ZwlrLayerSurfaceV1, uint32) {} | ||
| 121 | func (l *layerSurface) unmap() { | 123 | func (l *layerSurface) unmap() { | ||
| 122 | if !l.mapped { | 124 | if !l.mapped { | ||
| @@ -131,7 +133,7 @@ func (l *layerSurface) unmap() { | |||||
| 131 | } | 133 | } | ||
| 132 | func (s *Server) layerChanged() { | 134 | func (s *Server) layerChanged() { | ||
| 133 | list := make([]ports.LayerSurface, 0) | 135 | list := make([]ports.LayerSurface, 0) | ||
| 134 | - | for _, l := range layerSets[s] { | 136 | + | for _, l := range s.layers { |
| 135 | if !l.mapped { | 137 | if !l.mapped { | ||
| 136 | continue | 138 | continue | ||
| 137 | } | 139 | } | ||
| @@ -149,7 +151,7 @@ func (s *Server) layerChanged() { | |||||
| 149 | func (l *layerSurface) commit(buffer bool) { | 151 | func (l *layerSurface) commit(buffer bool) { | ||
| 150 | if l.surface.destroyed { | 152 | if l.surface.destroyed { | ||
| 151 | l.unmap() | 153 | l.unmap() | ||
| 152 | - | delete(layerSets[l.shell.server], l.id) | 154 | + | delete(l.shell.server.layers, l.id) |
| 153 | return | 155 | return | ||
| 154 | } | 156 | } | ||
| 155 | p := l.pending | 157 | p := l.pending | ||
M internal/adapters/wayland/layer_test.go +86 -0
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package wayland | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "fmt" | |
| 4 | 5 | "github.com/bnema/nefertty/internal/ports" | |
| 5 | 6 | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 6 | 7 | "github.com/bnema/purego-libwayland/protocol/wlrlayershell" | |
| @@ -140,3 +141,88 @@ func TestWaybarLayer(t *testing.T) { | |||
| 140 | 141 | t.Fatalf("waybar timeout stderr: %s", output.String()) | |
| 141 | 142 | } | |
| 142 | 143 | } | |
| 144 | + | ||
| 145 | + | func TestLayerAlreadyConstructed(t *testing.T) { | |
| 146 | + | for _, scenario := range []string{"role", "attached", "committed"} { | |
| 147 | + | t.Run(scenario, func(t *testing.T) { | |
| 148 | + | s, _, _, dir := lifecycleServer(t) | |
| 149 | + | c := protocolClient(t, s, dir) | |
| 150 | + | comp := bindProtocol(t, c, "wl_compositor") | |
| 151 | + | shell := bindProtocol(t, c, "zwlr_layer_shell_v1") | |
| 152 | + | surf := c.AllocateID() | |
| 153 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf) | |
| 154 | + | registerProtocol(t, c, surf) | |
| 155 | + | switch scenario { | |
| 156 | + | case "role": | |
| 157 | + | sub := bindProtocol(t, c, "wl_subcompositor") | |
| 158 | + | parent := c.AllocateID() | |
| 159 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, parent) | |
| 160 | + | registerProtocol(t, c, parent) | |
| 161 | + | id := c.AllocateID() | |
| 162 | + | requestProtocol(t, c, sub, wayland.SubcompositorRequestGetSubsurface, id, surf, parent) | |
| 163 | + | case "attached": | |
| 164 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, uint32(0), int32(0), int32(0)) | |
| 165 | + | case "committed": | |
| 166 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |
| 167 | + | } | |
| 168 | + | id := c.AllocateID() | |
| 169 | + | requestProtocol(t, c, shell, wlrlayershell.ZwlrLayerShellV1RequestGetLayerSurface, id, surf, uint32(0), uint32(ports.LayerTop), "test") | |
| 170 | + | expectProtocolError(t, c, shell, uint32(wlrlayershell.ZwlrLayerShellV1ErrorAlreadyConstructed)) | |
| 171 | + | }) | |
| 172 | + | } | |
| 173 | + | } | |
| 174 | + | ||
| 175 | + | type doneProxy struct { | |
| 176 | + | wlturbo.BaseProxy | |
| 177 | + | done int | |
| 178 | + | opcode uint16 | |
| 179 | + | } | |
| 180 | + | ||
| 181 | + | func (p *doneProxy) Dispatch(e *wlturbo.Event) { | |
| 182 | + | if e.Opcode == p.opcode { | |
| 183 | + | p.done++ | |
| 184 | + | } | |
| 185 | + | } | |
| 186 | + | func TestXDGOutputDone(t *testing.T) { | |
| 187 | + | for _, version := range []uint32{1, 2} { | |
| 188 | + | t.Run(fmt.Sprintf("wl_output_v%d", version), func(t *testing.T) { | |
| 189 | + | s, _, _, dir := lifecycleServer(t) | |
| 190 | + | c := protocolClient(t, s, dir) | |
| 191 | + | global, ok := c.Registry().FindGlobal("wl_output") | |
| 192 | + | if !ok { | |
| 193 | + | t.Fatal("missing wl_output") | |
| 194 | + | } | |
| 195 | + | output, err := c.Registry().BindID(global.Name, global.Interface, version) | |
| 196 | + | if err != nil { | |
| 197 | + | t.Fatal(err) | |
| 198 | + | } | |
| 199 | + | op := &doneProxy{opcode: uint16(wayland.OutputEventDone)} | |
| 200 | + | op.SetID(output) | |
| 201 | + | c.Context().Register(op) | |
| 202 | + | global, ok = c.Registry().FindGlobal("zxdg_output_manager_v1") | |
| 203 | + | if !ok { | |
| 204 | + | t.Fatal("missing xdg output manager") | |
| 205 | + | } | |
| 206 | + | manager, err := c.Registry().BindID(global.Name, global.Interface, 3) | |
| 207 | + | if err != nil { | |
| 208 | + | t.Fatal(err) | |
| 209 | + | } | |
| 210 | + | registerProtocol(t, c, manager) | |
| 211 | + | if err := c.Roundtrip(); err != nil { | |
| 212 | + | t.Fatal(err) | |
| 213 | + | } | |
| 214 | + | op.done = 0 | |
| 215 | + | xdg := c.AllocateID() | |
| 216 | + | xp := &doneProxy{opcode: 2} | |
| 217 | + | xp.SetID(xdg) | |
| 218 | + | c.Context().Register(xp) | |
| 219 | + | requestProtocol(t, c, manager, 1, xdg, output) | |
| 220 | + | if err := c.Roundtrip(); err != nil { | |
| 221 | + | t.Fatal(err) | |
| 222 | + | } | |
| 223 | + | if version == 1 && (xp.done != 1 || op.done != 0) || version == 2 && (xp.done != 0 || op.done != 1) { | |
| 224 | + | t.Fatalf("wl_output v%d: xdg done=%d, wl_output done=%d", version, xp.done, op.done) | |
| 225 | + | } | |
| 226 | + | }) | |
| 227 | + | } | |
| 228 | + | } | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package wayland | 1 | package wayland | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "fmt" | |||
| 4 | "github.com/bnema/nefertty/internal/ports" | 5 | "github.com/bnema/nefertty/internal/ports" | ||
| 5 | "github.com/bnema/purego-libwayland/protocol/wayland" | 6 | "github.com/bnema/purego-libwayland/protocol/wayland" | ||
| 6 | "github.com/bnema/purego-libwayland/protocol/wlrlayershell" | 7 | "github.com/bnema/purego-libwayland/protocol/wlrlayershell" | ||
| @@ -140,3 +141,88 @@ func TestWaybarLayer(t *testing.T) { | |||||
| 140 | t.Fatalf("waybar timeout stderr: %s", output.String()) | 141 | t.Fatalf("waybar timeout stderr: %s", output.String()) | ||
| 141 | } | 142 | } | ||
| 142 | } | 143 | } | ||
| 144 | + | ||||
| 145 | + | func TestLayerAlreadyConstructed(t *testing.T) { | |||
| 146 | + | for _, scenario := range []string{"role", "attached", "committed"} { | |||
| 147 | + | t.Run(scenario, func(t *testing.T) { | |||
| 148 | + | s, _, _, dir := lifecycleServer(t) | |||
| 149 | + | c := protocolClient(t, s, dir) | |||
| 150 | + | comp := bindProtocol(t, c, "wl_compositor") | |||
| 151 | + | shell := bindProtocol(t, c, "zwlr_layer_shell_v1") | |||
| 152 | + | surf := c.AllocateID() | |||
| 153 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf) | |||
| 154 | + | registerProtocol(t, c, surf) | |||
| 155 | + | switch scenario { | |||
| 156 | + | case "role": | |||
| 157 | + | sub := bindProtocol(t, c, "wl_subcompositor") | |||
| 158 | + | parent := c.AllocateID() | |||
| 159 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, parent) | |||
| 160 | + | registerProtocol(t, c, parent) | |||
| 161 | + | id := c.AllocateID() | |||
| 162 | + | requestProtocol(t, c, sub, wayland.SubcompositorRequestGetSubsurface, id, surf, parent) | |||
| 163 | + | case "attached": | |||
| 164 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, uint32(0), int32(0), int32(0)) | |||
| 165 | + | case "committed": | |||
| 166 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |||
| 167 | + | } | |||
| 168 | + | id := c.AllocateID() | |||
| 169 | + | requestProtocol(t, c, shell, wlrlayershell.ZwlrLayerShellV1RequestGetLayerSurface, id, surf, uint32(0), uint32(ports.LayerTop), "test") | |||
| 170 | + | expectProtocolError(t, c, shell, uint32(wlrlayershell.ZwlrLayerShellV1ErrorAlreadyConstructed)) | |||
| 171 | + | }) | |||
| 172 | + | } | |||
| 173 | + | } | |||
| 174 | + | ||||
| 175 | + | type doneProxy struct { | |||
| 176 | + | wlturbo.BaseProxy | |||
| 177 | + | done int | |||
| 178 | + | opcode uint16 | |||
| 179 | + | } | |||
| 180 | + | ||||
| 181 | + | func (p *doneProxy) Dispatch(e *wlturbo.Event) { | |||
| 182 | + | if e.Opcode == p.opcode { | |||
| 183 | + | p.done++ | |||
| 184 | + | } | |||
| 185 | + | } | |||
| 186 | + | func TestXDGOutputDone(t *testing.T) { | |||
| 187 | + | for _, version := range []uint32{1, 2} { | |||
| 188 | + | t.Run(fmt.Sprintf("wl_output_v%d", version), func(t *testing.T) { | |||
| 189 | + | s, _, _, dir := lifecycleServer(t) | |||
| 190 | + | c := protocolClient(t, s, dir) | |||
| 191 | + | global, ok := c.Registry().FindGlobal("wl_output") | |||
| 192 | + | if !ok { | |||
| 193 | + | t.Fatal("missing wl_output") | |||
| 194 | + | } | |||
| 195 | + | output, err := c.Registry().BindID(global.Name, global.Interface, version) | |||
| 196 | + | if err != nil { | |||
| 197 | + | t.Fatal(err) | |||
| 198 | + | } | |||
| 199 | + | op := &doneProxy{opcode: uint16(wayland.OutputEventDone)} | |||
| 200 | + | op.SetID(output) | |||
| 201 | + | c.Context().Register(op) | |||
| 202 | + | global, ok = c.Registry().FindGlobal("zxdg_output_manager_v1") | |||
| 203 | + | if !ok { | |||
| 204 | + | t.Fatal("missing xdg output manager") | |||
| 205 | + | } | |||
| 206 | + | manager, err := c.Registry().BindID(global.Name, global.Interface, 3) | |||
| 207 | + | if err != nil { | |||
| 208 | + | t.Fatal(err) | |||
| 209 | + | } | |||
| 210 | + | registerProtocol(t, c, manager) | |||
| 211 | + | if err := c.Roundtrip(); err != nil { | |||
| 212 | + | t.Fatal(err) | |||
| 213 | + | } | |||
| 214 | + | op.done = 0 | |||
| 215 | + | xdg := c.AllocateID() | |||
| 216 | + | xp := &doneProxy{opcode: 2} | |||
| 217 | + | xp.SetID(xdg) | |||
| 218 | + | c.Context().Register(xp) | |||
| 219 | + | requestProtocol(t, c, manager, 1, xdg, output) | |||
| 220 | + | if err := c.Roundtrip(); err != nil { | |||
| 221 | + | t.Fatal(err) | |||
| 222 | + | } | |||
| 223 | + | if version == 1 && (xp.done != 1 || op.done != 0) || version == 2 && (xp.done != 0 || op.done != 1) { | |||
| 224 | + | t.Fatalf("wl_output v%d: xdg done=%d, wl_output done=%d", version, xp.done, op.done) | |||
| 225 | + | } | |||
| 226 | + | }) | |||
| 227 | + | } | |||
| 228 | + | } | |||
M internal/adapters/wayland/server.go +2 -1
| @@ -43,6 +43,7 @@ type Server struct { | |||
| 43 | 43 | serial uint32 | |
| 44 | 44 | ctx context.Context | |
| 45 | 45 | windows map[ports.WindowID]*window | |
| 46 | + | layers map[ports.WindowID]*layerSurface | |
| 46 | 47 | nextWindow ports.WindowID | |
| 47 | 48 | focused ports.WindowID | |
| 48 | 49 | pointerFocus ports.WindowID | |
| @@ -90,7 +91,7 @@ func New(opts Options, ch Channels, log zerowrap.Logger) (*Server, error) { | |||
| 90 | 91 | d.Close() | |
| 91 | 92 | return nil, err | |
| 92 | 93 | } | |
| 93 | - | s := &Server{display: d, name: name, cleanup: cleanup, log: log, channels: ch, surfaces: make(map[*server.Resource]*surface), buffers: make(map[*server.Resource]*buffer), windows: make(map[ports.WindowID]*window), nextWindow: 1, eventReady: make(chan struct{}, 1), contents: make(map[ports.WindowID]ports.SurfaceContent), contentSeq: make(map[ports.WindowID]uint64), contentReady: make(chan struct{}, 1), contentNotify: make(chan struct{}), keymapFD: -1, keyboards: make(map[server.Client][]*wayland.Keyboard), pointers: make(map[server.Client][]*wayland.Pointer), repeatRate: opts.RepeatRate, repeatDelay: opts.RepeatDelay} | |
| 94 | + | s := &Server{display: d, name: name, cleanup: cleanup, log: log, channels: ch, surfaces: make(map[*server.Resource]*surface), buffers: make(map[*server.Resource]*buffer), windows: make(map[ports.WindowID]*window), layers: make(map[ports.WindowID]*layerSurface), nextWindow: 1, eventReady: make(chan struct{}, 1), contents: make(map[ports.WindowID]ports.SurfaceContent), contentSeq: make(map[ports.WindowID]uint64), contentReady: make(chan struct{}, 1), contentNotify: make(chan struct{}), keymapFD: -1, keyboards: make(map[server.Client][]*wayland.Keyboard), pointers: make(map[server.Client][]*wayland.Pointer), repeatRate: opts.RepeatRate, repeatDelay: opts.RepeatDelay} | |
| 94 | 95 | if opts.Keymap != "" { | |
| 95 | 96 | if uint64(len(opts.Keymap))+1 > uint64(^uint32(0)) { | |
| 96 | 97 | cleanup() | |
| @@ -43,6 +43,7 @@ type Server struct { | |||||
| 43 | serial uint32 | 43 | serial uint32 | ||
| 44 | ctx context.Context | 44 | ctx context.Context | ||
| 45 | windows map[ports.WindowID]*window | 45 | windows map[ports.WindowID]*window | ||
| 46 | + | layers map[ports.WindowID]*layerSurface | |||
| 46 | nextWindow ports.WindowID | 47 | nextWindow ports.WindowID | ||
| 47 | focused ports.WindowID | 48 | focused ports.WindowID | ||
| 48 | pointerFocus ports.WindowID | 49 | pointerFocus ports.WindowID | ||
| @@ -90,7 +91,7 @@ func New(opts Options, ch Channels, log zerowrap.Logger) (*Server, error) { | |||||
| 90 | d.Close() | 91 | d.Close() | ||
| 91 | return nil, err | 92 | return nil, err | ||
| 92 | } | 93 | } | ||
| 93 | - | s := &Server{display: d, name: name, cleanup: cleanup, log: log, channels: ch, surfaces: make(map[*server.Resource]*surface), buffers: make(map[*server.Resource]*buffer), windows: make(map[ports.WindowID]*window), nextWindow: 1, eventReady: make(chan struct{}, 1), contents: make(map[ports.WindowID]ports.SurfaceContent), contentSeq: make(map[ports.WindowID]uint64), contentReady: make(chan struct{}, 1), contentNotify: make(chan struct{}), keymapFD: -1, keyboards: make(map[server.Client][]*wayland.Keyboard), pointers: make(map[server.Client][]*wayland.Pointer), repeatRate: opts.RepeatRate, repeatDelay: opts.RepeatDelay} | 94 | + | s := &Server{display: d, name: name, cleanup: cleanup, log: log, channels: ch, surfaces: make(map[*server.Resource]*surface), buffers: make(map[*server.Resource]*buffer), windows: make(map[ports.WindowID]*window), layers: make(map[ports.WindowID]*layerSurface), nextWindow: 1, eventReady: make(chan struct{}, 1), contents: make(map[ports.WindowID]ports.SurfaceContent), contentSeq: make(map[ports.WindowID]uint64), contentReady: make(chan struct{}, 1), contentNotify: make(chan struct{}), keymapFD: -1, keyboards: make(map[server.Client][]*wayland.Keyboard), pointers: make(map[server.Client][]*wayland.Pointer), repeatRate: opts.RepeatRate, repeatDelay: opts.RepeatDelay} |
| 94 | if opts.Keymap != "" { | 95 | if opts.Keymap != "" { | ||
| 95 | if uint64(len(opts.Keymap))+1 > uint64(^uint32(0)) { | 96 | if uint64(len(opts.Keymap))+1 > uint64(^uint32(0)) { | ||
| 96 | cleanup() | 97 | cleanup() | ||
M internal/adapters/wayland/surface.go +2 -0
| @@ -23,6 +23,7 @@ type surface struct { | |||
| 23 | 23 | server *Server | |
| 24 | 24 | current, pending *wayland.Buffer | |
| 25 | 25 | attached bool | |
| 26 | + | committed bool | |
| 26 | 27 | callbacks []*wayland.Callback | |
| 27 | 28 | role func(bool) | |
| 28 | 29 | destroyed bool | |
| @@ -51,6 +52,7 @@ func (s *surface) Frame(r *wayland.Surface, id uint32) { | |||
| 51 | 52 | } | |
| 52 | 53 | } | |
| 53 | 54 | func (s *surface) Commit(*wayland.Surface) { | |
| 55 | + | s.committed = true | |
| 54 | 56 | fresh := s.attached && s.pending != nil | |
| 55 | 57 | if s.layer != nil && s.attached && s.pending != nil && !s.layer.acked { | |
| 56 | 58 | s.layer.resource.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "buffer before configure ack") | |
| @@ -23,6 +23,7 @@ type surface struct { | |||||
| 23 | server *Server | 23 | server *Server | ||
| 24 | current, pending *wayland.Buffer | 24 | current, pending *wayland.Buffer | ||
| 25 | attached bool | 25 | attached bool | ||
| 26 | + | committed bool | |||
| 26 | callbacks []*wayland.Callback | 27 | callbacks []*wayland.Callback | ||
| 27 | role func(bool) | 28 | role func(bool) | ||
| 28 | destroyed bool | 29 | destroyed bool | ||
| @@ -51,6 +52,7 @@ func (s *surface) Frame(r *wayland.Surface, id uint32) { | |||||
| 51 | } | 52 | } | ||
| 52 | } | 53 | } | ||
| 53 | func (s *surface) Commit(*wayland.Surface) { | 54 | func (s *surface) Commit(*wayland.Surface) { | ||
| 55 | + | s.committed = true | |||
| 54 | fresh := s.attached && s.pending != nil | 56 | fresh := s.attached && s.pending != nil | ||
| 55 | if s.layer != nil && s.attached && s.pending != nil && !s.layer.acked { | 57 | if s.layer != nil && s.attached && s.pending != nil && !s.layer.acked { | ||
| 56 | s.layer.resource.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "buffer before configure ack") | 58 | s.layer.resource.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "buffer before configure ack") | ||
M internal/adapters/wayland/xdgoutput.go +2 -2
| @@ -25,9 +25,9 @@ func (m xdgOutputManager) GetXdgOutput(r *xdgoutput.ZxdgOutputManagerV1, id uint | |||
| 25 | 25 | x.SendName("HEADLESS-1") | |
| 26 | 26 | x.SendDescription("NeferTTY headless output") | |
| 27 | 27 | } | |
| 28 | - | if r.Version() < 3 { | |
| 28 | + | if r.Version() < 3 || output == nil || output.Version() < 2 { | |
| 29 | 29 | x.SendDone() | |
| 30 | - | } else if output != nil && output.Version() >= 2 { | |
| 30 | + | } else { | |
| 31 | 31 | output.SendDone() | |
| 32 | 32 | } | |
| 33 | 33 | } | |
| @@ -25,9 +25,9 @@ func (m xdgOutputManager) GetXdgOutput(r *xdgoutput.ZxdgOutputManagerV1, id uint | |||||
| 25 | x.SendName("HEADLESS-1") | 25 | x.SendName("HEADLESS-1") | ||
| 26 | x.SendDescription("NeferTTY headless output") | 26 | x.SendDescription("NeferTTY headless output") | ||
| 27 | } | 27 | } | ||
| 28 | - | if r.Version() < 3 { | 28 | + | if r.Version() < 3 || output == nil || output.Version() < 2 { |
| 29 | x.SendDone() | 29 | x.SendDone() | ||
| 30 | - | } else if output != nil && output.Version() >= 2 { | 30 | + | } else { |
| 31 | output.SendDone() | 31 | output.SendDone() | ||
| 32 | } | 32 | } | ||
| 33 | } | 33 | } | ||