1// Package server generates Go server-side bindings (for
2// github.com/bnema/purego-libwayland/server) from Wayland protocol XML.
3package server
4
5import (
6 "bytes"
7 "fmt"
8 "go/format"
9 "sort"
10 "strings"
11 "unicode"
12
13 "github.com/bnema/go-wayland-bindings/internal/gen/genutil"
14 "github.com/bnema/go-wayland-bindings/spec"
15)
16
17// Options configures Generate.
18type Options struct {
19 // Package is the Go package name of the generated file.
20 Package string
21 // Imports maps an import alias to a Go import path.
22 Imports map[string]string
23 // Owners maps an interface name to the import alias of the package that
24 // defines it; "" means the generated package itself. Interfaces defined
25 // by the protocol being generated are always local.
26 Owners map[string]string
27 // Tool is the tool name in the generated-code header (default "wlbgen").
28 Tool string
29 // Copyright is the upstream protocol copyright and license text. When
30 // non-empty it is retained in the header, as the client generator does.
31 Copyright string
32 // SPDX, when non-empty, adds an "SPDX-License-Identifier" header line
33 // unless Copyright already holds that exact line.
34 SPDX string
35}
36
37// Generate returns the gofmt-formatted server bindings for p.
38func Generate(p *spec.Protocol, opts Options) ([]byte, error) {
39 if p == nil {
40 return nil, fmt.Errorf("server: nil protocol")
41 }
42 if opts.Package == "" {
43 return nil, fmt.Errorf("server: empty package name")
44 }
45 tool := opts.Tool
46 if tool == "" {
47 tool = "wlbgen"
48 }
49 g := &gen{owners: map[string]string{}, imports: map[string]string{}}
50 for k, v := range opts.Imports {
51 g.imports[k] = v
52 }
53 for k, v := range opts.Owners {
54 g.owners[k] = v
55 }
56 for _, it := range p.Interfaces {
57 g.owners[it.Name] = ""
58 }
59 return g.generate(p, opts.Package, tool, opts)
60}
61
62func summary(d *spec.Description) string {
63 if d == nil {
64 return ""
65 }
66 return d.Summary
67}
68
69func camel(s string) string {
70 var b strings.Builder
71 for _, part := range strings.Split(s, "_") {
72 if part == "" {
73 continue
74 }
75 r := []rune(part)
76 b.WriteRune(unicode.ToUpper(r[0]))
77 b.WriteString(string(r[1:]))
78 }
79 return b.String()
80}
81func name(s string) string {
82 for _, prefix := range []string{"wl_", "xdg_"} {
83 if strings.HasPrefix(s, prefix) {
84 s = strings.TrimPrefix(s, prefix)
85 break
86 }
87 }
88 return camel(s)
89}
90func signature(m spec.Message) string {
91 var b strings.Builder
92 if m.Since > 1 {
93 fmt.Fprint(&b, m.Since)
94 }
95 for _, a := range m.Args {
96 if a.Type == "new_id" && a.Interface == "" {
97 b.WriteString("sun")
98 continue
99 }
100 if a.AllowNull {
101 b.WriteByte('?')
102 }
103 b.WriteString(map[string]string{"int": "i", "uint": "u", "fixed": "f", "string": "s", "array": "a", "object": "o", "new_id": "n", "fd": "h"}[a.Type])
104 }
105 return b.String()
106}
107
108type gen struct {
109 bytes.Buffer
110 owners map[string]string
111 imports map[string]string
112}
113
114func (g *gen) p(f string, a ...any) { fmt.Fprintf(&g.Buffer, f, a...) }
115func (g *gen) ref(s string) (string, error) {
116 if s == "" {
117 return "nil", nil
118 }
119 alias, ok := g.owners[s]
120 if !ok {
121 return "", fmt.Errorf("unknown interface %s", s)
122 }
123 if alias != "" {
124 alias += "."
125 }
126 return alias + name(s) + "Interface", nil
127}
128func (g *gen) wrapper(s string) string {
129 alias := g.owners[s]
130 if alias != "" {
131 alias += "."
132 }
133 return alias + name(s)
134}
135func (g *gen) table(m spec.Message) error {
136 var ts []string
137 for _, a := range m.Args {
138 if a.Type == "new_id" && a.Interface == "" {
139 ts = append(ts, "nil", "nil", "nil")
140 continue
141 }
142 t := "nil"
143 if a.Type == "object" || a.Type == "new_id" {
144 var e error
145 t, e = g.ref(a.Interface)
146 if e != nil {
147 return e
148 }
149 }
150 ts = append(ts, t)
151 }
152 g.p("{Name:%q,Signature:%q,Types:[]*server.Interface{%s}},\n", m.Name, signature(m), strings.Join(ts, ","))
153 return nil
154}
155func (g *gen) typ(a spec.Arg, event bool) string {
156 switch a.Type {
157 case "uint":
158 return "uint32"
159 case "int":
160 return "int32"
161 case "fixed":
162 return "server.Fixed"
163 case "string":
164 return "string"
165 case "array":
166 return "[]byte"
167 case "fd":
168 return "int"
169 case "new_id":
170 if !event {
171 return "uint32"
172 }
173 fallthrough
174 case "object":
175 if a.Interface == "" {
176 return "*server.Resource"
177 }
178 return "*" + g.wrapper(a.Interface)
179 }
180 return "server.Arg"
181}
182func param(a spec.Arg) string {
183 s := a.Name
184 if parts := strings.Split(s, "_"); len(parts) > 1 {
185 s = parts[0]
186 for _, part := range parts[1:] {
187 s += camel(part)
188 }
189 }
190 switch s {
191 case "interface", "type", "map", "range", "func", "var", "package", "select", "default", "go", "defer", "return", "chan", "import":
192 return s + "Arg"
193 }
194 return s
195}
196func (g *gen) params(m spec.Message, event bool) {
197 for _, a := range m.Args {
198 if a.Type == "new_id" && a.Interface == "" && !event {
199 g.p(", interfaceName string, version uint32, id uint32")
200 continue
201 }
202 g.p(", %s %s", param(a), g.typ(a, event))
203 }
204}
205func (g *gen) generate(pr *spec.Protocol, pkg, tool string, opts Options) ([]byte, error) {
206 g.p("// Code generated by %s; DO NOT EDIT.\n", tool)
207 spdx := genutil.SPDXID(opts.SPDX, opts.Copyright)
208 notice := genutil.CommentBlock(opts.Copyright)
209 if spdx != "" {
210 g.p("// SPDX-License-Identifier: %s\n", spdx)
211 }
212 if notice != "" {
213 g.p("\n// Upstream protocol copyright and license:\n%s\n", notice)
214 } else if spdx != "" {
215 g.p("\n")
216 }
217 g.p("package %s\nimport (\"github.com/bnema/purego-libwayland/server\"\n", pkg)
218 pin := false
219 for _, it := range pr.Interfaces {
220 for _, m := range it.Events {
221 for _, a := range m.Args {
222 if a.Type == "string" || a.Type == "array" || (a.Type == "new_id" && a.Interface == "") {
223 pin = true
224 }
225 }
226 }
227 }
228 if pin {
229 g.p("\"runtime\"\n")
230 }
231 for _, it := range pr.Interfaces {
232 if len(it.Requests) > 0 {
233 g.p("\"fmt\"\n")
234 break
235 }
236 }
237 aliases := make([]string, 0, len(g.imports))
238 for alias := range g.imports {
239 aliases = append(aliases, alias)
240 }
241 sort.Strings(aliases)
242 for _, alias := range aliases {
243 g.p("%s %q\n", alias, g.imports[alias])
244 }
245 g.p(")\n")
246 for _, it := range pr.Interfaces {
247 g.p("var %sInterface *server.Interface\n", name(it.Name))
248 }
249 g.p("func init(){\n")
250 for _, it := range pr.Interfaces {
251 g.p("%sInterface=&server.Interface{Name:%q,Version:%d}\n", name(it.Name), it.Name, it.Version)
252 }
253 for _, it := range pr.Interfaces {
254 n := name(it.Name)
255 g.p("%sInterface.Requests=[]server.Message{\n", n)
256 for _, m := range it.Requests {
257 if e := g.table(m); e != nil {
258 return nil, e
259 }
260 }
261 g.p("}\n%sInterface.Events=[]server.Message{\n", n)
262 for _, m := range it.Events {
263 if e := g.table(m); e != nil {
264 return nil, e
265 }
266 }
267 g.p("}\n")
268 }
269 g.p("if err:=server.NewInterfaces(")
270 for _, it := range pr.Interfaces {
271 g.p("%sInterface,", name(it.Name))
272 }
273 g.p(");err!=nil{panic(err)}}\n")
274 for _, it := range pr.Interfaces {
275 n := name(it.Name)
276 g.p("// %s %s.\ntype %s struct{*server.Resource}\nfunc Wrap%s(r *server.Resource)*%s{if r==nil{return nil};return &%s{r}}\n", n, summary(it.Description), n, n, n, n)
277 for j, m := range it.Requests {
278 g.p("const %sRequest%s uint32=%d\n", n, camel(m.Name), j)
279 }
280 for j, m := range it.Events {
281 g.p("const %sEvent%s uint32=%d\n", n, camel(m.Name), j)
282 }
283 for _, en := range it.Enums {
284 t := n + camel(en.Name)
285 g.p("// %s %s", t, summary(en.Description))
286 if en.Bitfield {
287 g.p(" (bitfield)")
288 }
289 g.p(".\ntype %s uint32\nconst(\n", t)
290 for _, v := range en.Entries {
291 g.p("%s%s %s=%s\n", t, camel(v.Name), t, v.Value)
292 }
293 g.p(")\n")
294 }
295 g.p("// %sHandler handles %s requests.\ntype %sHandler interface{\n", n, it.Name, n)
296 for _, m := range it.Requests {
297 g.p("%s(self *%s", camel(m.Name), n)
298 g.params(m, false)
299 g.p(")\n")
300 }
301 g.p("}\nfunc New%s(c server.Client,version int32,id uint32,h %sHandler)(*%s,error){", n, n, n)
302 if len(it.Requests) > 0 {
303 g.p("if h==nil{return nil,fmt.Errorf(%q)}\n", "purego-libwayland: nil handler for "+it.Name)
304 }
305 g.p("var self *%s\nr,err:=c.CreateResource(%sInterface,version,id,func(_ *server.Resource,opcode uint32,args []server.Arg){switch opcode{\n", n, n)
306 for j, m := range it.Requests {
307 g.p("case %d:h.%s(self", j, camel(m.Name))
308 idx := 0
309 for _, a := range m.Args {
310 if a.Type == "new_id" && a.Interface == "" {
311 g.p(",args[%d].String(),args[%d].Uint(),args[%d].NewID()", idx, idx+1, idx+2)
312 idx += 3
313 continue
314 }
315 x := fmt.Sprintf("args[%d]", idx)
316 idx++
317 switch a.Type {
318 case "uint", "int", "fixed", "string", "array", "fd", "new_id":
319 accessor := camel(a.Type)
320 if a.Type == "new_id" {
321 accessor = "NewID"
322 }
323 g.p(",%s.%s()", x, accessor)
324 case "object":
325 if a.Interface == "" {
326 g.p(",%s.Resource()", x)
327 } else {
328 g.p(",%sWrap%s(%s.Resource())", func() string {
329 if v := g.owners[a.Interface]; v != "" {
330 return v + "."
331 }
332 return ""
333 }(), name(a.Interface), x)
334 }
335 }
336 }
337 g.p(")")
338 if m.Type == "destructor" {
339 g.p(";self.Destroy()")
340 }
341 g.p("\n")
342 }
343 g.p("}});if err!=nil{return nil,err};self=Wrap%s(r);r.Data=self;return self,nil}\n", n)
344 for _, m := range it.Events {
345 g.p("// Send%s %s", camel(m.Name), summary(m.Description))
346 if m.Since > 1 {
347 g.p(" (since version %d)", m.Since)
348 }
349 g.p(".\nfunc(self *%s)Send%s(", n, camel(m.Name))
350 for i, a := range m.Args {
351 if i > 0 {
352 g.p(",")
353 }
354 g.p("%s %s", param(a), g.typ(a, true))
355 }
356 g.p("){\n")
357 needsPin := false
358 for _, a := range m.Args {
359 if a.Type == "string" || a.Type == "array" || (a.Type == "new_id" && a.Interface == "") {
360 needsPin = true
361 }
362 }
363 if needsPin {
364 g.p("var p runtime.Pinner;defer p.Unpin()\n")
365 }
366 g.p("self.PostEvent(%sEvent%s", n, camel(m.Name))
367 for _, a := range m.Args {
368 v := param(a)
369 if a.Type == "new_id" && a.Interface == "" {
370 g.p(",server.String(&p,%s.Iface().Name,false),server.Uint(uint32(%s.Version())),server.NewID(%s)", v, v, v)
371 continue
372 }
373 switch a.Type {
374 case "uint", "int", "fd":
375 g.p(",server.%s(%s)", map[string]string{"uint": "Uint", "int": "Int", "fd": "Fd"}[a.Type], v)
376 case "fixed":
377 g.p(",server.FixedArg(%s)", v)
378 case "string":
379 g.p(",server.String(&p,%s,%t)", v, a.AllowNull)
380 case "array":
381 g.p(",server.Array(&p,%s)", v)
382 case "object", "new_id":
383 constructor := "Object"
384 if a.Type == "new_id" {
385 constructor = "NewID"
386 }
387 if a.Interface == "" {
388 g.p(",server.%s(%s)", constructor, v)
389 } else {
390 g.p(",server.%s(resourceOf(%s))", constructor, v)
391 }
392 }
393 }
394 g.p(")\n}\n")
395 }
396 g.p("func New%sGlobal(d *server.Display,version int32,bind server.BindFunc)error{return d.CreateGlobal(%sInterface,version,bind)}\n", n, n)
397 }
398 g.p("func resourceOf(v interface{GetResource()*server.Resource})*server.Resource{if v==nil{return nil};return v.GetResource()}\n")
399 for _, it := range pr.Interfaces {
400 n := name(it.Name)
401 g.p("func(self *%s)GetResource()*server.Resource{if self==nil{return nil};return self.Resource}\n", n)
402 }
403 return format.Source(g.Bytes())
404}