v0.7.0 f00b1b1

main.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 14

↑↓ moveenter openesc close