merge: layer-wayland into layer-shell
5 files · +391 -2
M internal/adapters/wayland/globals.go +2 -0
| @@ -11,6 +11,8 @@ import ( | |||
| 11 | 11 | func registerGlobals(d *server.Display, o Options, s *Server) error { | |
| 12 | 12 | for _, register := range []func() error{ | |
| 13 | 13 | func() error { return registerXDG(d, s) }, | |
| 14 | + | func() error { return registerLayer(d, o, s) }, | |
| 15 | + | func() error { return registerXDGOutput(d, o) }, | |
| 14 | 16 | func() error { | |
| 15 | 17 | return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{s}) }) | |
| 16 | 18 | }, | |
| @@ -11,6 +11,8 @@ import ( | |||||
| 11 | func registerGlobals(d *server.Display, o Options, s *Server) error { | 11 | func registerGlobals(d *server.Display, o Options, s *Server) error { | ||
| 12 | for _, register := range []func() error{ | 12 | for _, register := range []func() error{ | ||
| 13 | func() error { return registerXDG(d, s) }, | 13 | func() error { return registerXDG(d, s) }, | ||
| 14 | + | func() error { return registerLayer(d, o, s) }, | |||
| 15 | + | func() error { return registerXDGOutput(d, o) }, | |||
| 14 | func() error { | 16 | func() error { | ||
| 15 | return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{s}) }) | 17 | return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{s}) }) | ||
| 16 | }, | 18 | }, | ||
A internal/adapters/wayland/layer.go +193 -0
| @@ -0,0 +1,193 @@ | |||
| 1 | + | package wayland | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "github.com/bnema/nefertty/internal/ports" | |
| 5 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 6 | + | "github.com/bnema/purego-libwayland/protocol/wlrlayershell" | |
| 7 | + | "github.com/bnema/purego-libwayland/protocol/xdgshell" | |
| 8 | + | "github.com/bnema/purego-libwayland/server" | |
| 9 | + | "sort" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | type layerShell struct { | |
| 13 | + | server *Server | |
| 14 | + | width, height uint32 | |
| 15 | + | } | |
| 16 | + | type layerState struct { | |
| 17 | + | width, height, anchor uint32 | |
| 18 | + | zone int32 | |
| 19 | + | margin [4]int32 | |
| 20 | + | keyboard uint32 | |
| 21 | + | layer ports.Layer | |
| 22 | + | } | |
| 23 | + | type layerSurface struct { | |
| 24 | + | resource *wlrlayershell.ZwlrLayerSurfaceV1 | |
| 25 | + | shell layerShell | |
| 26 | + | surface *surface | |
| 27 | + | id ports.WindowID | |
| 28 | + | namespace string | |
| 29 | + | pending, current layerState | |
| 30 | + | configured, acked, mapped bool | |
| 31 | + | serials []uint32 | |
| 32 | + | } | |
| 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 { | |
| 38 | + | layerSets[s] = make(map[ports.WindowID]*layerSurface) | |
| 39 | + | 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)}) | |
| 41 | + | }) | |
| 42 | + | } | |
| 43 | + | func (h layerShell) Destroy(*wlrlayershell.ZwlrLayerShellV1) {} | |
| 44 | + | func (h layerShell) GetLayerSurface(r *wlrlayershell.ZwlrLayerShellV1, id uint32, w *wayland.Surface, _ *wayland.Output, layer uint32, namespace string) { | |
| 45 | + | if w == nil || h.server.surfaces[w.Resource] == nil { | |
| 46 | + | return | |
| 47 | + | } | |
| 48 | + | state := h.server.surfaces[w.Resource] | |
| 49 | + | if state.kind != roleNone { | |
| 50 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerShellV1ErrorRole), "surface already has role") | |
| 51 | + | return | |
| 52 | + | } | |
| 53 | + | if layer > 3 { | |
| 54 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerShellV1ErrorInvalidLayer), "invalid layer") | |
| 55 | + | return | |
| 56 | + | } | |
| 57 | + | l := &layerSurface{shell: h, surface: state, id: h.server.nextWindow, namespace: namespace} | |
| 58 | + | l.pending.layer = ports.Layer(layer) | |
| 59 | + | resource, err := wlrlayershell.NewZwlrLayerSurfaceV1(r.Client(), r.Version(), id, l) | |
| 60 | + | if err != nil { | |
| 61 | + | return | |
| 62 | + | } | |
| 63 | + | l.resource = resource | |
| 64 | + | h.server.nextWindow++ | |
| 65 | + | state.kind = roleLayer | |
| 66 | + | state.layer = l | |
| 67 | + | state.role = l.commit | |
| 68 | + | layerSets[h.server][l.id] = l | |
| 69 | + | resource.OnDestroy = func() { | |
| 70 | + | l.unmap() | |
| 71 | + | delete(layerSets[h.server], l.id) | |
| 72 | + | state.layer = nil | |
| 73 | + | state.role = nil | |
| 74 | + | state.current, state.pending = nil, nil | |
| 75 | + | state.attached = false | |
| 76 | + | } | |
| 77 | + | } | |
| 78 | + | func (l *layerSurface) SetSize(_ *wlrlayershell.ZwlrLayerSurfaceV1, w, h uint32) { | |
| 79 | + | l.pending.width, l.pending.height = w, h | |
| 80 | + | } | |
| 81 | + | func (l *layerSurface) SetAnchor(r *wlrlayershell.ZwlrLayerSurfaceV1, a uint32) { | |
| 82 | + | if a > 15 { | |
| 83 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidAnchor), "invalid anchor") | |
| 84 | + | return | |
| 85 | + | } | |
| 86 | + | l.pending.anchor = a | |
| 87 | + | } | |
| 88 | + | func (l *layerSurface) SetExclusiveZone(_ *wlrlayershell.ZwlrLayerSurfaceV1, z int32) { | |
| 89 | + | l.pending.zone = z | |
| 90 | + | } | |
| 91 | + | func (l *layerSurface) SetMargin(_ *wlrlayershell.ZwlrLayerSurfaceV1, t, r, b, left int32) { | |
| 92 | + | l.pending.margin = [4]int32{t, r, b, left} | |
| 93 | + | } | |
| 94 | + | func (l *layerSurface) SetKeyboardInteractivity(r *wlrlayershell.ZwlrLayerSurfaceV1, k uint32) { | |
| 95 | + | if k > 2 { | |
| 96 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity), "invalid keyboard interactivity") | |
| 97 | + | return | |
| 98 | + | } | |
| 99 | + | l.pending.keyboard = k | |
| 100 | + | } | |
| 101 | + | func (*layerSurface) GetPopup(*wlrlayershell.ZwlrLayerSurfaceV1, *xdgshell.Popup) {} | |
| 102 | + | func (l *layerSurface) AckConfigure(r *wlrlayershell.ZwlrLayerSurfaceV1, serial uint32) { | |
| 103 | + | for i, v := range l.serials { | |
| 104 | + | if v == serial { | |
| 105 | + | l.serials = l.serials[i+1:] | |
| 106 | + | l.acked = true | |
| 107 | + | return | |
| 108 | + | } | |
| 109 | + | } | |
| 110 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "unknown configure serial") | |
| 111 | + | } | |
| 112 | + | func (*layerSurface) Destroy(*wlrlayershell.ZwlrLayerSurfaceV1) {} | |
| 113 | + | func (l *layerSurface) SetLayer(r *wlrlayershell.ZwlrLayerSurfaceV1, v uint32) { | |
| 114 | + | if v > 3 { | |
| 115 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "invalid layer") | |
| 116 | + | return | |
| 117 | + | } | |
| 118 | + | l.pending.layer = ports.Layer(v) | |
| 119 | + | } | |
| 120 | + | func (*layerSurface) SetExclusiveEdge(*wlrlayershell.ZwlrLayerSurfaceV1, uint32) {} | |
| 121 | + | func (l *layerSurface) unmap() { | |
| 122 | + | if !l.mapped { | |
| 123 | + | return | |
| 124 | + | } | |
| 125 | + | l.mapped = false | |
| 126 | + | l.configured = false | |
| 127 | + | l.acked = false | |
| 128 | + | l.serials = nil | |
| 129 | + | l.shell.server.layerChanged() | |
| 130 | + | l.shell.server.emitContent(ports.SurfaceContent{ID: l.id}) | |
| 131 | + | } | |
| 132 | + | func (s *Server) layerChanged() { | |
| 133 | + | list := make([]ports.LayerSurface, 0) | |
| 134 | + | for _, l := range layerSets[s] { | |
| 135 | + | if !l.mapped { | |
| 136 | + | continue | |
| 137 | + | } | |
| 138 | + | v := ports.LayerSurface{ID: l.id, Layer: l.current.layer, Anchor: l.current.anchor, ExclusiveZone: l.current.zone, Margin: l.current.margin, Namespace: l.namespace} | |
| 139 | + | if l.surface.current != nil { | |
| 140 | + | if b := s.buffers[l.surface.current.Resource]; b != nil { | |
| 141 | + | v.Width, v.Height = b.width, b.height | |
| 142 | + | } | |
| 143 | + | } | |
| 144 | + | list = append(list, v) | |
| 145 | + | } | |
| 146 | + | sort.Slice(list, func(i, j int) bool { return list[i].ID < list[j].ID }) | |
| 147 | + | s.emit(ports.LayerChanged{Layers: list}) | |
| 148 | + | } | |
| 149 | + | func (l *layerSurface) commit(buffer bool) { | |
| 150 | + | if l.surface.destroyed { | |
| 151 | + | l.unmap() | |
| 152 | + | delete(layerSets[l.shell.server], l.id) | |
| 153 | + | return | |
| 154 | + | } | |
| 155 | + | p := l.pending | |
| 156 | + | if (p.width == 0 && p.anchor&(ports.AnchorLeft|ports.AnchorRight) != ports.AnchorLeft|ports.AnchorRight) || (p.height == 0 && p.anchor&(ports.AnchorTop|ports.AnchorBottom) != ports.AnchorTop|ports.AnchorBottom) { | |
| 157 | + | l.resource.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSize), "zero size requires opposing anchors") | |
| 158 | + | return | |
| 159 | + | } | |
| 160 | + | changed := p != l.current | |
| 161 | + | sizeChanged := p.width != l.current.width || p.height != l.current.height | |
| 162 | + | l.current = p | |
| 163 | + | if !l.configured || sizeChanged { | |
| 164 | + | w, h := p.width, p.height | |
| 165 | + | if w == 0 { | |
| 166 | + | w = l.shell.width | |
| 167 | + | } | |
| 168 | + | if h == 0 { | |
| 169 | + | h = l.shell.height | |
| 170 | + | } | |
| 171 | + | l.shell.server.serial++ | |
| 172 | + | l.resource.SendConfigure(l.shell.server.serial, w, h) | |
| 173 | + | l.serials = append(l.serials, l.shell.server.serial) | |
| 174 | + | l.configured = true | |
| 175 | + | ||
| 176 | + | } | |
| 177 | + | if !buffer { | |
| 178 | + | if l.mapped { | |
| 179 | + | l.unmap() | |
| 180 | + | l.configured = false | |
| 181 | + | l.acked = false | |
| 182 | + | l.serials = nil | |
| 183 | + | } | |
| 184 | + | return | |
| 185 | + | } | |
| 186 | + | if l.acked { | |
| 187 | + | wasMapped := l.mapped | |
| 188 | + | l.mapped = true | |
| 189 | + | if changed || !wasMapped { | |
| 190 | + | l.shell.server.layerChanged() | |
| 191 | + | } | |
| 192 | + | } | |
| 193 | + | } | |
| @@ -0,0 +1,193 @@ | |||||
| 1 | + | package wayland | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "github.com/bnema/nefertty/internal/ports" | |||
| 5 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 6 | + | "github.com/bnema/purego-libwayland/protocol/wlrlayershell" | |||
| 7 | + | "github.com/bnema/purego-libwayland/protocol/xdgshell" | |||
| 8 | + | "github.com/bnema/purego-libwayland/server" | |||
| 9 | + | "sort" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | type layerShell struct { | |||
| 13 | + | server *Server | |||
| 14 | + | width, height uint32 | |||
| 15 | + | } | |||
| 16 | + | type layerState struct { | |||
| 17 | + | width, height, anchor uint32 | |||
| 18 | + | zone int32 | |||
| 19 | + | margin [4]int32 | |||
| 20 | + | keyboard uint32 | |||
| 21 | + | layer ports.Layer | |||
| 22 | + | } | |||
| 23 | + | type layerSurface struct { | |||
| 24 | + | resource *wlrlayershell.ZwlrLayerSurfaceV1 | |||
| 25 | + | shell layerShell | |||
| 26 | + | surface *surface | |||
| 27 | + | id ports.WindowID | |||
| 28 | + | namespace string | |||
| 29 | + | pending, current layerState | |||
| 30 | + | configured, acked, mapped bool | |||
| 31 | + | serials []uint32 | |||
| 32 | + | } | |||
| 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 { | |||
| 38 | + | layerSets[s] = make(map[ports.WindowID]*layerSurface) | |||
| 39 | + | 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)}) | |||
| 41 | + | }) | |||
| 42 | + | } | |||
| 43 | + | func (h layerShell) Destroy(*wlrlayershell.ZwlrLayerShellV1) {} | |||
| 44 | + | func (h layerShell) GetLayerSurface(r *wlrlayershell.ZwlrLayerShellV1, id uint32, w *wayland.Surface, _ *wayland.Output, layer uint32, namespace string) { | |||
| 45 | + | if w == nil || h.server.surfaces[w.Resource] == nil { | |||
| 46 | + | return | |||
| 47 | + | } | |||
| 48 | + | state := h.server.surfaces[w.Resource] | |||
| 49 | + | if state.kind != roleNone { | |||
| 50 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerShellV1ErrorRole), "surface already has role") | |||
| 51 | + | return | |||
| 52 | + | } | |||
| 53 | + | if layer > 3 { | |||
| 54 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerShellV1ErrorInvalidLayer), "invalid layer") | |||
| 55 | + | return | |||
| 56 | + | } | |||
| 57 | + | l := &layerSurface{shell: h, surface: state, id: h.server.nextWindow, namespace: namespace} | |||
| 58 | + | l.pending.layer = ports.Layer(layer) | |||
| 59 | + | resource, err := wlrlayershell.NewZwlrLayerSurfaceV1(r.Client(), r.Version(), id, l) | |||
| 60 | + | if err != nil { | |||
| 61 | + | return | |||
| 62 | + | } | |||
| 63 | + | l.resource = resource | |||
| 64 | + | h.server.nextWindow++ | |||
| 65 | + | state.kind = roleLayer | |||
| 66 | + | state.layer = l | |||
| 67 | + | state.role = l.commit | |||
| 68 | + | layerSets[h.server][l.id] = l | |||
| 69 | + | resource.OnDestroy = func() { | |||
| 70 | + | l.unmap() | |||
| 71 | + | delete(layerSets[h.server], l.id) | |||
| 72 | + | state.layer = nil | |||
| 73 | + | state.role = nil | |||
| 74 | + | state.current, state.pending = nil, nil | |||
| 75 | + | state.attached = false | |||
| 76 | + | } | |||
| 77 | + | } | |||
| 78 | + | func (l *layerSurface) SetSize(_ *wlrlayershell.ZwlrLayerSurfaceV1, w, h uint32) { | |||
| 79 | + | l.pending.width, l.pending.height = w, h | |||
| 80 | + | } | |||
| 81 | + | func (l *layerSurface) SetAnchor(r *wlrlayershell.ZwlrLayerSurfaceV1, a uint32) { | |||
| 82 | + | if a > 15 { | |||
| 83 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidAnchor), "invalid anchor") | |||
| 84 | + | return | |||
| 85 | + | } | |||
| 86 | + | l.pending.anchor = a | |||
| 87 | + | } | |||
| 88 | + | func (l *layerSurface) SetExclusiveZone(_ *wlrlayershell.ZwlrLayerSurfaceV1, z int32) { | |||
| 89 | + | l.pending.zone = z | |||
| 90 | + | } | |||
| 91 | + | func (l *layerSurface) SetMargin(_ *wlrlayershell.ZwlrLayerSurfaceV1, t, r, b, left int32) { | |||
| 92 | + | l.pending.margin = [4]int32{t, r, b, left} | |||
| 93 | + | } | |||
| 94 | + | func (l *layerSurface) SetKeyboardInteractivity(r *wlrlayershell.ZwlrLayerSurfaceV1, k uint32) { | |||
| 95 | + | if k > 2 { | |||
| 96 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity), "invalid keyboard interactivity") | |||
| 97 | + | return | |||
| 98 | + | } | |||
| 99 | + | l.pending.keyboard = k | |||
| 100 | + | } | |||
| 101 | + | func (*layerSurface) GetPopup(*wlrlayershell.ZwlrLayerSurfaceV1, *xdgshell.Popup) {} | |||
| 102 | + | func (l *layerSurface) AckConfigure(r *wlrlayershell.ZwlrLayerSurfaceV1, serial uint32) { | |||
| 103 | + | for i, v := range l.serials { | |||
| 104 | + | if v == serial { | |||
| 105 | + | l.serials = l.serials[i+1:] | |||
| 106 | + | l.acked = true | |||
| 107 | + | return | |||
| 108 | + | } | |||
| 109 | + | } | |||
| 110 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "unknown configure serial") | |||
| 111 | + | } | |||
| 112 | + | func (*layerSurface) Destroy(*wlrlayershell.ZwlrLayerSurfaceV1) {} | |||
| 113 | + | func (l *layerSurface) SetLayer(r *wlrlayershell.ZwlrLayerSurfaceV1, v uint32) { | |||
| 114 | + | if v > 3 { | |||
| 115 | + | r.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "invalid layer") | |||
| 116 | + | return | |||
| 117 | + | } | |||
| 118 | + | l.pending.layer = ports.Layer(v) | |||
| 119 | + | } | |||
| 120 | + | func (*layerSurface) SetExclusiveEdge(*wlrlayershell.ZwlrLayerSurfaceV1, uint32) {} | |||
| 121 | + | func (l *layerSurface) unmap() { | |||
| 122 | + | if !l.mapped { | |||
| 123 | + | return | |||
| 124 | + | } | |||
| 125 | + | l.mapped = false | |||
| 126 | + | l.configured = false | |||
| 127 | + | l.acked = false | |||
| 128 | + | l.serials = nil | |||
| 129 | + | l.shell.server.layerChanged() | |||
| 130 | + | l.shell.server.emitContent(ports.SurfaceContent{ID: l.id}) | |||
| 131 | + | } | |||
| 132 | + | func (s *Server) layerChanged() { | |||
| 133 | + | list := make([]ports.LayerSurface, 0) | |||
| 134 | + | for _, l := range layerSets[s] { | |||
| 135 | + | if !l.mapped { | |||
| 136 | + | continue | |||
| 137 | + | } | |||
| 138 | + | v := ports.LayerSurface{ID: l.id, Layer: l.current.layer, Anchor: l.current.anchor, ExclusiveZone: l.current.zone, Margin: l.current.margin, Namespace: l.namespace} | |||
| 139 | + | if l.surface.current != nil { | |||
| 140 | + | if b := s.buffers[l.surface.current.Resource]; b != nil { | |||
| 141 | + | v.Width, v.Height = b.width, b.height | |||
| 142 | + | } | |||
| 143 | + | } | |||
| 144 | + | list = append(list, v) | |||
| 145 | + | } | |||
| 146 | + | sort.Slice(list, func(i, j int) bool { return list[i].ID < list[j].ID }) | |||
| 147 | + | s.emit(ports.LayerChanged{Layers: list}) | |||
| 148 | + | } | |||
| 149 | + | func (l *layerSurface) commit(buffer bool) { | |||
| 150 | + | if l.surface.destroyed { | |||
| 151 | + | l.unmap() | |||
| 152 | + | delete(layerSets[l.shell.server], l.id) | |||
| 153 | + | return | |||
| 154 | + | } | |||
| 155 | + | p := l.pending | |||
| 156 | + | if (p.width == 0 && p.anchor&(ports.AnchorLeft|ports.AnchorRight) != ports.AnchorLeft|ports.AnchorRight) || (p.height == 0 && p.anchor&(ports.AnchorTop|ports.AnchorBottom) != ports.AnchorTop|ports.AnchorBottom) { | |||
| 157 | + | l.resource.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSize), "zero size requires opposing anchors") | |||
| 158 | + | return | |||
| 159 | + | } | |||
| 160 | + | changed := p != l.current | |||
| 161 | + | sizeChanged := p.width != l.current.width || p.height != l.current.height | |||
| 162 | + | l.current = p | |||
| 163 | + | if !l.configured || sizeChanged { | |||
| 164 | + | w, h := p.width, p.height | |||
| 165 | + | if w == 0 { | |||
| 166 | + | w = l.shell.width | |||
| 167 | + | } | |||
| 168 | + | if h == 0 { | |||
| 169 | + | h = l.shell.height | |||
| 170 | + | } | |||
| 171 | + | l.shell.server.serial++ | |||
| 172 | + | l.resource.SendConfigure(l.shell.server.serial, w, h) | |||
| 173 | + | l.serials = append(l.serials, l.shell.server.serial) | |||
| 174 | + | l.configured = true | |||
| 175 | + | ||||
| 176 | + | } | |||
| 177 | + | if !buffer { | |||
| 178 | + | if l.mapped { | |||
| 179 | + | l.unmap() | |||
| 180 | + | l.configured = false | |||
| 181 | + | l.acked = false | |||
| 182 | + | l.serials = nil | |||
| 183 | + | } | |||
| 184 | + | return | |||
| 185 | + | } | |||
| 186 | + | if l.acked { | |||
| 187 | + | wasMapped := l.mapped | |||
| 188 | + | l.mapped = true | |||
| 189 | + | if changed || !wasMapped { | |||
| 190 | + | l.shell.server.layerChanged() | |||
| 191 | + | } | |||
| 192 | + | } | |||
| 193 | + | } | |||
A internal/adapters/wayland/layer_test.go +142 -0
| @@ -0,0 +1,142 @@ | |||
| 1 | + | package wayland | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "github.com/bnema/nefertty/internal/ports" | |
| 5 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 6 | + | "github.com/bnema/purego-libwayland/protocol/wlrlayershell" | |
| 7 | + | "github.com/bnema/wlturbo" | |
| 8 | + | "golang.org/x/sys/unix" | |
| 9 | + | "os" | |
| 10 | + | "os/exec" | |
| 11 | + | "testing" | |
| 12 | + | "time" | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | type layerProxy struct { | |
| 16 | + | wlturbo.BaseProxy | |
| 17 | + | configured chan [3]uint32 | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | func (p *layerProxy) Dispatch(e *wlturbo.Event) { | |
| 21 | + | if e.Opcode == uint16(wlrlayershell.ZwlrLayerSurfaceV1EventConfigure) { | |
| 22 | + | p.configured <- [3]uint32{e.Uint32(), e.Uint32(), e.Uint32()} | |
| 23 | + | } | |
| 24 | + | } | |
| 25 | + | func layerProtocol(t *testing.T, c *wlturbo.Display) (uint32, uint32) { | |
| 26 | + | t.Helper() | |
| 27 | + | comp := bindProtocol(t, c, "wl_compositor") | |
| 28 | + | shell := bindProtocol(t, c, "zwlr_layer_shell_v1") | |
| 29 | + | surf := c.AllocateID() | |
| 30 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf) | |
| 31 | + | registerProtocol(t, c, surf) | |
| 32 | + | layer := c.AllocateID() | |
| 33 | + | requestProtocol(t, c, shell, wlrlayershell.ZwlrLayerShellV1RequestGetLayerSurface, layer, surf, uint32(0), uint32(ports.LayerTop), "test") | |
| 34 | + | return surf, layer | |
| 35 | + | } | |
| 36 | + | func TestLayerInvalidSize(t *testing.T) { | |
| 37 | + | s, _, _, dir := lifecycleServer(t) | |
| 38 | + | c := protocolClient(t, s, dir) | |
| 39 | + | surf, layer := layerProtocol(t, c) | |
| 40 | + | registerProtocol(t, c, layer) | |
| 41 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetSize, uint32(0), uint32(30)) | |
| 42 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetAnchor, uint32(ports.AnchorTop)) | |
| 43 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |
| 44 | + | expectProtocolError(t, c, layer, uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSize)) | |
| 45 | + | } | |
| 46 | + | func TestLayerLifecycle(t *testing.T) { | |
| 47 | + | s, events, _, dir := lifecycleServer(t) | |
| 48 | + | c := protocolClient(t, s, dir) | |
| 49 | + | surf, layer := layerProtocol(t, c) | |
| 50 | + | proxy := &layerProxy{configured: make(chan [3]uint32, 2)} | |
| 51 | + | proxy.SetID(layer) | |
| 52 | + | c.Context().Register(proxy) | |
| 53 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetAnchor, uint32(13)) | |
| 54 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetSize, uint32(0), uint32(30)) | |
| 55 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetExclusiveZone, int32(30)) | |
| 56 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |
| 57 | + | if err := c.Roundtrip(); err != nil { | |
| 58 | + | t.Fatal(err) | |
| 59 | + | } | |
| 60 | + | var config [3]uint32 | |
| 61 | + | select { | |
| 62 | + | case config = <-proxy.configured: | |
| 63 | + | case <-time.After(2 * time.Second): | |
| 64 | + | t.Fatal("configure timeout") | |
| 65 | + | } | |
| 66 | + | if config[1] != 1920 || config[2] != 30 { | |
| 67 | + | t.Fatalf("configure: %v", config) | |
| 68 | + | } | |
| 69 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestAckConfigure, config[0]) | |
| 70 | + | shm := bindProtocol(t, c, "wl_shm") | |
| 71 | + | registerProtocol(t, c, shm) | |
| 72 | + | fd, err := unix.MemfdCreate("layer-buffer", 0) | |
| 73 | + | if err != nil { | |
| 74 | + | t.Fatal(err) | |
| 75 | + | } | |
| 76 | + | defer unix.Close(fd) | |
| 77 | + | if err = unix.Ftruncate(fd, 1920*30*4); err != nil { | |
| 78 | + | t.Fatal(err) | |
| 79 | + | } | |
| 80 | + | pool, buf := c.AllocateID(), c.AllocateID() | |
| 81 | + | registerProtocol(t, c, pool) | |
| 82 | + | registerProtocol(t, c, buf) | |
| 83 | + | if err = c.SendRequestWithFDs(shm, uint16(wayland.ShmRequestCreatePool), []int{fd}, pool, int32(1920*30*4)); err != nil { | |
| 84 | + | t.Fatal(err) | |
| 85 | + | } | |
| 86 | + | requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, buf, int32(0), int32(1920), int32(30), int32(1920*4), uint32(wayland.ShmFormatArgb8888)) | |
| 87 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, buf, int32(0), int32(0)) | |
| 88 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |
| 89 | + | if err = c.Roundtrip(); err != nil { | |
| 90 | + | t.Fatal(err) | |
| 91 | + | } | |
| 92 | + | select { | |
| 93 | + | case ev := <-events: | |
| 94 | + | v, ok := ev.(ports.LayerChanged) | |
| 95 | + | if !ok || len(v.Layers) != 1 || v.Layers[0].Anchor != 13 || v.Layers[0].ExclusiveZone != 30 || v.Layers[0].Width != 1920 || v.Layers[0].Height != 30 { | |
| 96 | + | t.Fatalf("mapped: %#v", ev) | |
| 97 | + | } | |
| 98 | + | case <-time.After(2 * time.Second): | |
| 99 | + | t.Fatal("map timeout") | |
| 100 | + | } | |
| 101 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestDestroy) | |
| 102 | + | if err = c.Roundtrip(); err != nil { | |
| 103 | + | t.Fatal(err) | |
| 104 | + | } | |
| 105 | + | select { | |
| 106 | + | case ev := <-events: | |
| 107 | + | v, ok := ev.(ports.LayerChanged) | |
| 108 | + | if !ok || len(v.Layers) != 0 { | |
| 109 | + | t.Fatalf("destroy: %#v", ev) | |
| 110 | + | } | |
| 111 | + | case <-time.After(2 * time.Second): | |
| 112 | + | t.Fatal("destroy timeout") | |
| 113 | + | } | |
| 114 | + | } | |
| 115 | + | func TestWaybarLayer(t *testing.T) { | |
| 116 | + | tool, err := exec.LookPath("waybar") | |
| 117 | + | if err != nil { | |
| 118 | + | t.Skip("waybar missing") | |
| 119 | + | } | |
| 120 | + | s, events, _, dir := keyboardServer(t) | |
| 121 | + | home := t.TempDir() | |
| 122 | + | config := home + "/config.json" | |
| 123 | + | style := home + "/style.css" | |
| 124 | + | if err = os.WriteFile(config, []byte(`{"layer":"top","position":"top","height":30,"modules-left":["clock"]}`), 0600); err != nil { | |
| 125 | + | t.Fatal(err) | |
| 126 | + | } | |
| 127 | + | if err = os.WriteFile(style, nil, 0600); err != nil { | |
| 128 | + | t.Fatal(err) | |
| 129 | + | } | |
| 130 | + | _, done, output := lifecycleClient(t, tool, s, dir, []string{"HOME=" + home}, "-c", config, "-s", style) | |
| 131 | + | select { | |
| 132 | + | case ev := <-events: | |
| 133 | + | v, ok := ev.(ports.LayerChanged) | |
| 134 | + | if !ok || len(v.Layers) != 1 || v.Layers[0].Height != 30 { | |
| 135 | + | t.Fatalf("waybar event: %#v stderr: %s", ev, output.String()) | |
| 136 | + | } | |
| 137 | + | case err := <-done: | |
| 138 | + | t.Fatalf("waybar exited: %v stderr: %s", err, output.String()) | |
| 139 | + | case <-time.After(10 * time.Second): | |
| 140 | + | t.Fatalf("waybar timeout stderr: %s", output.String()) | |
| 141 | + | } | |
| 142 | + | } | |
| @@ -0,0 +1,142 @@ | |||||
| 1 | + | package wayland | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "github.com/bnema/nefertty/internal/ports" | |||
| 5 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 6 | + | "github.com/bnema/purego-libwayland/protocol/wlrlayershell" | |||
| 7 | + | "github.com/bnema/wlturbo" | |||
| 8 | + | "golang.org/x/sys/unix" | |||
| 9 | + | "os" | |||
| 10 | + | "os/exec" | |||
| 11 | + | "testing" | |||
| 12 | + | "time" | |||
| 13 | + | ) | |||
| 14 | + | ||||
| 15 | + | type layerProxy struct { | |||
| 16 | + | wlturbo.BaseProxy | |||
| 17 | + | configured chan [3]uint32 | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | func (p *layerProxy) Dispatch(e *wlturbo.Event) { | |||
| 21 | + | if e.Opcode == uint16(wlrlayershell.ZwlrLayerSurfaceV1EventConfigure) { | |||
| 22 | + | p.configured <- [3]uint32{e.Uint32(), e.Uint32(), e.Uint32()} | |||
| 23 | + | } | |||
| 24 | + | } | |||
| 25 | + | func layerProtocol(t *testing.T, c *wlturbo.Display) (uint32, uint32) { | |||
| 26 | + | t.Helper() | |||
| 27 | + | comp := bindProtocol(t, c, "wl_compositor") | |||
| 28 | + | shell := bindProtocol(t, c, "zwlr_layer_shell_v1") | |||
| 29 | + | surf := c.AllocateID() | |||
| 30 | + | requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf) | |||
| 31 | + | registerProtocol(t, c, surf) | |||
| 32 | + | layer := c.AllocateID() | |||
| 33 | + | requestProtocol(t, c, shell, wlrlayershell.ZwlrLayerShellV1RequestGetLayerSurface, layer, surf, uint32(0), uint32(ports.LayerTop), "test") | |||
| 34 | + | return surf, layer | |||
| 35 | + | } | |||
| 36 | + | func TestLayerInvalidSize(t *testing.T) { | |||
| 37 | + | s, _, _, dir := lifecycleServer(t) | |||
| 38 | + | c := protocolClient(t, s, dir) | |||
| 39 | + | surf, layer := layerProtocol(t, c) | |||
| 40 | + | registerProtocol(t, c, layer) | |||
| 41 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetSize, uint32(0), uint32(30)) | |||
| 42 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetAnchor, uint32(ports.AnchorTop)) | |||
| 43 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |||
| 44 | + | expectProtocolError(t, c, layer, uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSize)) | |||
| 45 | + | } | |||
| 46 | + | func TestLayerLifecycle(t *testing.T) { | |||
| 47 | + | s, events, _, dir := lifecycleServer(t) | |||
| 48 | + | c := protocolClient(t, s, dir) | |||
| 49 | + | surf, layer := layerProtocol(t, c) | |||
| 50 | + | proxy := &layerProxy{configured: make(chan [3]uint32, 2)} | |||
| 51 | + | proxy.SetID(layer) | |||
| 52 | + | c.Context().Register(proxy) | |||
| 53 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetAnchor, uint32(13)) | |||
| 54 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetSize, uint32(0), uint32(30)) | |||
| 55 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestSetExclusiveZone, int32(30)) | |||
| 56 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |||
| 57 | + | if err := c.Roundtrip(); err != nil { | |||
| 58 | + | t.Fatal(err) | |||
| 59 | + | } | |||
| 60 | + | var config [3]uint32 | |||
| 61 | + | select { | |||
| 62 | + | case config = <-proxy.configured: | |||
| 63 | + | case <-time.After(2 * time.Second): | |||
| 64 | + | t.Fatal("configure timeout") | |||
| 65 | + | } | |||
| 66 | + | if config[1] != 1920 || config[2] != 30 { | |||
| 67 | + | t.Fatalf("configure: %v", config) | |||
| 68 | + | } | |||
| 69 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestAckConfigure, config[0]) | |||
| 70 | + | shm := bindProtocol(t, c, "wl_shm") | |||
| 71 | + | registerProtocol(t, c, shm) | |||
| 72 | + | fd, err := unix.MemfdCreate("layer-buffer", 0) | |||
| 73 | + | if err != nil { | |||
| 74 | + | t.Fatal(err) | |||
| 75 | + | } | |||
| 76 | + | defer unix.Close(fd) | |||
| 77 | + | if err = unix.Ftruncate(fd, 1920*30*4); err != nil { | |||
| 78 | + | t.Fatal(err) | |||
| 79 | + | } | |||
| 80 | + | pool, buf := c.AllocateID(), c.AllocateID() | |||
| 81 | + | registerProtocol(t, c, pool) | |||
| 82 | + | registerProtocol(t, c, buf) | |||
| 83 | + | if err = c.SendRequestWithFDs(shm, uint16(wayland.ShmRequestCreatePool), []int{fd}, pool, int32(1920*30*4)); err != nil { | |||
| 84 | + | t.Fatal(err) | |||
| 85 | + | } | |||
| 86 | + | requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, buf, int32(0), int32(1920), int32(30), int32(1920*4), uint32(wayland.ShmFormatArgb8888)) | |||
| 87 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, buf, int32(0), int32(0)) | |||
| 88 | + | requestProtocol(t, c, surf, wayland.SurfaceRequestCommit) | |||
| 89 | + | if err = c.Roundtrip(); err != nil { | |||
| 90 | + | t.Fatal(err) | |||
| 91 | + | } | |||
| 92 | + | select { | |||
| 93 | + | case ev := <-events: | |||
| 94 | + | v, ok := ev.(ports.LayerChanged) | |||
| 95 | + | if !ok || len(v.Layers) != 1 || v.Layers[0].Anchor != 13 || v.Layers[0].ExclusiveZone != 30 || v.Layers[0].Width != 1920 || v.Layers[0].Height != 30 { | |||
| 96 | + | t.Fatalf("mapped: %#v", ev) | |||
| 97 | + | } | |||
| 98 | + | case <-time.After(2 * time.Second): | |||
| 99 | + | t.Fatal("map timeout") | |||
| 100 | + | } | |||
| 101 | + | requestProtocol(t, c, layer, wlrlayershell.ZwlrLayerSurfaceV1RequestDestroy) | |||
| 102 | + | if err = c.Roundtrip(); err != nil { | |||
| 103 | + | t.Fatal(err) | |||
| 104 | + | } | |||
| 105 | + | select { | |||
| 106 | + | case ev := <-events: | |||
| 107 | + | v, ok := ev.(ports.LayerChanged) | |||
| 108 | + | if !ok || len(v.Layers) != 0 { | |||
| 109 | + | t.Fatalf("destroy: %#v", ev) | |||
| 110 | + | } | |||
| 111 | + | case <-time.After(2 * time.Second): | |||
| 112 | + | t.Fatal("destroy timeout") | |||
| 113 | + | } | |||
| 114 | + | } | |||
| 115 | + | func TestWaybarLayer(t *testing.T) { | |||
| 116 | + | tool, err := exec.LookPath("waybar") | |||
| 117 | + | if err != nil { | |||
| 118 | + | t.Skip("waybar missing") | |||
| 119 | + | } | |||
| 120 | + | s, events, _, dir := keyboardServer(t) | |||
| 121 | + | home := t.TempDir() | |||
| 122 | + | config := home + "/config.json" | |||
| 123 | + | style := home + "/style.css" | |||
| 124 | + | if err = os.WriteFile(config, []byte(`{"layer":"top","position":"top","height":30,"modules-left":["clock"]}`), 0600); err != nil { | |||
| 125 | + | t.Fatal(err) | |||
| 126 | + | } | |||
| 127 | + | if err = os.WriteFile(style, nil, 0600); err != nil { | |||
| 128 | + | t.Fatal(err) | |||
| 129 | + | } | |||
| 130 | + | _, done, output := lifecycleClient(t, tool, s, dir, []string{"HOME=" + home}, "-c", config, "-s", style) | |||
| 131 | + | select { | |||
| 132 | + | case ev := <-events: | |||
| 133 | + | v, ok := ev.(ports.LayerChanged) | |||
| 134 | + | if !ok || len(v.Layers) != 1 || v.Layers[0].Height != 30 { | |||
| 135 | + | t.Fatalf("waybar event: %#v stderr: %s", ev, output.String()) | |||
| 136 | + | } | |||
| 137 | + | case err := <-done: | |||
| 138 | + | t.Fatalf("waybar exited: %v stderr: %s", err, output.String()) | |||
| 139 | + | case <-time.After(10 * time.Second): | |||
| 140 | + | t.Fatalf("waybar timeout stderr: %s", output.String()) | |||
| 141 | + | } | |||
| 142 | + | } | |||
M internal/adapters/wayland/surface.go +17 -2
| @@ -1,7 +1,9 @@ | |||
| 1 | 1 | package wayland | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "github.com/bnema/nefertty/internal/ports" | |
| 4 | 5 | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 6 | + | "github.com/bnema/purego-libwayland/protocol/wlrlayershell" | |
| 5 | 7 | "github.com/bnema/purego-libwayland/protocol/xdgshell" | |
| 6 | 8 | ) | |
| 7 | 9 | ||
| @@ -11,11 +13,13 @@ const ( | |||
| 11 | 13 | roleNone roleKind = iota | |
| 12 | 14 | roleXDG | |
| 13 | 15 | roleSubsurface | |
| 16 | + | roleLayer | |
| 14 | 17 | ) | |
| 15 | 18 | ||
| 16 | 19 | type surface struct { | |
| 17 | 20 | kind roleKind | |
| 18 | 21 | xdg *xdgSurface | |
| 22 | + | layer *layerSurface | |
| 19 | 23 | server *Server | |
| 20 | 24 | current, pending *wayland.Buffer | |
| 21 | 25 | attached bool | |
| @@ -48,6 +52,10 @@ func (s *surface) Frame(r *wayland.Surface, id uint32) { | |||
| 48 | 52 | } | |
| 49 | 53 | func (s *surface) Commit(*wayland.Surface) { | |
| 50 | 54 | fresh := s.attached && s.pending != nil | |
| 55 | + | if s.layer != nil && s.attached && s.pending != nil && !s.layer.acked { | |
| 56 | + | s.layer.resource.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "buffer before configure ack") | |
| 57 | + | return | |
| 58 | + | } | |
| 51 | 59 | if s.xdg != nil && s.attached && s.pending != nil && !s.xdg.acked { | |
| 52 | 60 | s.xdg.resource.PostError(uint32(xdgshell.SurfaceErrorUnconfiguredBuffer), "buffer before initial configure ack") | |
| 53 | 61 | return | |
| @@ -68,10 +76,17 @@ func (s *surface) Commit(*wayland.Surface) { | |||
| 68 | 76 | if s.role != nil { | |
| 69 | 77 | s.role(s.current != nil) | |
| 70 | 78 | } | |
| 71 | - | if fresh && s.xdg != nil && s.xdg.window != nil && s.xdg.window.mapped && s.server.channels.Contents != nil { | |
| 79 | + | id := ports.WindowID(0) | |
| 80 | + | if s.xdg != nil && s.xdg.window != nil && s.xdg.window.mapped { | |
| 81 | + | id = s.xdg.window.id | |
| 82 | + | } | |
| 83 | + | if s.layer != nil && s.layer.mapped { | |
| 84 | + | id = s.layer.id | |
| 85 | + | } | |
| 86 | + | if fresh && id != 0 && s.server.channels.Contents != nil { | |
| 72 | 87 | b := s.current | |
| 73 | 88 | if state, ok := s.server.buffers[b.Resource]; ok { | |
| 74 | - | if c, ok := state.content(s.xdg.window.id); ok { | |
| 89 | + | if c, ok := state.content(id); ok { | |
| 75 | 90 | s.server.emitContent(c) | |
| 76 | 91 | } else { | |
| 77 | 92 | state.pool.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "SHM backing file truncated") | |
| @@ -1,7 +1,9 @@ | |||||
| 1 | package wayland | 1 | package wayland | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "github.com/bnema/nefertty/internal/ports" | |||
| 4 | "github.com/bnema/purego-libwayland/protocol/wayland" | 5 | "github.com/bnema/purego-libwayland/protocol/wayland" | ||
| 6 | + | "github.com/bnema/purego-libwayland/protocol/wlrlayershell" | |||
| 5 | "github.com/bnema/purego-libwayland/protocol/xdgshell" | 7 | "github.com/bnema/purego-libwayland/protocol/xdgshell" | ||
| 6 | ) | 8 | ) | ||
| 7 | 9 | ||||
| @@ -11,11 +13,13 @@ const ( | |||||
| 11 | roleNone roleKind = iota | 13 | roleNone roleKind = iota | ||
| 12 | roleXDG | 14 | roleXDG | ||
| 13 | roleSubsurface | 15 | roleSubsurface | ||
| 16 | + | roleLayer | |||
| 14 | ) | 17 | ) | ||
| 15 | 18 | ||||
| 16 | type surface struct { | 19 | type surface struct { | ||
| 17 | kind roleKind | 20 | kind roleKind | ||
| 18 | xdg *xdgSurface | 21 | xdg *xdgSurface | ||
| 22 | + | layer *layerSurface | |||
| 19 | server *Server | 23 | server *Server | ||
| 20 | current, pending *wayland.Buffer | 24 | current, pending *wayland.Buffer | ||
| 21 | attached bool | 25 | attached bool | ||
| @@ -48,6 +52,10 @@ func (s *surface) Frame(r *wayland.Surface, id uint32) { | |||||
| 48 | } | 52 | } | ||
| 49 | func (s *surface) Commit(*wayland.Surface) { | 53 | func (s *surface) Commit(*wayland.Surface) { | ||
| 50 | fresh := s.attached && s.pending != nil | 54 | fresh := s.attached && s.pending != nil | ||
| 55 | + | if s.layer != nil && s.attached && s.pending != nil && !s.layer.acked { | |||
| 56 | + | s.layer.resource.PostError(uint32(wlrlayershell.ZwlrLayerSurfaceV1ErrorInvalidSurfaceState), "buffer before configure ack") | |||
| 57 | + | return | |||
| 58 | + | } | |||
| 51 | if s.xdg != nil && s.attached && s.pending != nil && !s.xdg.acked { | 59 | if s.xdg != nil && s.attached && s.pending != nil && !s.xdg.acked { | ||
| 52 | s.xdg.resource.PostError(uint32(xdgshell.SurfaceErrorUnconfiguredBuffer), "buffer before initial configure ack") | 60 | s.xdg.resource.PostError(uint32(xdgshell.SurfaceErrorUnconfiguredBuffer), "buffer before initial configure ack") | ||
| 53 | return | 61 | return | ||
| @@ -68,10 +76,17 @@ func (s *surface) Commit(*wayland.Surface) { | |||||
| 68 | if s.role != nil { | 76 | if s.role != nil { | ||
| 69 | s.role(s.current != nil) | 77 | s.role(s.current != nil) | ||
| 70 | } | 78 | } | ||
| 71 | - | if fresh && s.xdg != nil && s.xdg.window != nil && s.xdg.window.mapped && s.server.channels.Contents != nil { | 79 | + | id := ports.WindowID(0) |
| 80 | + | if s.xdg != nil && s.xdg.window != nil && s.xdg.window.mapped { | |||
| 81 | + | id = s.xdg.window.id | |||
| 82 | + | } | |||
| 83 | + | if s.layer != nil && s.layer.mapped { | |||
| 84 | + | id = s.layer.id | |||
| 85 | + | } | |||
| 86 | + | if fresh && id != 0 && s.server.channels.Contents != nil { | |||
| 72 | b := s.current | 87 | b := s.current | ||
| 73 | if state, ok := s.server.buffers[b.Resource]; ok { | 88 | if state, ok := s.server.buffers[b.Resource]; ok { | ||
| 74 | - | if c, ok := state.content(s.xdg.window.id); ok { | 89 | + | if c, ok := state.content(id); ok { |
| 75 | s.server.emitContent(c) | 90 | s.server.emitContent(c) | ||
| 76 | } else { | 91 | } else { | ||
| 77 | state.pool.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "SHM backing file truncated") | 92 | state.pool.shm.PostError(uint32(wayland.ShmErrorInvalidFd), "SHM backing file truncated") | ||
A internal/adapters/wayland/xdgoutput.go +37 -0
| @@ -0,0 +1,37 @@ | |||
| 1 | + | package wayland | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 5 | + | "github.com/bnema/purego-libwayland/protocol/xdgoutput" | |
| 6 | + | "github.com/bnema/purego-libwayland/server" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | type xdgOutputManager struct{ width, height int32 } | |
| 10 | + | ||
| 11 | + | func registerXDGOutput(d *server.Display, o Options) error { | |
| 12 | + | return xdgoutput.NewZxdgOutputManagerV1Global(d, 3, func(c server.Client, v, id uint32) { | |
| 13 | + | _, _ = xdgoutput.NewZxdgOutputManagerV1(c, int32(v), id, xdgOutputManager{int32(o.OutputWidth), int32(o.OutputHeight)}) | |
| 14 | + | }) | |
| 15 | + | } | |
| 16 | + | func (xdgOutputManager) Destroy(*xdgoutput.ZxdgOutputManagerV1) {} | |
| 17 | + | func (m xdgOutputManager) GetXdgOutput(r *xdgoutput.ZxdgOutputManagerV1, id uint32, output *wayland.Output) { | |
| 18 | + | x, err := xdgoutput.NewZxdgOutputV1(r.Client(), r.Version(), id, xdgOutput{}) | |
| 19 | + | if err != nil { | |
| 20 | + | return | |
| 21 | + | } | |
| 22 | + | x.SendLogicalPosition(0, 0) | |
| 23 | + | x.SendLogicalSize(m.width, m.height) | |
| 24 | + | if r.Version() >= 2 { | |
| 25 | + | x.SendName("HEADLESS-1") | |
| 26 | + | x.SendDescription("NeferTTY headless output") | |
| 27 | + | } | |
| 28 | + | if r.Version() < 3 { | |
| 29 | + | x.SendDone() | |
| 30 | + | } else if output != nil && output.Version() >= 2 { | |
| 31 | + | output.SendDone() | |
| 32 | + | } | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | type xdgOutput struct{} | |
| 36 | + | ||
| 37 | + | func (xdgOutput) Destroy(*xdgoutput.ZxdgOutputV1) {} | |
| @@ -0,0 +1,37 @@ | |||||
| 1 | + | package wayland | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 5 | + | "github.com/bnema/purego-libwayland/protocol/xdgoutput" | |||
| 6 | + | "github.com/bnema/purego-libwayland/server" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | type xdgOutputManager struct{ width, height int32 } | |||
| 10 | + | ||||
| 11 | + | func registerXDGOutput(d *server.Display, o Options) error { | |||
| 12 | + | return xdgoutput.NewZxdgOutputManagerV1Global(d, 3, func(c server.Client, v, id uint32) { | |||
| 13 | + | _, _ = xdgoutput.NewZxdgOutputManagerV1(c, int32(v), id, xdgOutputManager{int32(o.OutputWidth), int32(o.OutputHeight)}) | |||
| 14 | + | }) | |||
| 15 | + | } | |||
| 16 | + | func (xdgOutputManager) Destroy(*xdgoutput.ZxdgOutputManagerV1) {} | |||
| 17 | + | func (m xdgOutputManager) GetXdgOutput(r *xdgoutput.ZxdgOutputManagerV1, id uint32, output *wayland.Output) { | |||
| 18 | + | x, err := xdgoutput.NewZxdgOutputV1(r.Client(), r.Version(), id, xdgOutput{}) | |||
| 19 | + | if err != nil { | |||
| 20 | + | return | |||
| 21 | + | } | |||
| 22 | + | x.SendLogicalPosition(0, 0) | |||
| 23 | + | x.SendLogicalSize(m.width, m.height) | |||
| 24 | + | if r.Version() >= 2 { | |||
| 25 | + | x.SendName("HEADLESS-1") | |||
| 26 | + | x.SendDescription("NeferTTY headless output") | |||
| 27 | + | } | |||
| 28 | + | if r.Version() < 3 { | |||
| 29 | + | x.SendDone() | |||
| 30 | + | } else if output != nil && output.Version() >= 2 { | |||
| 31 | + | output.SendDone() | |||
| 32 | + | } | |||
| 33 | + | } | |||
| 34 | + | ||||
| 35 | + | type xdgOutput struct{} | |||
| 36 | + | ||||
| 37 | + | func (xdgOutput) Destroy(*xdgoutput.ZxdgOutputV1) {} | |||