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