detached 1a6c6c9

server.go RawHistoryFolder

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}

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 1

↑↓ moveenter openesc close