1// Package gen generates the deliberately bounded libxkbcommon ABI subset.
2package gen
3
4import (
5 "fmt"
6 "go/format"
7 "os"
8 "regexp"
9 "strconv"
10 "strings"
11)
12
13var comments = regexp.MustCompile(`(?s)/\*.*?\*/|//[^\n]*`)
14
15func clean(s string) string { return comments.ReplaceAllString(s, "") }
16func declared(h, name string) bool {
17 re := regexp.MustCompile(`(?m)\b` + regexp.QuoteMeta(name) + `\s*\([^;{}]*\)\s*;`)
18 return re.MatchString(clean(h))
19}
20
21var def = regexp.MustCompile(`(?m)^\s*#define\s+(XKB_(?:KEY_|MOD_NAME_|VMOD_NAME_|LED_NAME_)[A-Za-z0-9_]+)\s+("[^"]*"|0x[0-9a-fA-F]+|[0-9]+)\b?`)
22var en = regexp.MustCompile(`(?s)enum\s+xkb_[a-z_]+\s*\{([^}]*)\}`)
23var item = regexp.MustCompile(`^\s*(XKB_[A-Z][A-Z0-9_]*)(?:\s*=\s*(.+))?$`)
24
25func constants(h string) string {
26 h = clean(h)
27 var b strings.Builder
28 for _, e := range en.FindAllStringSubmatch(h, -1) {
29 last := int64(-1)
30 for _, part := range strings.Split(e[1], ",") {
31 m := item.FindStringSubmatch(strings.TrimSpace(part))
32 if m == nil {
33 continue
34 }
35 expr := strings.TrimSpace(m[2])
36 if expr == "" {
37 last++
38 expr = strconv.FormatInt(last, 10)
39 } else if n, err := strconv.ParseInt(expr, 0, 64); err == nil {
40 last = n
41 } else if v := regexp.MustCompile(`^\(1 << (\d+)\)$`).FindStringSubmatch(expr); v != nil {
42 shift, _ := strconv.Atoi(v[1])
43 last = 1 << shift
44 } else {
45 continue
46 }
47 fmt.Fprintf(&b, "%s = %s\n", m[1], expr)
48 }
49 }
50 for _, m := range def.FindAllStringSubmatch(h, -1) {
51 fmt.Fprintf(&b, "%s = %s\n", m[1], m[2])
52 }
53 return b.String()
54}
55
56// Functions describes the bound ABI; each name is checked against the pinned headers.
57// Pointers are uintptr to avoid GC passing Go pointers to native code unexpectedly.
58var Functions = []struct{ Name, Signature string }{
59 {"xkb_context_new", "func(uint32) uintptr"}, {"xkb_context_ref", "func(uintptr) uintptr"}, {"xkb_context_unref", "func(uintptr)"},
60 {"xkb_keymap_new_from_names", "func(uintptr, uintptr, uint32) uintptr"}, {"xkb_keymap_new_from_string", "func(uintptr, uintptr, uint32, uint32) uintptr"}, {"xkb_keymap_new_from_buffer", "func(uintptr, uintptr, uintptr, uint32, uint32) uintptr"},
61 {"xkb_keymap_ref", "func(uintptr) uintptr"}, {"xkb_keymap_unref", "func(uintptr)"}, {"xkb_keymap_get_as_string", "func(uintptr,uint32) uintptr"},
62 {"xkb_keymap_mod_get_index", "func(uintptr,uintptr) uint32"}, {"xkb_keymap_num_layouts", "func(uintptr) uint32"},
63 {"xkb_state_new", "func(uintptr) uintptr"}, {"xkb_state_ref", "func(uintptr) uintptr"}, {"xkb_state_unref", "func(uintptr)"},
64 {"xkb_state_update_mask", "func(uintptr,uint32,uint32,uint32,uint32,uint32,uint32) uint32"},
65 {"xkb_state_serialize_layout", "func(uintptr,uint32) uint32"}, {"xkb_state_serialize_mods", "func(uintptr,uint32) uint32"},
66 {"xkb_state_key_get_one_sym", "func(uintptr,uint32) uint32"}, {"xkb_state_key_get_utf8", "func(uintptr,uint32,uintptr,uintptr) int32"},
67 {"xkb_keysym_get_name", "func(uint32,uintptr,uintptr) int32"}, {"xkb_keysym_from_name", "func(uintptr,uint32) uint32"},
68 {"xkb_compose_table_new_from_locale", "func(uintptr,uintptr,uint32) uintptr"}, {"xkb_compose_table_ref", "func(uintptr) uintptr"}, {"xkb_compose_table_unref", "func(uintptr)"},
69 {"xkb_compose_state_new", "func(uintptr,uint32) uintptr"}, {"xkb_compose_state_ref", "func(uintptr) uintptr"}, {"xkb_compose_state_unref", "func(uintptr)"},
70 {"xkb_compose_state_feed", "func(uintptr,uint32) uint32"}, {"xkb_compose_state_get_status", "func(uintptr) uint32"},
71 {"xkb_compose_state_reset", "func(uintptr)"}, {"xkb_compose_state_get_utf8", "func(uintptr,uintptr,uintptr) int32"}, {"xkb_compose_state_get_one_sym", "func(uintptr) uint32"},
72}
73
74func field(name string) string { return strings.ToUpper(name[:1]) + name[1:] }
75func Generate(root string) error {
76 h1, err := os.ReadFile(root + "/upstream/xkbcommon.h")
77 if err != nil {
78 return err
79 }
80 h2, err := os.ReadFile(root + "/upstream/xkbcommon-compose.h")
81 if err != nil {
82 return err
83 }
84 hdr := string(h1) + "\n" + string(h2)
85 var b strings.Builder
86 b.WriteString("// Code generated by go generate; DO NOT EDIT.\npackage raw\n\n")
87 b.WriteString("// Opaque C handles and scalar typedefs.\ntype Context uintptr\ntype Keymap uintptr\ntype State uintptr\ntype ComposeTable uintptr\ntype ComposeState uintptr\ntype Keycode uint32\ntype Keysym uint32\ntype LayoutIndex uint32\ntype ModIndex uint32\ntype ModMask uint32\ntype LayoutMask uint32\n")
88 b.WriteString("// RuleNames matches struct xkb_rule_names (five const char pointers).\ntype RuleNames struct { Rules, Model, Layout, Variant, Options uintptr }\n")
89 b.WriteString("const (\n" + constants(hdr))
90 for _, f := range []string{"xkbcommon-keysyms.h", "xkbcommon-names.h"} {
91 data, e := os.ReadFile(root + "/upstream/" + f)
92 if e != nil {
93 return e
94 }
95 b.WriteString(constants(string(data)))
96 }
97 b.WriteString(")\n\n// Funcs holds resolved library functions.\ntype Funcs struct {\n")
98 for _, f := range Functions {
99 if !declared(hdr, f.Name) {
100 return fmt.Errorf("missing pinned declaration: %s", f.Name)
101 }
102 fmt.Fprintf(&b, "%s %s\n", field(f.Name), f.Signature)
103 }
104 b.WriteString("}\n\n// Symbols is the complete required symbol set.\nvar Symbols = []string{\n")
105 for _, f := range Functions {
106 fmt.Fprintf(&b, "%q,\n", f.Name)
107 }
108 b.WriteString("}\n\n// Register registers prevalidated addresses in a Funcs value.\nfunc Register(f *Funcs, register func(any,uintptr)) {\n")
109 for _, f := range Functions {
110 fmt.Fprintf(&b, "register(&f.%s, addresses[%q])\n", field(f.Name), f.Name)
111 }
112 b.WriteString("}\n")
113 // address map is passed in, not a global mutable singleton.
114 s := strings.Replace(b.String(), "func Register(f *Funcs, register func(any,uintptr))", "func Register(f *Funcs, addresses map[string]uintptr, register func(any,uintptr))", 1)
115 formatted, err := format.Source([]byte(s))
116 if err != nil {
117 return fmt.Errorf("format generated file: %w", err)
118 }
119 return os.WriteFile(root+"/raw/generated.go", formatted, 0644)
120}