// Package server generates Go server-side bindings (for // github.com/bnema/purego-libwayland/server) from Wayland protocol XML. package server import ( "bytes" "fmt" "go/format" "sort" "strings" "unicode" "github.com/bnema/go-wayland-bindings/internal/gen/genutil" "github.com/bnema/go-wayland-bindings/spec" ) // Options configures Generate. type Options struct { // Package is the Go package name of the generated file. Package string // Imports maps an import alias to a Go import path. Imports map[string]string // Owners maps an interface name to the import alias of the package that // defines it; "" means the generated package itself. Interfaces defined // by the protocol being generated are always local. Owners map[string]string // Tool is the tool name in the generated-code header (default "wlbgen"). Tool string // Copyright is the upstream protocol copyright and license text. When // non-empty it is retained in the header, as the client generator does. Copyright string // SPDX, when non-empty, adds an "SPDX-License-Identifier" header line // unless Copyright already holds that exact line. SPDX string } // Generate returns the gofmt-formatted server bindings for p. func Generate(p *spec.Protocol, opts Options) ([]byte, error) { if p == nil { return nil, fmt.Errorf("server: nil protocol") } if opts.Package == "" { return nil, fmt.Errorf("server: empty package name") } tool := opts.Tool if tool == "" { tool = "wlbgen" } g := &gen{owners: map[string]string{}, imports: map[string]string{}} for k, v := range opts.Imports { g.imports[k] = v } for k, v := range opts.Owners { g.owners[k] = v } for _, it := range p.Interfaces { g.owners[it.Name] = "" } return g.generate(p, opts.Package, tool, opts) } func summary(d *spec.Description) string { if d == nil { return "" } return d.Summary } func camel(s string) string { var b strings.Builder for _, part := range strings.Split(s, "_") { if part == "" { continue } r := []rune(part) b.WriteRune(unicode.ToUpper(r[0])) b.WriteString(string(r[1:])) } return b.String() } func name(s string) string { for _, prefix := range []string{"wl_", "xdg_"} { if strings.HasPrefix(s, prefix) { s = strings.TrimPrefix(s, prefix) break } } return camel(s) } func signature(m spec.Message) string { var b strings.Builder if m.Since > 1 { fmt.Fprint(&b, m.Since) } for _, a := range m.Args { if a.Type == "new_id" && a.Interface == "" { b.WriteString("sun") continue } if a.AllowNull { b.WriteByte('?') } b.WriteString(map[string]string{"int": "i", "uint": "u", "fixed": "f", "string": "s", "array": "a", "object": "o", "new_id": "n", "fd": "h"}[a.Type]) } return b.String() } type gen struct { bytes.Buffer owners map[string]string imports map[string]string } func (g *gen) p(f string, a ...any) { fmt.Fprintf(&g.Buffer, f, a...) } func (g *gen) ref(s string) (string, error) { if s == "" { return "nil", nil } alias, ok := g.owners[s] if !ok { return "", fmt.Errorf("unknown interface %s", s) } if alias != "" { alias += "." } return alias + name(s) + "Interface", nil } func (g *gen) wrapper(s string) string { alias := g.owners[s] if alias != "" { alias += "." } return alias + name(s) } func (g *gen) table(m spec.Message) error { var ts []string for _, a := range m.Args { if a.Type == "new_id" && a.Interface == "" { ts = append(ts, "nil", "nil", "nil") continue } t := "nil" if a.Type == "object" || a.Type == "new_id" { var e error t, e = g.ref(a.Interface) if e != nil { return e } } ts = append(ts, t) } g.p("{Name:%q,Signature:%q,Types:[]*server.Interface{%s}},\n", m.Name, signature(m), strings.Join(ts, ",")) return nil } func (g *gen) typ(a spec.Arg, event bool) string { switch a.Type { case "uint": return "uint32" case "int": return "int32" case "fixed": return "server.Fixed" case "string": return "string" case "array": return "[]byte" case "fd": return "int" case "new_id": if !event { return "uint32" } fallthrough case "object": if a.Interface == "" { return "*server.Resource" } return "*" + g.wrapper(a.Interface) } return "server.Arg" } func param(a spec.Arg) string { s := a.Name if parts := strings.Split(s, "_"); len(parts) > 1 { s = parts[0] for _, part := range parts[1:] { s += camel(part) } } switch s { case "interface", "type", "map", "range", "func", "var", "package", "select", "default", "go", "defer", "return", "chan", "import": return s + "Arg" } return s } func (g *gen) params(m spec.Message, event bool) { for _, a := range m.Args { if a.Type == "new_id" && a.Interface == "" && !event { g.p(", interfaceName string, version uint32, id uint32") continue } g.p(", %s %s", param(a), g.typ(a, event)) } } func (g *gen) generate(pr *spec.Protocol, pkg, tool string, opts Options) ([]byte, error) { g.p("// Code generated by %s; DO NOT EDIT.\n", tool) spdx := genutil.SPDXID(opts.SPDX, opts.Copyright) notice := genutil.CommentBlock(opts.Copyright) if spdx != "" { g.p("// SPDX-License-Identifier: %s\n", spdx) } if notice != "" { g.p("\n// Upstream protocol copyright and license:\n%s\n", notice) } else if spdx != "" { g.p("\n") } g.p("package %s\nimport (\"github.com/bnema/purego-libwayland/server\"\n", pkg) pin := false for _, it := range pr.Interfaces { for _, m := range it.Events { for _, a := range m.Args { if a.Type == "string" || a.Type == "array" || (a.Type == "new_id" && a.Interface == "") { pin = true } } } } if pin { g.p("\"runtime\"\n") } for _, it := range pr.Interfaces { if len(it.Requests) > 0 { g.p("\"fmt\"\n") break } } aliases := make([]string, 0, len(g.imports)) for alias := range g.imports { aliases = append(aliases, alias) } sort.Strings(aliases) for _, alias := range aliases { g.p("%s %q\n", alias, g.imports[alias]) } g.p(")\n") for _, it := range pr.Interfaces { g.p("var %sInterface *server.Interface\n", name(it.Name)) } g.p("func init(){\n") for _, it := range pr.Interfaces { g.p("%sInterface=&server.Interface{Name:%q,Version:%d}\n", name(it.Name), it.Name, it.Version) } for _, it := range pr.Interfaces { n := name(it.Name) g.p("%sInterface.Requests=[]server.Message{\n", n) for _, m := range it.Requests { if e := g.table(m); e != nil { return nil, e } } g.p("}\n%sInterface.Events=[]server.Message{\n", n) for _, m := range it.Events { if e := g.table(m); e != nil { return nil, e } } g.p("}\n") } g.p("if err:=server.NewInterfaces(") for _, it := range pr.Interfaces { g.p("%sInterface,", name(it.Name)) } g.p(");err!=nil{panic(err)}}\n") for _, it := range pr.Interfaces { n := name(it.Name) 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) for j, m := range it.Requests { g.p("const %sRequest%s uint32=%d\n", n, camel(m.Name), j) } for j, m := range it.Events { g.p("const %sEvent%s uint32=%d\n", n, camel(m.Name), j) } for _, en := range it.Enums { t := n + camel(en.Name) g.p("// %s %s", t, summary(en.Description)) if en.Bitfield { g.p(" (bitfield)") } g.p(".\ntype %s uint32\nconst(\n", t) for _, v := range en.Entries { g.p("%s%s %s=%s\n", t, camel(v.Name), t, v.Value) } g.p(")\n") } g.p("// %sHandler handles %s requests.\ntype %sHandler interface{\n", n, it.Name, n) for _, m := range it.Requests { g.p("%s(self *%s", camel(m.Name), n) g.params(m, false) g.p(")\n") } g.p("}\nfunc New%s(c server.Client,version int32,id uint32,h %sHandler)(*%s,error){", n, n, n) if len(it.Requests) > 0 { g.p("if h==nil{return nil,fmt.Errorf(%q)}\n", "purego-libwayland: nil handler for "+it.Name) } 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) for j, m := range it.Requests { g.p("case %d:h.%s(self", j, camel(m.Name)) idx := 0 for _, a := range m.Args { if a.Type == "new_id" && a.Interface == "" { g.p(",args[%d].String(),args[%d].Uint(),args[%d].NewID()", idx, idx+1, idx+2) idx += 3 continue } x := fmt.Sprintf("args[%d]", idx) idx++ switch a.Type { case "uint", "int", "fixed", "string", "array", "fd", "new_id": accessor := camel(a.Type) if a.Type == "new_id" { accessor = "NewID" } g.p(",%s.%s()", x, accessor) case "object": if a.Interface == "" { g.p(",%s.Resource()", x) } else { g.p(",%sWrap%s(%s.Resource())", func() string { if v := g.owners[a.Interface]; v != "" { return v + "." } return "" }(), name(a.Interface), x) } } } g.p(")") if m.Type == "destructor" { g.p(";self.Destroy()") } g.p("\n") } g.p("}});if err!=nil{return nil,err};self=Wrap%s(r);r.Data=self;return self,nil}\n", n) for _, m := range it.Events { g.p("// Send%s %s", camel(m.Name), summary(m.Description)) if m.Since > 1 { g.p(" (since version %d)", m.Since) } g.p(".\nfunc(self *%s)Send%s(", n, camel(m.Name)) for i, a := range m.Args { if i > 0 { g.p(",") } g.p("%s %s", param(a), g.typ(a, true)) } g.p("){\n") needsPin := false for _, a := range m.Args { if a.Type == "string" || a.Type == "array" || (a.Type == "new_id" && a.Interface == "") { needsPin = true } } if needsPin { g.p("var p runtime.Pinner;defer p.Unpin()\n") } g.p("self.PostEvent(%sEvent%s", n, camel(m.Name)) for _, a := range m.Args { v := param(a) if a.Type == "new_id" && a.Interface == "" { g.p(",server.String(&p,%s.Iface().Name,false),server.Uint(uint32(%s.Version())),server.NewID(%s)", v, v, v) continue } switch a.Type { case "uint", "int", "fd": g.p(",server.%s(%s)", map[string]string{"uint": "Uint", "int": "Int", "fd": "Fd"}[a.Type], v) case "fixed": g.p(",server.FixedArg(%s)", v) case "string": g.p(",server.String(&p,%s,%t)", v, a.AllowNull) case "array": g.p(",server.Array(&p,%s)", v) case "object", "new_id": constructor := "Object" if a.Type == "new_id" { constructor = "NewID" } if a.Interface == "" { g.p(",server.%s(%s)", constructor, v) } else { g.p(",server.%s(resourceOf(%s))", constructor, v) } } } g.p(")\n}\n") } g.p("func New%sGlobal(d *server.Display,version int32,bind server.BindFunc)error{return d.CreateGlobal(%sInterface,version,bind)}\n", n, n) } g.p("func resourceOf(v interface{GetResource()*server.Resource})*server.Resource{if v==nil{return nil};return v.GetResource()}\n") for _, it := range pr.Interfaces { n := name(it.Name) g.p("func(self *%s)GetResource()*server.Resource{if self==nil{return nil};return self.Resource}\n", n) } return format.Source(g.Bytes()) }