1package testserver
2
3import (
4 "encoding/binary"
5
6 "github.com/bnema/purego-libwayland/protocol/extworkspace"
7 "github.com/bnema/purego-libwayland/protocol/wayland"
8 "github.com/bnema/purego-libwayland/server"
9
10 "github.com/bnema/nefercap/internal/adapters/wayland/testserver/neferwl"
11)
12
13type workspace struct {
14 spec WorkspaceSpec
15 removed bool
16}
17
18type wsHandle struct {
19 w *workspace
20 res *extworkspace.ExtWorkspaceHandleV1
21}
22
23type wsManager struct {
24 s *Server
25 res *extworkspace.ExtWorkspaceManagerV1
26 group *extworkspace.ExtWorkspaceGroupHandleV1
27 handles []*wsHandle
28}
29
30func (m *wsManager) Commit(*extworkspace.ExtWorkspaceManagerV1) {}
31func (m *wsManager) Stop(*extworkspace.ExtWorkspaceManagerV1) {}
32
33type groupHandler struct{}
34
35func (groupHandler) CreateWorkspace(*extworkspace.ExtWorkspaceGroupHandleV1, string) {}
36func (groupHandler) Destroy(*extworkspace.ExtWorkspaceGroupHandleV1) {}
37
38type handleHandler struct{}
39
40func (handleHandler) Destroy(*extworkspace.ExtWorkspaceHandleV1) {}
41func (handleHandler) Activate(*extworkspace.ExtWorkspaceHandleV1) {}
42func (handleHandler) Deactivate(*extworkspace.ExtWorkspaceHandleV1) {}
43func (handleHandler) Assign(*extworkspace.ExtWorkspaceHandleV1, *extworkspace.ExtWorkspaceGroupHandleV1) {
44}
45func (handleHandler) Remove(*extworkspace.ExtWorkspaceHandleV1) {}
46
47func stateBits(w WorkspaceSpec) uint32 {
48 var st uint32
49 if w.Active {
50 st |= uint32(extworkspace.ExtWorkspaceHandleV1StateActive)
51 }
52 if w.Hidden {
53 st |= uint32(extworkspace.ExtWorkspaceHandleV1StateHidden)
54 }
55 return st
56}
57
58// announce sends the group, its outputs and every workspace, then done.
59func (m *wsManager) announce(c server.Client) {
60 g, err := extworkspace.NewExtWorkspaceGroupHandleV1(c, 1, 0, groupHandler{})
61 if err != nil {
62 return
63 }
64 m.group = g
65 m.res.SendWorkspaceGroup(g)
66 g.SendCapabilities(0)
67 for _, o := range m.s.outputs {
68 for _, r := range o.res {
69 if r.Client() == c {
70 g.SendOutputEnter(r)
71 }
72 }
73 }
74 for i, w := range m.s.workspaces {
75 if !w.removed {
76 m.add(c, g, w, i)
77 }
78 }
79 m.res.SendDone()
80}
81
82func (m *wsManager) add(c server.Client, g *extworkspace.ExtWorkspaceGroupHandleV1, w *workspace, index int) {
83 r, err := extworkspace.NewExtWorkspaceHandleV1(c, 1, 0, handleHandler{})
84 if err != nil {
85 return
86 }
87 m.handles = append(m.handles, &wsHandle{w: w, res: r})
88 m.res.SendWorkspace(r)
89 if w.spec.ID != "" {
90 r.SendId(w.spec.ID)
91 }
92 r.SendName(w.spec.Name)
93 var b [4]byte
94 binary.NativeEndian.PutUint32(b[:], uint32(index))
95 r.SendCoordinates(b[:])
96 r.SendState(stateBits(w.spec))
97 r.SendCapabilities(0)
98 g.SendWorkspaceEnter(r)
99}
100
101func (s *Server) workspaceNamed(name string) *workspace {
102 for _, w := range s.workspaces {
103 if w.spec.Name == name {
104 return w
105 }
106 }
107 return nil
108}
109
110// SetActive changes the active state of the named workspace.
111func (s *Server) SetActive(name string, active bool) {
112 s.Do(func() {
113 w := s.workspaceNamed(name)
114 if w == nil {
115 return
116 }
117 w.spec.Active = active
118 for _, m := range s.wsMgrs {
119 for _, h := range m.handles {
120 if h.w == w && !w.removed {
121 h.res.SendState(stateBits(w.spec))
122 }
123 }
124 m.res.SendDone()
125 }
126 })
127}
128
129// RemoveWorkspace removes the named workspace from every manager.
130func (s *Server) RemoveWorkspace(name string) {
131 s.Do(func() {
132 w := s.workspaceNamed(name)
133 if w == nil || w.removed {
134 return
135 }
136 w.removed = true
137 for _, m := range s.wsMgrs {
138 for _, h := range m.handles {
139 if h.w == w {
140 m.group.SendWorkspaceLeave(h.res)
141 h.res.SendRemoved()
142 }
143 }
144 m.res.SendDone()
145 }
146 for _, c := range s.sessions {
147 if c.src.ws == w {
148 c.stop()
149 }
150 }
151 })
152}
153
154// workspaceOf finds the workspace behind a handle resource.
155func (s *Server) workspaceOf(r *server.Resource) *workspace {
156 for _, m := range s.wsMgrs {
157 for _, h := range m.handles {
158 if h.res.Resource == r {
159 return h.w
160 }
161 }
162 }
163 return nil
164}
165
166type neferwlSources struct{ s *Server }
167
168// frameObject is one neferwl_workspace_frame_v1.
169type frameObject struct {
170 w *workspace
171 res *neferwl.NeferwlWorkspaceFrameV1
172}
173
174func (frameObject) Destroy(*neferwl.NeferwlWorkspaceFrameV1) {}
175
176func (f *frameObject) send() {
177 if r := f.w.spec.Frame; r != ([4]int32{}) {
178 f.res.SendFrame(r[0], r[1], r[2], r[3])
179 }
180}
181
182func (m neferwlSources) GetWorkspaceFrame(self *neferwl.NeferwlImageCaptureSourceManagerV1, id uint32, h *extworkspace.ExtWorkspaceHandleV1) {
183 s := m.s
184 f := &frameObject{w: s.workspaceOf(h.Resource)}
185 res, err := neferwl.NewNeferwlWorkspaceFrameV1(self.Client(), 1, id, f)
186 if err != nil {
187 return
188 }
189 f.res = res
190 s.stats.WorkspaceFrames++
191 res.OnDestroy = func() {
192 s.stats.DestroyedWorkspaceFrames++
193 s.frames = removeFrame(s.frames, f)
194 }
195 if f.w == nil || f.w.removed {
196 return
197 }
198 s.frames = append(s.frames, f)
199 f.send()
200}
201
202func removeFrame(list []*frameObject, f *frameObject) []*frameObject {
203 for i, g := range list {
204 if g == f {
205 return append(list[:i], list[i+1:]...)
206 }
207 }
208 return list
209}
210
211// SetFrame changes the frame of the named workspace and reports it to every
212// frame object that watches it.
213func (s *Server) SetFrame(name string, frame [4]int32) {
214 s.Do(func() {
215 w := s.workspaceNamed(name)
216 if w == nil {
217 return
218 }
219 w.spec.Frame = frame
220 for _, f := range s.frames {
221 if f.w == w && !w.removed {
222 f.send()
223 }
224 }
225 })
226}
227
228func (neferwlSources) Destroy(*neferwl.NeferwlImageCaptureSourceManagerV1) {}
229func (m neferwlSources) CreateWorkspaceSource(self *neferwl.NeferwlImageCaptureSourceManagerV1, id uint32, h *extworkspace.ExtWorkspaceHandleV1) {
230 w := m.s.workspaceOf(h.Resource)
231 m.s.newSource(self.Client(), id, &source{kind: "workspace", ws: w, gone: w == nil || w.removed})
232}
233func (m neferwlSources) CreateOutputRegionSource(self *neferwl.NeferwlImageCaptureSourceManagerV1, id uint32, out *wayland.Output, x, y, w, h int32) {
234 if w < 1 || h < 1 {
235 self.PostError(uint32(neferwl.NeferwlImageCaptureSourceManagerV1ErrorInvalidRegion), "empty region")
236 return
237 }
238 m.s.newSource(self.Client(), id, &source{kind: "region", out: m.s.outputOf(out.Resource), region: [4]int32{x, y, w, h}})
239}