1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
3
4package main
5
6// #cgo CFLAGS: -x objective-c
7// #cgo LDFLAGS: -framework AppKit
8//
9// #import <AppKit/AppKit.h>
10//
11// __attribute__((used))
12// static void __force_protocol_load() {
13// id o = NULL;
14// o = @protocol(NSAccessibility);
15// o = @protocol(NSApplicationDelegate);
16// }
17import "C"
18
19import (
20 "fmt"
21 "io"
22 "os"
23 "strconv"
24 "strings"
25 "text/template"
26
27 "github.com/ebitengine/purego/objc"
28)
29
30var protocols = []string{
31 "NSAccessibility",
32 "NSApplicationDelegate",
33}
34
35type ProtocolImpl struct {
36 Name string
37
38 RequiredInstanceMethods []objc.MethodDescription
39 RequiredClassMethods []objc.MethodDescription
40 OptionalInstanceMethods []objc.MethodDescription
41 OptionalClassMethods []objc.MethodDescription
42
43 AdoptedProtocols []*objc.Protocol
44
45 RequiredInstanceProperties []objc.Property
46 RequiredClassProperties []objc.Property
47 OptionalInstanceProperties []objc.Property
48 OptionalClassProperties []objc.Property
49}
50
51func readProtocols(names []string) (imps []ProtocolImpl, err error) {
52 for _, name := range names {
53 p := objc.GetProtocol(name)
54 if p == nil {
55 return nil, fmt.Errorf("protocol '%s' does not exist", name)
56 }
57 imp := ProtocolImpl{}
58 imp.Name = name
59 imp.RequiredInstanceMethods = p.CopyMethodDescriptionList(true, true)
60 imp.RequiredClassMethods = p.CopyMethodDescriptionList(true, false)
61 imp.OptionalInstanceMethods = p.CopyMethodDescriptionList(false, true)
62 imp.OptionalClassMethods = p.CopyMethodDescriptionList(false, false)
63
64 imp.AdoptedProtocols = p.CopyProtocolList()
65
66 imp.RequiredInstanceProperties = p.CopyPropertyList(true, true)
67 imp.RequiredClassProperties = p.CopyPropertyList(true, false)
68 imp.OptionalInstanceProperties = p.CopyPropertyList(false, true)
69 imp.OptionalClassProperties = p.CopyPropertyList(false, false)
70 imps = append(imps, imp)
71 }
72 return imps, nil
73}
74
75const templ = `// Code generated by protocol-dumper; DO NOT EDIT.
76
77package main
78
79import (
80 "log"
81
82 "github.com/ebitengine/purego/objc"
83)
84
85func init() {
86 var p *objc.Protocol
87 {{- range . }}
88 {{- $protocolName := .Name }}
89 if p = objc.AllocateProtocol("{{$protocolName}}"); p != nil {
90 {{- range .RequiredInstanceMethods }}
91 p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", true, true)
92 {{- end }}
93
94 {{- range .RequiredClassMethods }}
95 p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", true, false)
96 {{- end }}
97
98 {{- range .OptionalInstanceMethods }}
99 p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", false, true)
100 {{- end }}
101
102 {{- range .OptionalClassMethods }}
103 p.AddMethodDescription(objc.RegisterName("{{ .Name }}"), "{{ .Types }}", false, false)
104 {{- end }}
105 var adoptedProtocol *objc.Protocol
106 {{- range .AdoptedProtocols }}
107 adoptedProtocol = objc.GetProtocol("{{ .Name }}")
108 if adoptedProtocol == nil {
109 log.Fatalln("protocol '{{ .Name }}' does not exist")
110 }
111 p.AddProtocol(adoptedProtocol)
112 {{- end }}
113
114 {{- range .RequiredInstanceProperties }}
115 p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, true, true)
116 {{- end }}
117
118
119 {{- range .RequiredClassProperties }}
120 p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, true, false)
121 {{- end }}
122
123 {{- range .OptionalInstanceProperties }}
124 p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, false, true)
125 {{- end }}
126
127 {{- range .OptionalClassProperties }}
128 p.AddProperty("{{ .Name }}", {{ attributeToStructString . }}, false, false)
129 {{- end }}
130 p.Register()
131 } // Finished protocol: {{$protocolName}}
132 {{- end }}
133}
134`
135
136func printProtocols(impls []ProtocolImpl, w io.Writer) error {
137 tmpl, err := template.New("protocol.tmpl").Funcs(template.FuncMap{
138 "attributeToStructString": attributeToStructString,
139 }).Parse(templ)
140 if err != nil {
141 return err
142 }
143 err = tmpl.Execute(w, impls)
144 if err != nil {
145 return err
146 }
147 return nil
148}
149
150func attributeToStructString(p objc.Property) string {
151 attribs := strings.Split(p.Attributes(), ",")
152 var b strings.Builder
153 b.WriteString("[]objc.PropertyAttribute{")
154 for i, attrib := range attribs {
155 b.WriteString(fmt.Sprintf(`{Name: &[]byte("%s\x00")[0], Value: &[]byte(%s)[0]}`, string(attrib[0]), strconv.Quote(attrib[1:]+"\x00")))
156 if i != len(attribs)-1 {
157 b.WriteString(", ")
158 }
159 }
160 b.WriteString("}")
161 return b.String()
162}
163
164// This program prints out the Go code to generate the OBJC protocols list.
165// To use it add the @protocol(name) to the C import section and then add it to the protocols list.
166// You'll want to run it as amd64 and arm64 since the types are slightly different for each architecture.
167func main() {
168 var imps []ProtocolImpl
169 var err error
170 if imps, err = readProtocols(protocols); err != nil {
171 panic(err)
172 }
173 if err = printProtocols(imps, os.Stdout); err != nil {
174 panic(err)
175 }
176}