detached 9e67979

feat(bindings): generate xkbcommon ABI and implement safe keyboard bindings

commit
9e679793fbcb1259aab513fda45a913edaf43536
author
brice · · 2026-09-28 07:01 UTC
parent
a7f62f3
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
19 files · +11021 -1
A LICENSE +21 -0
@@ -0,0 +1,21 @@
1+MIT License
2+
3+Copyright (c) 2026 purego-xkbcommon contributors
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
@@ -0,0 +1,21 @@
1+MIT License
2+
3+Copyright (c) 2026 purego-xkbcommon contributors
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
M README.md +18 -1
@@ -1,3 +1,20 @@
11 # purego-xkbcommon
22
3-Pure Go (no cgo) bindings for libxkbcommon, loaded at runtime with purego.
3+Go 1.27 bindings for Linux `libxkbcommon.so.0`, without cgo or X11. The safe package owns native references; the `raw` package exposes generated ABI constants, types, and function registrations. Install libxkbcommon at runtime and xkeyboard-config for rules-based keymaps. The loader reports missing libraries and required symbols via `Available`.
4+
5+```go
6+ctx, err := xkbcommon.NewContext()
7+if err != nil { return err }
8+defer ctx.Close()
9+keymap, err := ctx.NewKeymapRules("evdev", "pc105", "fr", "", "")
10+if err != nil { return err }
11+defer keymap.Close()
12+state, err := keymap.NewState()
13+if err != nil { return err }
14+defer state.Close()
15+text, err := state.UTF8(xkbcommon.WaylandKeycode(evdevKeycode))
16+```
17+
18+Use `NewKeymapFD(fd, size)` for Wayland `xkb_v1` keymaps; it maps the supplied size with `PROT_READ|MAP_PRIVATE` and requires a trailing NUL. **The caller retains and must close the FD**, on success and failure. `UpdateMask` accepts Wayland modifier masks and group indices. `ComposeState.Feed` and `Status` distinguish composing, composed, cancelled, and ignored keysyms; use `UTF8` only for a composed result. Objects retain their native parents; close each object, in any order. Do not copy value structs containing a mutex.
19+
20+Pinned upstream headers and licenses are in `upstream/`; `upstream/METADATA` and `SHA256SUMS` identify their exact source. Run `GOWORK=off go generate ./...` to regenerate `raw/generated.go`; consumers do not run generation. The generator checks the selected ABI declarations against the headers. `github.com/bnema/purego v0.13.0-bnema.1` matches purego-vulkan and supports `Dlopen`, `Dlsym`, and `RegisterFunc` with cgo disabled.
@@ -1,3 +1,20 @@
1 # purego-xkbcommon1 # purego-xkbcommon
2 2
3-Pure Go (no cgo) bindings for libxkbcommon, loaded at runtime with purego.3+Go 1.27 bindings for Linux `libxkbcommon.so.0`, without cgo or X11. The safe package owns native references; the `raw` package exposes generated ABI constants, types, and function registrations. Install libxkbcommon at runtime and xkeyboard-config for rules-based keymaps. The loader reports missing libraries and required symbols via `Available`.
4+
5+```go
6+ctx, err := xkbcommon.NewContext()
7+if err != nil { return err }
8+defer ctx.Close()
9+keymap, err := ctx.NewKeymapRules("evdev", "pc105", "fr", "", "")
10+if err != nil { return err }
11+defer keymap.Close()
12+state, err := keymap.NewState()
13+if err != nil { return err }
14+defer state.Close()
15+text, err := state.UTF8(xkbcommon.WaylandKeycode(evdevKeycode))
16+```
17+
18+Use `NewKeymapFD(fd, size)` for Wayland `xkb_v1` keymaps; it maps the supplied size with `PROT_READ|MAP_PRIVATE` and requires a trailing NUL. **The caller retains and must close the FD**, on success and failure. `UpdateMask` accepts Wayland modifier masks and group indices. `ComposeState.Feed` and `Status` distinguish composing, composed, cancelled, and ignored keysyms; use `UTF8` only for a composed result. Objects retain their native parents; close each object, in any order. Do not copy value structs containing a mutex.
19+
20+Pinned upstream headers and licenses are in `upstream/`; `upstream/METADATA` and `SHA256SUMS` identify their exact source. Run `GOWORK=off go generate ./...` to regenerate `raw/generated.go`; consumers do not run generation. The generator checks the selected ABI declarations against the headers. `github.com/bnema/purego v0.13.0-bnema.1` matches purego-vulkan and supports `Dlopen`, `Dlsym`, and `RegisterFunc` with cgo disabled.
A cmd/xkbgen/main.go +12 -0
@@ -0,0 +1,12 @@
1+package main
2+
3+import (
4+ "github.com/bnema/purego-xkbcommon/internal/gen"
5+ "log"
6+)
7+
8+func main() {
9+ if err := gen.Generate("."); err != nil {
10+ log.Fatal(err)
11+ }
12+}
@@ -0,0 +1,12 @@
1+package main
2+
3+import (
4+ "github.com/bnema/purego-xkbcommon/internal/gen"
5+ "log"
6+)
7+
8+func main() {
9+ if err := gen.Generate("."); err != nil {
10+ log.Fatal(err)
11+ }
12+}
A generate.go +3 -0
@@ -0,0 +1,3 @@
1+package xkbcommon
2+
3+//go:generate go run ./cmd/xkbgen
@@ -0,0 +1,3 @@
1+package xkbcommon
2+
3+//go:generate go run ./cmd/xkbgen
A go.mod +5 -0
@@ -0,0 +1,5 @@
1+module github.com/bnema/purego-xkbcommon
2+
3+go 1.27
4+
5+require github.com/bnema/purego v0.13.0-bnema.1
@@ -0,0 +1,5 @@
1+module github.com/bnema/purego-xkbcommon
2+
3+go 1.27
4+
5+require github.com/bnema/purego v0.13.0-bnema.1
A go.sum +2 -0 generated file: collapsed
A internal/gen/gen.go +120 -0
@@ -0,0 +1,120 @@
1+// Package gen generates the deliberately bounded libxkbcommon ABI subset.
2+package gen
3+
4+import (
5+ "fmt"
6+ "go/format"
7+ "os"
8+ "regexp"
9+ "strconv"
10+ "strings"
11+)
12+
13+var comments = regexp.MustCompile(`(?s)/\*.*?\*/|//[^\n]*`)
14+
15+func clean(s string) string { return comments.ReplaceAllString(s, "") }
16+func declared(h, name string) bool {
17+ re := regexp.MustCompile(`(?m)\b` + regexp.QuoteMeta(name) + `\s*\([^;{}]*\)\s*;`)
18+ return re.MatchString(clean(h))
19+}
20+
21+var 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?`)
22+var en = regexp.MustCompile(`(?s)enum\s+xkb_[a-z_]+\s*\{([^}]*)\}`)
23+var item = regexp.MustCompile(`^\s*(XKB_[A-Z][A-Z0-9_]*)(?:\s*=\s*(.+))?$`)
24+
25+func 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.
58+var 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+
74+func field(name string) string { return strings.ToUpper(name[:1]) + name[1:] }
75+func 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+}
@@ -0,0 +1,120 @@
1+// Package gen generates the deliberately bounded libxkbcommon ABI subset.
2+package gen
3+
4+import (
5+ "fmt"
6+ "go/format"
7+ "os"
8+ "regexp"
9+ "strconv"
10+ "strings"
11+)
12+
13+var comments = regexp.MustCompile(`(?s)/\*.*?\*/|//[^\n]*`)
14+
15+func clean(s string) string { return comments.ReplaceAllString(s, "") }
16+func declared(h, name string) bool {
17+ re := regexp.MustCompile(`(?m)\b` + regexp.QuoteMeta(name) + `\s*\([^;{}]*\)\s*;`)
18+ return re.MatchString(clean(h))
19+}
20+
21+var 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?`)
22+var en = regexp.MustCompile(`(?s)enum\s+xkb_[a-z_]+\s*\{([^}]*)\}`)
23+var item = regexp.MustCompile(`^\s*(XKB_[A-Z][A-Z0-9_]*)(?:\s*=\s*(.+))?$`)
24+
25+func 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.
58+var 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+
74+func field(name string) string { return strings.ToUpper(name[:1]) + name[1:] }
75+func 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+}
A internal/gen/gen_test.go +25 -0
@@ -0,0 +1,25 @@
1+package gen
2+
3+import (
4+ "strings"
5+ "testing"
6+)
7+
8+func TestDeclarations(t *testing.T) {
9+ h := "/* xkb_fake(void); */\nXKB_EXPORT struct xkb_context *\nxkb_context_new(enum xkb_context_flags flags);"
10+ if !declared(h, "xkb_context_new") || declared(h, "xkb_fake") {
11+ t.Fatal("incorrect declaration selection")
12+ }
13+ if declared(h, "xkb_context_unref") {
14+ t.Fatal("missing declaration accepted")
15+ }
16+}
17+func TestConstants(t *testing.T) {
18+ h := "enum xkb_test { XKB_A = (1 << 2), XKB_B, XKB_C = 0x10 };\n#define XKB_MOD_NAME_SHIFT \"Shift\"\n#define XKB_KEY_dead_acute 0xfe51\n"
19+ got := constants(h)
20+ for _, want := range []string{"XKB_A = (1 << 2)", "XKB_B = 5", "XKB_C = 0x10", "XKB_MOD_NAME_SHIFT = \"Shift\"", "XKB_KEY_dead_acute = 0xfe51"} {
21+ if !strings.Contains(got, want) {
22+ t.Fatalf("missing %s in %s", want, got)
23+ }
24+ }
25+}
@@ -0,0 +1,25 @@
1+package gen
2+
3+import (
4+ "strings"
5+ "testing"
6+)
7+
8+func TestDeclarations(t *testing.T) {
9+ h := "/* xkb_fake(void); */\nXKB_EXPORT struct xkb_context *\nxkb_context_new(enum xkb_context_flags flags);"
10+ if !declared(h, "xkb_context_new") || declared(h, "xkb_fake") {
11+ t.Fatal("incorrect declaration selection")
12+ }
13+ if declared(h, "xkb_context_unref") {
14+ t.Fatal("missing declaration accepted")
15+ }
16+}
17+func TestConstants(t *testing.T) {
18+ h := "enum xkb_test { XKB_A = (1 << 2), XKB_B, XKB_C = 0x10 };\n#define XKB_MOD_NAME_SHIFT \"Shift\"\n#define XKB_KEY_dead_acute 0xfe51\n"
19+ got := constants(h)
20+ for _, want := range []string{"XKB_A = (1 << 2)", "XKB_B = 5", "XKB_C = 0x10", "XKB_MOD_NAME_SHIFT = \"Shift\"", "XKB_KEY_dead_acute = 0xfe51"} {
21+ if !strings.Contains(got, want) {
22+ t.Fatalf("missing %s in %s", want, got)
23+ }
24+ }
25+}
A loader.go +41 -0
@@ -0,0 +1,41 @@
1+package xkbcommon
2+
3+import (
4+ "fmt"
5+ "sync"
6+
7+ "github.com/bnema/purego"
8+ "github.com/bnema/purego-xkbcommon/raw"
9+)
10+
11+var loader struct {
12+ sync.Once
13+ f raw.Funcs
14+ err error
15+}
16+
17+// Available resolves the SONAME and every required symbol, reporting the first
18+// incompatible symbol rather than failing later in a keyboard event callback.
19+func Available() error {
20+ loader.Do(func() {
21+ lib, err := purego.Dlopen("libxkbcommon.so.0", purego.RTLD_NOW|purego.RTLD_LOCAL)
22+ if err != nil {
23+ loader.err = fmt.Errorf("xkbcommon: load libxkbcommon.so.0: %w", err)
24+ return
25+ }
26+ addresses := make(map[string]uintptr, len(raw.Symbols))
27+ for _, name := range raw.Symbols {
28+ address, e := purego.Dlsym(lib, name)
29+ if e != nil || address == 0 {
30+ loader.err = fmt.Errorf("xkbcommon: incompatible libxkbcommon.so.0: missing %s: %v", name, e)
31+ _ = purego.Dlclose(lib)
32+ return
33+ }
34+ addresses[name] = address
35+ }
36+ raw.Register(&loader.f, addresses, purego.RegisterFunc)
37+ // Intentionally keep the library open: registered function pointers and live
38+ // native objects remain valid for the process lifetime.
39+ })
40+ return loader.err
41+}
@@ -0,0 +1,41 @@
1+package xkbcommon
2+
3+import (
4+ "fmt"
5+ "sync"
6+
7+ "github.com/bnema/purego"
8+ "github.com/bnema/purego-xkbcommon/raw"
9+)
10+
11+var loader struct {
12+ sync.Once
13+ f raw.Funcs
14+ err error
15+}
16+
17+// Available resolves the SONAME and every required symbol, reporting the first
18+// incompatible symbol rather than failing later in a keyboard event callback.
19+func Available() error {
20+ loader.Do(func() {
21+ lib, err := purego.Dlopen("libxkbcommon.so.0", purego.RTLD_NOW|purego.RTLD_LOCAL)
22+ if err != nil {
23+ loader.err = fmt.Errorf("xkbcommon: load libxkbcommon.so.0: %w", err)
24+ return
25+ }
26+ addresses := make(map[string]uintptr, len(raw.Symbols))
27+ for _, name := range raw.Symbols {
28+ address, e := purego.Dlsym(lib, name)
29+ if e != nil || address == 0 {
30+ loader.err = fmt.Errorf("xkbcommon: incompatible libxkbcommon.so.0: missing %s: %v", name, e)
31+ _ = purego.Dlclose(lib)
32+ return
33+ }
34+ addresses[name] = address
35+ }
36+ raw.Register(&loader.f, addresses, purego.RegisterFunc)
37+ // Intentionally keep the library open: registered function pointers and live
38+ // native objects remain valid for the process lifetime.
39+ })
40+ return loader.err
41+}
A raw/generated.go +2834 -0 large diff (2834 lines changed): collapsed
A upstream/LICENSE +279 -0
@@ -0,0 +1,279 @@
1+The following is a list of all copyright notices and license statements which
2+appear in the xkbcommon source tree, along with the SPDX short license
3+identifiers which source files use to refer to them. To see which parts are
4+covered by which license and copyright, refer to the source files.
5+
6+If making new contributions, the MIT License is vastly preferred.
7+
8+All licenses are derivative of the MIT/X11 license, mostly identical other
9+than no-endorsement clauses (e.g. paragraph 4 of The Open Group's license).
10+
11+These statements are split into two sections: one for the code compiled and
12+distributed as part of the libxkbcommon shared library and the code component
13+of all tests, and another only for the test data under test/data, which is
14+distributed with the xkbcommon source code, but not installed to the system.
15+
16+BEGINNING OF SOFTWARE COPYRIGHT/LICENSE STATEMENTS:
17+
18+
19+-------------------------------------------------------------------------------
20+SPDX full name: MIT License
21+SPDX short identifier: MIT
22+License text:
23+
24+Copyright © 2008-2009 Dan Nicholson <dbn.lists@gmail.com>
25+Copyright © 2009-2012, 2016 Daniel Stone <daniel@fooishbar.org>
26+Copyright © 2012-2014, 2020-2022, 2025 Ran Benita <ran@unusedvar.com>
27+Copyright © 2010-2012, 2016 Intel Corporation
28+Copyright © 2013-2015, 2018-2020 Red Hat, Inc.
29+Copyright © 2023-2024 Pierre Le Marre <dev@wismill.eu>
30+Copyright (c) 1991, Oracle and/or its affiliates.
31+Copyright © 2008-2011 Kristian Høgsberg
32+Copyright © 2010 Francisco Jerez <currojerez@riseup.net>
33+Copyright © 2012 Collabora, Ltd.
34+Copyright © 2015 Kazunobu Kuriyama <kazunobu.kuriyama@nifty.com>
35+Copyright (C) 2011 Joseph Adams <joeyadams3.14159@gmail.com>
36+
37+Permission is hereby granted, free of charge, to any person obtaining a
38+copy of this software and associated documentation files (the "Software"),
39+to deal in the Software without restriction, including without limitation
40+the rights to use, copy, modify, merge, publish, distribute, sublicense,
41+and/or sell copies of the Software, and to permit persons to whom the
42+Software is furnished to do so, subject to the following conditions:
43+
44+The above copyright notice and this permission notice (including the next
45+paragraph) shall be included in all copies or substantial portions of the
46+Software.
47+
48+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
51+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
53+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
54+DEALINGS IN THE SOFTWARE.
55+
56+
57+-------------------------------------------------------------------------------
58+SPDX full name: MIT Open Group variant
59+SPDX short identifier: MIT-open-group
60+License text:
61+
62+Copyright 1985, 1987, 1988, 1990, 1998 The Open Group
63+
64+Permission is hereby granted, free of charge, to any person obtaining a
65+copy of this software and associated documentation files (the "Software"),
66+to deal in the Software without restriction, including without limitation
67+the rights to use, copy, modify, merge, publish, distribute, sublicense,
68+and/or sell copies of the Software, and to permit persons to whom the
69+Software is furnished to do so, subject to the following conditions:
70+
71+The above copyright notice and this permission notice shall be included in
72+all copies or substantial portions of the Software.
73+
74+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
75+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
76+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
77+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
78+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
79+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
80+
81+Except as contained in this notice, the names of the authors or their
82+institutions shall not be used in advertising or otherwise to promote the
83+sale, use or other dealings in this Software without prior written
84+authorization from the authors.
85+
86+
87+-------------------------------------------------------------------------------
88+SPDX full name: Historical Permission Notice and Disclaimer
89+SPDX short identifier: HPND
90+License text:
91+
92+Copyright (c) 1993, 1994, 1995, 1996 by Silicon Graphics Computer Systems, Inc.
93+
94+Permission to use, copy, modify, and distribute this
95+software and its documentation for any purpose and without
96+fee is hereby granted, provided that the above copyright
97+notice appear in all copies and that both that copyright
98+notice and this permission notice appear in supporting
99+documentation, and that the name of Silicon Graphics not be
100+used in advertising or publicity pertaining to distribution
101+of the software without specific prior written permission.
102+Silicon Graphics makes no representation about the suitability
103+of this software for any purpose. It is provided "as is"
104+without any express or implied warranty.
105+
106+SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
107+SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
108+AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
109+GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
110+DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
111+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
112+OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH
113+THE USE OR PERFORMANCE OF THIS SOFTWARE.
114+
115+-------------------------------------------------------------------------------
116+SPDX full name: Historical Permission Notice and Disclaimer - sell variant
117+SPDX short identifier: HPND-sell-variant
118+License text:
119+
120+Copyright 1992 by Oki Technosystems Laboratory, Inc.
121+Copyright 1992 by Fuji Xerox Co., Ltd.
122+
123+Permission to use, copy, modify, distribute, and sell this software
124+and its documentation for any purpose is hereby granted without fee,
125+provided that the above copyright notice appear in all copies and
126+that both that copyright notice and this permission notice appear
127+in supporting documentation, and that the name of Oki Technosystems
128+Laboratory and Fuji Xerox not be used in advertising or publicity
129+pertaining to distribution of the software without specific, written
130+prior permission.
131+Oki Technosystems Laboratory and Fuji Xerox make no representations
132+about the suitability of this software for any purpose. It is provided
133+"as is" without express or implied warranty.
134+
135+OKI TECHNOSYSTEMS LABORATORY AND FUJI XEROX DISCLAIM ALL WARRANTIES
136+WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
137+MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL OKI TECHNOSYSTEMS
138+LABORATORY AND FUJI XEROX BE LIABLE FOR ANY SPECIAL, INDIRECT OR
139+CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
140+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
141+OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
142+OR PERFORMANCE OF THIS SOFTWARE.
143+
144+
145+-------------------------------------------------------------------------------
146+SPDX full name: <none>
147+SPDX short identifier: LicenseRef-digital-equipment-corporation
148+License text:
149+
150+Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts.
151+
152+ All Rights Reserved
153+
154+Permission to use, copy, modify, and distribute this software and its
155+documentation for any purpose and without fee is hereby granted,
156+provided that the above copyright notice appear in all copies and that
157+both that copyright notice and this permission notice appear in
158+supporting documentation, and that the name of Digital not be
159+used in advertising or publicity pertaining to distribution of the
160+software without specific, written prior permission.
161+
162+DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
163+ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
164+DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
165+ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
166+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
167+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
168+SOFTWARE.
169+
170+
171+-------------------------------------------------------------------------------
172+
173+
174+
175+END OF SOFTWARE COPYRIGHT/LICENSE STATEMENTS
176+
177+
178+BEGINNING OF LICENSE STATEMENTS ONLY FOR UNDISTRIBUTED DATA FILES IN test/data,
179+derived from xkeyboard-config and libX11 nls/ directory:
180+
181+
182+
183+-------------------------------------------------------------------------------
184+SPDX full name: Historical Permission Notice and Disclaimer - sell variant
185+SPDX short identifier: HPND-sell-variant
186+
187+Copyright 1996 by Joseph Moss
188+Copyright (C) 2002-2007 Free Software Foundation, Inc.
189+Copyright (C) Dmitry Golubev <lastguru@mail.ru>, 2003-2004
190+Copyright (C) 2004, Gregory Mokhin <mokhin@bog.msu.ru>
191+Copyright (C) 2006 Erdal Ronahî
192+
193+Permission to use, copy, modify, distribute, and sell this software and its
194+documentation for any purpose is hereby granted without fee, provided that
195+the above copyright notice appear in all copies and that both that
196+copyright notice and this permission notice appear in supporting
197+documentation, and that the name of the copyright holder(s) not be used in
198+advertising or publicity pertaining to distribution of the software without
199+specific, written prior permission. The copyright holder(s) makes no
200+representations about the suitability of this software for any purpose. It
201+is provided "as is" without express or implied warranty.
202+
203+THE COPYRIGHT HOLDER(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
204+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
205+EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
206+CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
207+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
208+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
209+PERFORMANCE OF THIS SOFTWARE.
210+
211+
212+-------------------------------------------------------------------------------
213+SPDX full name: MIT License
214+SPDX short identifier: MIT
215+
216+Copyright 2004-2005 Sun Microsystems, Inc. All rights reserved.
217+
218+
219+-------------------------------------------------------------------------------
220+SPDX full name: X11 License
221+SPDX short identifier: X11
222+License text:
223+
224+Copyright (c) 1996 X Consortium
225+
226+Permission is hereby granted, free of charge, to any person obtaining
227+a copy of this software and associated documentation files (the
228+"Software"), to deal in the Software without restriction, including
229+without limitation the rights to use, copy, modify, merge, publish,
230+distribute, sublicense, and/or sell copies of the Software, and to
231+permit persons to whom the Software is furnished to do so, subject to
232+the following conditions:
233+
234+The above copyright notice and this permission notice shall be
235+included in all copies or substantial portions of the Software.
236+
237+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
238+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
239+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
240+IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
241+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
242+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
243+OTHER DEALINGS IN THE SOFTWARE.
244+
245+Except as contained in this notice, the name of the X Consortium shall
246+not be used in advertising or otherwise to promote the sale, use or
247+other dealings in this Software without prior written authorization
248+from the X Consortium.
249+
250+
251+
252+-------------------------------------------------------------------------------
253+SPDX full name: MIT Open Group variant
254+SPDX short identifier: MIT-open-group
255+
256+Copyright (C) 2004, 2006 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
257+
258+
259+-------------------------------------------------------------------------------
260+SPDX full name: <none>
261+SPDX short identifier: MIT-open-group
262+SPDX short identifier: LicenseRef-anton-zinoviev
263+License text:
264+
265+Copyright (C) 1999, 2000 by Anton Zinoviev <anton@lml.bas.bg>
266+
267+This software may be used, modified, copied, distributed, and sold,
268+in both source and binary form provided that the above copyright
269+and these terms are retained. Under no circumstances is the author
270+responsible for the proper functioning of this software, nor does
271+the author assume any responsibility for damages incurred with its
272+use.
273+
274+Permission is granted to anyone to use, distribute and modify
275+this file in any way, provided that the above copyright notice
276+is left intact and the author of the modification summarizes
277+the changes in this header.
278+
279+This file is distributed without any expressed or implied warranty.
@@ -0,0 +1,279 @@
1+The following is a list of all copyright notices and license statements which
2+appear in the xkbcommon source tree, along with the SPDX short license
3+identifiers which source files use to refer to them. To see which parts are
4+covered by which license and copyright, refer to the source files.
5+
6+If making new contributions, the MIT License is vastly preferred.
7+
8+All licenses are derivative of the MIT/X11 license, mostly identical other
9+than no-endorsement clauses (e.g. paragraph 4 of The Open Group's license).
10+
11+These statements are split into two sections: one for the code compiled and
12+distributed as part of the libxkbcommon shared library and the code component
13+of all tests, and another only for the test data under test/data, which is
14+distributed with the xkbcommon source code, but not installed to the system.
15+
16+BEGINNING OF SOFTWARE COPYRIGHT/LICENSE STATEMENTS:
17+
18+
19+-------------------------------------------------------------------------------
20+SPDX full name: MIT License
21+SPDX short identifier: MIT
22+License text:
23+
24+Copyright © 2008-2009 Dan Nicholson <dbn.lists@gmail.com>
25+Copyright © 2009-2012, 2016 Daniel Stone <daniel@fooishbar.org>
26+Copyright © 2012-2014, 2020-2022, 2025 Ran Benita <ran@unusedvar.com>
27+Copyright © 2010-2012, 2016 Intel Corporation
28+Copyright © 2013-2015, 2018-2020 Red Hat, Inc.
29+Copyright © 2023-2024 Pierre Le Marre <dev@wismill.eu>
30+Copyright (c) 1991, Oracle and/or its affiliates.
31+Copyright © 2008-2011 Kristian Høgsberg
32+Copyright © 2010 Francisco Jerez <currojerez@riseup.net>
33+Copyright © 2012 Collabora, Ltd.
34+Copyright © 2015 Kazunobu Kuriyama <kazunobu.kuriyama@nifty.com>
35+Copyright (C) 2011 Joseph Adams <joeyadams3.14159@gmail.com>
36+
37+Permission is hereby granted, free of charge, to any person obtaining a
38+copy of this software and associated documentation files (the "Software"),
39+to deal in the Software without restriction, including without limitation
40+the rights to use, copy, modify, merge, publish, distribute, sublicense,
41+and/or sell copies of the Software, and to permit persons to whom the
42+Software is furnished to do so, subject to the following conditions:
43+
44+The above copyright notice and this permission notice (including the next
45+paragraph) shall be included in all copies or substantial portions of the
46+Software.
47+
48+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
51+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
53+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
54+DEALINGS IN THE SOFTWARE.
55+
56+
57+-------------------------------------------------------------------------------
58+SPDX full name: MIT Open Group variant
59+SPDX short identifier: MIT-open-group
60+License text:
61+
62+Copyright 1985, 1987, 1988, 1990, 1998 The Open Group
63+
64+Permission is hereby granted, free of charge, to any person obtaining a
65+copy of this software and associated documentation files (the "Software"),
66+to deal in the Software without restriction, including without limitation
67+the rights to use, copy, modify, merge, publish, distribute, sublicense,
68+and/or sell copies of the Software, and to permit persons to whom the
69+Software is furnished to do so, subject to the following conditions:
70+
71+The above copyright notice and this permission notice shall be included in
72+all copies or substantial portions of the Software.
73+
74+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
75+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
76+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
77+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
78+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
79+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
80+
81+Except as contained in this notice, the names of the authors or their
82+institutions shall not be used in advertising or otherwise to promote the
83+sale, use or other dealings in this Software without prior written
84+authorization from the authors.
85+
86+
87+-------------------------------------------------------------------------------
88+SPDX full name: Historical Permission Notice and Disclaimer
89+SPDX short identifier: HPND
90+License text:
91+
92+Copyright (c) 1993, 1994, 1995, 1996 by Silicon Graphics Computer Systems, Inc.
93+
94+Permission to use, copy, modify, and distribute this
95+software and its documentation for any purpose and without
96+fee is hereby granted, provided that the above copyright
97+notice appear in all copies and that both that copyright
98+notice and this permission notice appear in supporting
99+documentation, and that the name of Silicon Graphics not be
100+used in advertising or publicity pertaining to distribution
101+of the software without specific prior written permission.
102+Silicon Graphics makes no representation about the suitability
103+of this software for any purpose. It is provided "as is"
104+without any express or implied warranty.
105+
106+SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
107+SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
108+AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
109+GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
110+DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
111+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
112+OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH
113+THE USE OR PERFORMANCE OF THIS SOFTWARE.
114+
115+-------------------------------------------------------------------------------
116+SPDX full name: Historical Permission Notice and Disclaimer - sell variant
117+SPDX short identifier: HPND-sell-variant
118+License text:
119+
120+Copyright 1992 by Oki Technosystems Laboratory, Inc.
121+Copyright 1992 by Fuji Xerox Co., Ltd.
122+
123+Permission to use, copy, modify, distribute, and sell this software
124+and its documentation for any purpose is hereby granted without fee,
125+provided that the above copyright notice appear in all copies and
126+that both that copyright notice and this permission notice appear
127+in supporting documentation, and that the name of Oki Technosystems
128+Laboratory and Fuji Xerox not be used in advertising or publicity
129+pertaining to distribution of the software without specific, written
130+prior permission.
131+Oki Technosystems Laboratory and Fuji Xerox make no representations
132+about the suitability of this software for any purpose. It is provided
133+"as is" without express or implied warranty.
134+
135+OKI TECHNOSYSTEMS LABORATORY AND FUJI XEROX DISCLAIM ALL WARRANTIES
136+WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
137+MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL OKI TECHNOSYSTEMS
138+LABORATORY AND FUJI XEROX BE LIABLE FOR ANY SPECIAL, INDIRECT OR
139+CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
140+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
141+OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
142+OR PERFORMANCE OF THIS SOFTWARE.
143+
144+
145+-------------------------------------------------------------------------------
146+SPDX full name: <none>
147+SPDX short identifier: LicenseRef-digital-equipment-corporation
148+License text:
149+
150+Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts.
151+
152+ All Rights Reserved
153+
154+Permission to use, copy, modify, and distribute this software and its
155+documentation for any purpose and without fee is hereby granted,
156+provided that the above copyright notice appear in all copies and that
157+both that copyright notice and this permission notice appear in
158+supporting documentation, and that the name of Digital not be
159+used in advertising or publicity pertaining to distribution of the
160+software without specific, written prior permission.
161+
162+DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
163+ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
164+DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
165+ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
166+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
167+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
168+SOFTWARE.
169+
170+
171+-------------------------------------------------------------------------------
172+
173+
174+
175+END OF SOFTWARE COPYRIGHT/LICENSE STATEMENTS
176+
177+
178+BEGINNING OF LICENSE STATEMENTS ONLY FOR UNDISTRIBUTED DATA FILES IN test/data,
179+derived from xkeyboard-config and libX11 nls/ directory:
180+
181+
182+
183+-------------------------------------------------------------------------------
184+SPDX full name: Historical Permission Notice and Disclaimer - sell variant
185+SPDX short identifier: HPND-sell-variant
186+
187+Copyright 1996 by Joseph Moss
188+Copyright (C) 2002-2007 Free Software Foundation, Inc.
189+Copyright (C) Dmitry Golubev <lastguru@mail.ru>, 2003-2004
190+Copyright (C) 2004, Gregory Mokhin <mokhin@bog.msu.ru>
191+Copyright (C) 2006 Erdal Ronahî
192+
193+Permission to use, copy, modify, distribute, and sell this software and its
194+documentation for any purpose is hereby granted without fee, provided that
195+the above copyright notice appear in all copies and that both that
196+copyright notice and this permission notice appear in supporting
197+documentation, and that the name of the copyright holder(s) not be used in
198+advertising or publicity pertaining to distribution of the software without
199+specific, written prior permission. The copyright holder(s) makes no
200+representations about the suitability of this software for any purpose. It
201+is provided "as is" without express or implied warranty.
202+
203+THE COPYRIGHT HOLDER(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
204+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
205+EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
206+CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
207+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
208+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
209+PERFORMANCE OF THIS SOFTWARE.
210+
211+
212+-------------------------------------------------------------------------------
213+SPDX full name: MIT License
214+SPDX short identifier: MIT
215+
216+Copyright 2004-2005 Sun Microsystems, Inc. All rights reserved.
217+
218+
219+-------------------------------------------------------------------------------
220+SPDX full name: X11 License
221+SPDX short identifier: X11
222+License text:
223+
224+Copyright (c) 1996 X Consortium
225+
226+Permission is hereby granted, free of charge, to any person obtaining
227+a copy of this software and associated documentation files (the
228+"Software"), to deal in the Software without restriction, including
229+without limitation the rights to use, copy, modify, merge, publish,
230+distribute, sublicense, and/or sell copies of the Software, and to
231+permit persons to whom the Software is furnished to do so, subject to
232+the following conditions:
233+
234+The above copyright notice and this permission notice shall be
235+included in all copies or substantial portions of the Software.
236+
237+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
238+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
239+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
240+IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
241+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
242+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
243+OTHER DEALINGS IN THE SOFTWARE.
244+
245+Except as contained in this notice, the name of the X Consortium shall
246+not be used in advertising or otherwise to promote the sale, use or
247+other dealings in this Software without prior written authorization
248+from the X Consortium.
249+
250+
251+
252+-------------------------------------------------------------------------------
253+SPDX full name: MIT Open Group variant
254+SPDX short identifier: MIT-open-group
255+
256+Copyright (C) 2004, 2006 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
257+
258+
259+-------------------------------------------------------------------------------
260+SPDX full name: <none>
261+SPDX short identifier: MIT-open-group
262+SPDX short identifier: LicenseRef-anton-zinoviev
263+License text:
264+
265+Copyright (C) 1999, 2000 by Anton Zinoviev <anton@lml.bas.bg>
266+
267+This software may be used, modified, copied, distributed, and sold,
268+in both source and binary form provided that the above copyright
269+and these terms are retained. Under no circumstances is the author
270+responsible for the proper functioning of this software, nor does
271+the author assume any responsibility for damages incurred with its
272+use.
273+
274+Permission is granted to anyone to use, distribute and modify
275+this file in any way, provided that the above copyright notice
276+is left intact and the author of the modification summarizes
277+the changes in this header.
278+
279+This file is distributed without any expressed or implied warranty.
A upstream/METADATA +6 -0
@@ -0,0 +1,6 @@
1+Source: https://github.com/xkbcommon/libxkbcommon
2+Tag: xkbcommon-1.13.2
3+Commit: d1442aaa6b635551182e83c3b55037f4c118c962
4+Files: include/xkbcommon/{xkbcommon.h,xkbcommon-compose.h,xkbcommon-keysyms.h,xkbcommon-names.h}, LICENSE
5+SHA-256: see SHA256SUMS (paths relative to repository root)
6+Note: the keysyms header includes third-party copyright and license notices; preserve it verbatim.
@@ -0,0 +1,6 @@
1+Source: https://github.com/xkbcommon/libxkbcommon
2+Tag: xkbcommon-1.13.2
3+Commit: d1442aaa6b635551182e83c3b55037f4c118c962
4+Files: include/xkbcommon/{xkbcommon.h,xkbcommon-compose.h,xkbcommon-keysyms.h,xkbcommon-names.h}, LICENSE
5+SHA-256: see SHA256SUMS (paths relative to repository root)
6+Note: the keysyms header includes third-party copyright and license notices; preserve it verbatim.
A upstream/SHA256SUMS +5 -0
@@ -0,0 +1,5 @@
1+967e97b28658f0037bbe164a237bdfb0c3ec790e67f6c29372ea5b09306f61f3 upstream/LICENSE
2+6e2d985b3653c3674df87731931b98a68e4dcb72d5ff90e457708e8e16ce818d upstream/xkbcommon-compose.h
3+17e58db5edc299d13b4365adce6c6d805e16642977ffaacf98d9be0c4c7071ff upstream/xkbcommon.h
4+13023369f65a17411606084e3e09557b4886aeb15f89affba4aaa86490a463f3 upstream/xkbcommon-keysyms.h
5+a5b0ed01c8b1816da8b5ed29cf776eb2ad1f707cf03ff74e62e36a5ca9009f61 upstream/xkbcommon-names.h
@@ -0,0 +1,5 @@
1+967e97b28658f0037bbe164a237bdfb0c3ec790e67f6c29372ea5b09306f61f3 upstream/LICENSE
2+6e2d985b3653c3674df87731931b98a68e4dcb72d5ff90e457708e8e16ce818d upstream/xkbcommon-compose.h
3+17e58db5edc299d13b4365adce6c6d805e16642977ffaacf98d9be0c4c7071ff upstream/xkbcommon.h
4+13023369f65a17411606084e3e09557b4886aeb15f89affba4aaa86490a463f3 upstream/xkbcommon-keysyms.h
5+a5b0ed01c8b1816da8b5ed29cf776eb2ad1f707cf03ff74e62e36a5ca9009f61 upstream/xkbcommon-names.h
A upstream/xkbcommon-compose.h +621 -0
@@ -0,0 +1,621 @@
1+/*
2+ * Copyright © 2013 Ran Benita
3+ * SPDX-License-Identifier: MIT
4+ */
5+
6+#ifndef _XKBCOMMON_COMPOSE_H
7+#define _XKBCOMMON_COMPOSE_H
8+
9+#include <xkbcommon/xkbcommon.h>
10+
11+#ifdef __cplusplus
12+extern "C" {
13+#endif
14+
15+/**
16+ * @file
17+ * libxkbcommon Compose API - support for Compose and dead-keys.
18+ */
19+
20+/**
21+ * @defgroup compose Compose and dead-keys support
22+ * Support for Compose and dead-keys.
23+ * @since 0.5.0
24+ *
25+ * @{
26+ */
27+
28+/**
29+ * @page compose-overview Overview
30+ * @parblock
31+ *
32+ * Compose and dead-keys are a common feature of many keyboard input
33+ * systems. They extend the range of the keysyms that can be produced
34+ * directly from a keyboard by using a sequence of key strokes, instead
35+ * of just one.
36+ *
37+ * Here are some example sequences, in the libX11 Compose file format:
38+ *
39+ * <dead_acute> <a> : "á" aacute # LATIN SMALL LETTER A WITH ACUTE
40+ * <Multi_key> <A> <T> : "@" at # COMMERCIAL AT
41+ *
42+ * When the user presses a key which produces the `<dead_acute>` keysym,
43+ * nothing initially happens (thus the key is dubbed a *dead-key*). But
44+ * when the user enters `<a>`, “á” is *composed*, in place of “a”. If
45+ * instead the user had entered a keysym which does not follow
46+ * `<dead_acute>` in any compose sequence, the sequence is said to be
47+ * *cancelled*.
48+ *
49+ * Compose files define many such sequences. For a description of the
50+ * common file format for Compose files, see the `Compose(5)` man page.
51+ *
52+ * A successfully-composed sequence has two results: a keysym and a UTF-8
53+ * string. At least one of the two is defined for each sequence. If only
54+ * a keysym is given, the keysym’s string representation is used for the
55+ * result string (using xkb_keysym_to_utf8()).
56+ *
57+ * This library provides low-level support for Compose file parsing and
58+ * processing. Higher-level APIs (such as libX11’s `Xutf8LookupString(3)`)
59+ * may be built upon it, or it can be used directly.
60+ *
61+ * @endparblock
62+ */
63+
64+/**
65+ * @page compose-conflicting Conflicting Sequences
66+ * @parblock
67+ *
68+ * To avoid ambiguity, a sequence is not allowed to be a prefix of another.
69+ * When such a conflict occurs, the sequence defined last is the one retained.
70+ *
71+ * Sequences of length 1 are allowed.
72+ *
73+ * @endparblock
74+ */
75+
76+/**
77+ * @page compose-cancellation Cancellation Behavior
78+ * @parblock
79+ *
80+ * What should happen when a sequence is cancelled? For example, consider
81+ * there are only the above sequences, and the input keysyms are
82+ * `<dead_acute> <b>`. There are a few approaches:
83+ *
84+ * 1. Swallow the cancelling keysym; that is, no keysym is produced.
85+ * This is the approach taken by libX11.
86+ * 2. Let the cancelling keysym through; that is, `<b>` is produced.
87+ * 3. Replay the entire sequence; that is, `<dead_acute> <b>` is produced.
88+ * This is the approach taken by Microsoft Windows (approximately;
89+ * instead of `<dead_acute>`, the underlying key is used. This is
90+ * difficult to simulate with XKB keymaps).
91+ *
92+ * You can program whichever approach best fits users' expectations.
93+ *
94+ * @endparblock
95+ */
96+
97+/**
98+ * @struct xkb_compose_table
99+ * Opaque Compose table object.
100+ *
101+ * The compose table holds the definitions of the Compose sequences, as
102+ * gathered from Compose files. It is immutable.
103+ */
104+struct xkb_compose_table;
105+
106+/**
107+ * @struct xkb_compose_state
108+ * Opaque Compose state object.
109+ *
110+ * The compose state maintains state for compose sequence matching, such
111+ * as which possible sequences are being matched, and the position within
112+ * these sequences. It acts as a simple state machine wherein keysyms are
113+ * the input, and composed keysyms and strings are the output.
114+ *
115+ * The compose state is usually associated with a keyboard device.
116+ */
117+struct xkb_compose_state;
118+
119+/** Flags affecting Compose file compilation. */
120+enum xkb_compose_compile_flags {
121+ /** Do not apply any flags. */
122+ XKB_COMPOSE_COMPILE_NO_FLAGS = 0
123+};
124+
125+/** The recognized Compose file formats. */
126+enum xkb_compose_format {
127+ /** The classic libX11 Compose text format, described in Compose(5). */
128+ XKB_COMPOSE_FORMAT_TEXT_V1 = 1
129+};
130+
131+/**
132+ * @page compose-locale Compose Locale
133+ * @parblock
134+ *
135+ * Compose files are locale dependent:
136+ * - Compose files are written for a locale, and the locale is used when
137+ * searching for the appropriate file to use.
138+ * - Compose files may reference the locale internally, with directives
139+ * such as `%%L`.
140+ *
141+ * As such, functions like `xkb_compose_table::xkb_compose_table_new_from_locale()`
142+ * require a `locale` parameter. This will usually be the current locale (see
143+ * `locale(7)` for more details). You may also want to allow the user to
144+ * explicitly configure it, so he can use the Compose file of a given
145+ * locale, but not use that locale for other things.
146+ *
147+ * You may query the current locale as follows:
148+ *
149+ * ```c
150+ * const char *locale;
151+ * locale = setlocale(LC_CTYPE, NULL);
152+ * ```
153+ *
154+ * This will only give useful results if the program had previously set
155+ * the current locale using setlocale(3), with `LC_CTYPE` or `LC_ALL`
156+ * and a non-`NULL` argument.
157+ *
158+ * If you prefer not to use the locale system of the C runtime library,
159+ * you may nevertheless obtain the user’s locale directly using
160+ * environment variables, as described in `locale(7)`. For example,
161+ *
162+ * ```c
163+ * const char *locale;
164+ * locale = getenv("LC_ALL");
165+ * if (!locale || !*locale)
166+ * locale = getenv("LC_CTYPE");
167+ * if (!locale || !*locale)
168+ * locale = getenv("LANG");
169+ * if (!locale || !*locale)
170+ * locale = "C";
171+ * ```
172+ *
173+ * Note that some locales supported by the C standard library may not
174+ * have a Compose file assigned.
175+ *
176+ * @endparblock
177+ */
178+
179+/**
180+ * Create a compose table for a given locale.
181+ *
182+ * The locale is used for searching the file-system for an appropriate
183+ * Compose file. The search order is described in Compose(5). It is
184+ * affected by the following environment variables:
185+ *
186+ * 1. `XCOMPOSEFILE` - see Compose(5).
187+ * 2. `XDG_CONFIG_HOME` - before `$HOME/.XCompose` is checked,
188+ * `$XDG_CONFIG_HOME/XCompose` is checked (with a fall back to
189+ * `$HOME/.config/XCompose` if `XDG_CONFIG_HOME` is not defined).
190+ * This is a libxkbcommon extension to the search procedure in
191+ * Compose(5) (since libxkbcommon 1.0.0). Note that other
192+ * implementations, such as libX11, might not find a Compose file in
193+ * this path.
194+ * 3. `HOME` - see Compose(5).
195+ * 4. `XLOCALEDIR` - if set, used as the base directory for the system’s
196+ * X locale files, e.g. `/usr/share/X11/locale`, instead of the
197+ * preconfigured directory.
198+ *
199+ * Since 1.12, system locales not registered in `$XLOCALEDIR` will fallback
200+ * to `en_US.UTF-8`, if missing locale detection is supported by the C standard
201+ * library in use.
202+ *
203+ * @param context
204+ * The library context in which to create the compose table.
205+ * @param locale
206+ * The current locale. See @ref compose-locale.
207+ * \n
208+ * The value is copied, so it is safe to pass the result of `getenv(3)`
209+ * (or similar) without fear of it being invalidated by a subsequent
210+ * `setenv(3)` (or similar).
211+ * @param flags
212+ * Optional flags for the compose table, or 0.
213+ *
214+ * @returns A compose table for the given locale, or `NULL` if the
215+ * compilation failed or a Compose file was not found.
216+ *
217+ * @memberof xkb_compose_table
218+ */
219+XKB_EXPORT struct xkb_compose_table *
220+xkb_compose_table_new_from_locale(struct xkb_context *context,
221+ const char *locale,
222+ enum xkb_compose_compile_flags flags);
223+
224+/**
225+ * Create a new compose table from a Compose file.
226+ *
227+ * @param context
228+ * The library context in which to create the compose table.
229+ * @param file
230+ * The Compose file to compile.
231+ * @param locale
232+ * The current locale. See @ref compose-locale.
233+ * @param format
234+ * The text format of the Compose file to compile.
235+ * @param flags
236+ * Optional flags for the compose table, or 0.
237+ *
238+ * @returns A compose table compiled from the given file, or `NULL` if
239+ * the compilation failed.
240+ *
241+ * @memberof xkb_compose_table
242+ */
243+XKB_EXPORT struct xkb_compose_table *
244+xkb_compose_table_new_from_file(struct xkb_context *context,
245+ FILE *file,
246+ const char *locale,
247+ enum xkb_compose_format format,
248+ enum xkb_compose_compile_flags flags);
249+
250+/**
251+ * Create a new compose table from a memory buffer.
252+ *
253+ * This is just like xkb_compose_table_new_from_file(), but instead of
254+ * a file, gets the table as one enormous string.
255+ *
256+ * @see xkb_compose_table_new_from_file()
257+ * @memberof xkb_compose_table
258+ */
259+XKB_EXPORT struct xkb_compose_table *
260+xkb_compose_table_new_from_buffer(struct xkb_context *context,
261+ const char *buffer, size_t length,
262+ const char *locale,
263+ enum xkb_compose_format format,
264+ enum xkb_compose_compile_flags flags);
265+
266+/**
267+ * Take a new reference on a compose table.
268+ *
269+ * @returns The passed in object.
270+ *
271+ * @memberof xkb_compose_table
272+ */
273+XKB_EXPORT struct xkb_compose_table *
274+xkb_compose_table_ref(struct xkb_compose_table *table);
275+
276+/**
277+ * Release a reference on a compose table, and possibly free it.
278+ *
279+ * @param table The object. If it is `NULL`, this function does nothing.
280+ *
281+ * @memberof xkb_compose_table
282+ */
283+XKB_EXPORT void
284+xkb_compose_table_unref(struct xkb_compose_table *table);
285+
286+/**
287+ * @struct xkb_compose_table_entry
288+ * Opaque Compose table entry object.
289+ *
290+ * Represents a single entry in a Compose file in the iteration API.
291+ * It is immutable.
292+ *
293+ * @sa xkb_compose_table_iterator_new
294+ * @since 1.6.0
295+ */
296+struct xkb_compose_table_entry;
297+
298+/**
299+ * Get the left-hand keysym sequence of a Compose table entry.
300+ *
301+ * For example, given the following entry:
302+ *
303+ * ```
304+ * <dead_tilde> <space> : "~" asciitilde # TILDE
305+ * ```
306+ *
307+ * it will return `{XKB_KEY_dead_tilde, XKB_KEY_space}`.
308+ *
309+ * @param[in] entry The compose table entry object to process.
310+ *
311+ * @param[out] sequence_length Number of keysyms in the sequence.
312+ *
313+ * @returns The array of left-hand side keysyms. The number of keysyms
314+ * is returned in the @p sequence_length out-parameter.
315+ *
316+ * @memberof xkb_compose_table_entry
317+ * @since 1.6.0
318+ */
319+XKB_EXPORT const xkb_keysym_t *
320+xkb_compose_table_entry_sequence(struct xkb_compose_table_entry *entry,
321+ size_t *sequence_length);
322+
323+/**
324+ * Get the right-hand result keysym of a Compose table entry.
325+ *
326+ * For example, given the following entry:
327+ *
328+ * ```
329+ * <dead_tilde> <space> : "~" asciitilde # TILDE
330+ * ```
331+ *
332+ * it will return `XKB_KEY_asciitilde`.
333+ *
334+ * The keysym is optional; if the entry does not specify a keysym,
335+ * returns `XKB_KEY_NoSymbol`.
336+ *
337+ * @memberof xkb_compose_table_entry
338+ * @since 1.6.0
339+ */
340+XKB_EXPORT xkb_keysym_t
341+xkb_compose_table_entry_keysym(struct xkb_compose_table_entry *entry);
342+
343+/**
344+ * Get the right-hand result string of a Compose table entry.
345+ *
346+ * The string is UTF-8 encoded and `NULL`-terminated.
347+ *
348+ * For example, given the following entry:
349+ *
350+ * ```
351+ * <dead_tilde> <space> : "~" asciitilde # TILDE
352+ * ```
353+ *
354+ * it will return `"~"`.
355+ *
356+ * The string is optional; if the entry does not specify a string,
357+ * returns the empty string.
358+ *
359+ * @memberof xkb_compose_table_entry
360+ * @since 1.6.0
361+ */
362+XKB_EXPORT const char *
363+xkb_compose_table_entry_utf8(struct xkb_compose_table_entry *entry);
364+
365+/**
366+ * @struct xkb_compose_table_iterator
367+ * Iterator over a compose table’s entries.
368+ *
369+ * @sa xkb_compose_table_iterator_new()
370+ * @since 1.6.0
371+ */
372+struct xkb_compose_table_iterator;
373+
374+/**
375+ * Create a new iterator for a compose table.
376+ *
377+ * Intended use:
378+ *
379+ * ```c
380+ * struct xkb_compose_table_iterator *iter = xkb_compose_table_iterator_new(compose_table);
381+ * struct xkb_compose_table_entry *entry;
382+ * while ((entry = xkb_compose_table_iterator_next(iter))) {
383+ * // ...
384+ * }
385+ * xkb_compose_table_iterator_free(iter);
386+ * ```
387+ *
388+ * @returns A new compose table iterator, or `NULL` on failure.
389+ *
390+ * @memberof xkb_compose_table_iterator
391+ * @sa xkb_compose_table_iterator_free()
392+ * @since 1.6.0
393+ */
394+XKB_EXPORT struct xkb_compose_table_iterator *
395+xkb_compose_table_iterator_new(struct xkb_compose_table *table);
396+
397+/**
398+ * Free a compose iterator.
399+ *
400+ * @memberof xkb_compose_table_iterator
401+ * @since 1.6.0
402+ */
403+XKB_EXPORT void
404+xkb_compose_table_iterator_free(struct xkb_compose_table_iterator *iter);
405+
406+/**
407+ * Get the next compose entry from a compose table iterator.
408+ *
409+ * The entries are returned in lexicographic order of the left-hand
410+ * side of entries. This does not correspond to the order in which
411+ * the entries appear in the Compose file.
412+ *
413+ * @attention The return value is valid until the next call to this function.
414+ *
415+ * Returns `NULL` in case there is no more entries.
416+ *
417+ * @memberof xkb_compose_table_iterator
418+ * @since 1.6.0
419+ */
420+XKB_EXPORT struct xkb_compose_table_entry *
421+xkb_compose_table_iterator_next(struct xkb_compose_table_iterator *iter);
422+
423+/** Flags for compose state creation. */
424+enum xkb_compose_state_flags {
425+ /** Do not apply any flags. */
426+ XKB_COMPOSE_STATE_NO_FLAGS = 0
427+};
428+
429+/**
430+ * Create a new compose state object.
431+ *
432+ * @param table
433+ * The compose table the state will use.
434+ * @param flags
435+ * Optional flags for the compose state, or 0.
436+ *
437+ * @returns A new compose state, or `NULL` on failure.
438+ *
439+ * @memberof xkb_compose_state
440+ */
441+XKB_EXPORT struct xkb_compose_state *
442+xkb_compose_state_new(struct xkb_compose_table *table,
443+ enum xkb_compose_state_flags flags);
444+
445+/**
446+ * Take a new reference on a compose state object.
447+ *
448+ * @returns The passed in object.
449+ *
450+ * @memberof xkb_compose_state
451+ */
452+XKB_EXPORT struct xkb_compose_state *
453+xkb_compose_state_ref(struct xkb_compose_state *state);
454+
455+/**
456+ * Release a reference on a compose state object, and possibly free it.
457+ *
458+ * @param state The object. If `NULL`, do nothing.
459+ *
460+ * @memberof xkb_compose_state
461+ */
462+XKB_EXPORT void
463+xkb_compose_state_unref(struct xkb_compose_state *state);
464+
465+/**
466+ * Get the compose table which a compose state object is using.
467+ *
468+ * @returns The compose table which was passed to xkb_compose_state_new()
469+ * when creating this state object.
470+ *
471+ * This function does not take a new reference on the compose table; you
472+ * must explicitly reference it yourself if you plan to use it beyond the
473+ * lifetime of the state.
474+ *
475+ * @memberof xkb_compose_state
476+ */
477+XKB_EXPORT struct xkb_compose_table *
478+xkb_compose_state_get_compose_table(struct xkb_compose_state *state);
479+
480+/** Status of the Compose sequence state machine. */
481+enum xkb_compose_status {
482+ /** The initial state; no sequence has started yet. */
483+ XKB_COMPOSE_NOTHING,
484+ /** In the middle of a sequence. */
485+ XKB_COMPOSE_COMPOSING,
486+ /** A complete sequence has been matched. */
487+ XKB_COMPOSE_COMPOSED,
488+ /** The last sequence was cancelled due to an unmatched keysym. */
489+ XKB_COMPOSE_CANCELLED
490+};
491+
492+/** The effect of a keysym fed to xkb_compose_state_feed(). */
493+enum xkb_compose_feed_result {
494+ /** The keysym had no effect - it did not affect the status. */
495+ XKB_COMPOSE_FEED_IGNORED,
496+ /** The keysym started, advanced or cancelled a sequence. */
497+ XKB_COMPOSE_FEED_ACCEPTED
498+};
499+
500+/**
501+ * Feed one keysym to the Compose sequence state machine.
502+ *
503+ * This function can advance into a compose sequence, cancel a sequence,
504+ * start a new sequence, or do nothing in particular. The resulting
505+ * status may be observed with `xkb_compose_state_get_status()`.
506+ *
507+ * Some keysyms, such as keysyms for modifier keys, are ignored - they
508+ * have no effect on the status or otherwise.
509+ *
510+ * The following is a description of the possible status transitions, in
511+ * the format CURRENT STATUS => NEXT STATUS, given a non-ignored input
512+ * keysym `keysym`:
513+ *
514+ @verbatim
515+ NOTHING or CANCELLED or COMPOSED =>
516+ NOTHING if keysym does not start a sequence.
517+ COMPOSING if keysym starts a sequence.
518+ COMPOSED if keysym starts and terminates a single-keysym sequence.
519+
520+ COMPOSING =>
521+ COMPOSING if keysym advances any of the currently possible
522+ sequences but does not terminate any of them.
523+ COMPOSED if keysym terminates one of the currently possible
524+ sequences.
525+ CANCELLED if keysym does not advance any of the currently
526+ possible sequences.
527+ @endverbatim
528+ *
529+ * The current Compose formats do not support multiple-keysyms. Therefore, if
530+ * you are using a function such as `xkb_state::xkb_state_key_get_syms()`
531+ * and it returns more than one keysym, consider feeding `XKB_KEY_NoSymbol`
532+ * instead.
533+ *
534+ * @param state
535+ * The compose state object.
536+ * @param keysym
537+ * A keysym, usually obtained after a key-press event, with a
538+ * function such as `xkb_state::xkb_state_key_get_one_sym()`.
539+ *
540+ * @returns Whether the keysym was ignored. This is useful, for example,
541+ * if you want to keep a record of the sequence matched thus far.
542+ *
543+ * @memberof xkb_compose_state
544+ */
545+XKB_EXPORT enum xkb_compose_feed_result
546+xkb_compose_state_feed(struct xkb_compose_state *state,
547+ xkb_keysym_t keysym);
548+
549+/**
550+ * Reset the Compose sequence state machine.
551+ *
552+ * The status is set to `::XKB_COMPOSE_NOTHING`, and the current sequence
553+ * is discarded.
554+ *
555+ * @memberof xkb_compose_state
556+ */
557+XKB_EXPORT void
558+xkb_compose_state_reset(struct xkb_compose_state *state);
559+
560+/**
561+ * Get the current status of the compose state machine.
562+ *
563+ * @see xkb_compose_status
564+ * @memberof xkb_compose_state
565+ **/
566+XKB_EXPORT enum xkb_compose_status
567+xkb_compose_state_get_status(struct xkb_compose_state *state);
568+
569+/**
570+ * Get the result Unicode/UTF-8 string for a composed sequence.
571+ *
572+ * See @ref compose-overview for more details. This function is only
573+ * useful when the status is `::XKB_COMPOSE_COMPOSED`.
574+ *
575+ * @param[in] state
576+ * The compose state.
577+ * @param[out] buffer
578+ * A buffer to write the string into.
579+ * @param[in] size
580+ * Size of the buffer.
581+ *
582+ * @warning If the buffer passed is too small, the string is truncated
583+ * (though still `NULL`-terminated).
584+ *
585+ * @returns
586+ * The number of bytes required for the string, excluding the `NULL` byte.
587+ * If the sequence is not complete, or does not have a viable result
588+ * string, returns 0, and sets `buffer` to the empty string (if possible).
589+ * @returns
590+ * You may check if truncation has occurred by comparing the return value
591+ * with the size of `buffer`, similarly to the `snprintf(3)` function.
592+ * You may safely pass `NULL` and 0 to `buffer` and `size` to find the
593+ * required size (without the `NULL`-byte).
594+ *
595+ * @memberof xkb_compose_state
596+ **/
597+XKB_EXPORT int
598+xkb_compose_state_get_utf8(struct xkb_compose_state *state,
599+ char *buffer, size_t size);
600+
601+/**
602+ * Get the result keysym for a composed sequence.
603+ *
604+ * See @ref compose-overview for more details. This function is only
605+ * useful when the status is `::XKB_COMPOSE_COMPOSED`.
606+ *
607+ * @returns The result keysym. If the sequence is not complete, or does
608+ * not specify a result keysym, returns `XKB_KEY_NoSymbol`.
609+ *
610+ * @memberof xkb_compose_state
611+ **/
612+XKB_EXPORT xkb_keysym_t
613+xkb_compose_state_get_one_sym(struct xkb_compose_state *state);
614+
615+/** @} */
616+
617+#ifdef __cplusplus
618+} /* extern "C" */
619+#endif
620+
621+#endif /* _XKBCOMMON_COMPOSE_H */
@@ -0,0 +1,621 @@
1+/*
2+ * Copyright © 2013 Ran Benita
3+ * SPDX-License-Identifier: MIT
4+ */
5+
6+#ifndef _XKBCOMMON_COMPOSE_H
7+#define _XKBCOMMON_COMPOSE_H
8+
9+#include <xkbcommon/xkbcommon.h>
10+
11+#ifdef __cplusplus
12+extern "C" {
13+#endif
14+
15+/**
16+ * @file
17+ * libxkbcommon Compose API - support for Compose and dead-keys.
18+ */
19+
20+/**
21+ * @defgroup compose Compose and dead-keys support
22+ * Support for Compose and dead-keys.
23+ * @since 0.5.0
24+ *
25+ * @{
26+ */
27+
28+/**
29+ * @page compose-overview Overview
30+ * @parblock
31+ *
32+ * Compose and dead-keys are a common feature of many keyboard input
33+ * systems. They extend the range of the keysyms that can be produced
34+ * directly from a keyboard by using a sequence of key strokes, instead
35+ * of just one.
36+ *
37+ * Here are some example sequences, in the libX11 Compose file format:
38+ *
39+ * <dead_acute> <a> : "á" aacute # LATIN SMALL LETTER A WITH ACUTE
40+ * <Multi_key> <A> <T> : "@" at # COMMERCIAL AT
41+ *
42+ * When the user presses a key which produces the `<dead_acute>` keysym,
43+ * nothing initially happens (thus the key is dubbed a *dead-key*). But
44+ * when the user enters `<a>`, “á” is *composed*, in place of “a”. If
45+ * instead the user had entered a keysym which does not follow
46+ * `<dead_acute>` in any compose sequence, the sequence is said to be
47+ * *cancelled*.
48+ *
49+ * Compose files define many such sequences. For a description of the
50+ * common file format for Compose files, see the `Compose(5)` man page.
51+ *
52+ * A successfully-composed sequence has two results: a keysym and a UTF-8
53+ * string. At least one of the two is defined for each sequence. If only
54+ * a keysym is given, the keysym’s string representation is used for the
55+ * result string (using xkb_keysym_to_utf8()).
56+ *
57+ * This library provides low-level support for Compose file parsing and
58+ * processing. Higher-level APIs (such as libX11’s `Xutf8LookupString(3)`)
59+ * may be built upon it, or it can be used directly.
60+ *
61+ * @endparblock
62+ */
63+
64+/**
65+ * @page compose-conflicting Conflicting Sequences
66+ * @parblock
67+ *
68+ * To avoid ambiguity, a sequence is not allowed to be a prefix of another.
69+ * When such a conflict occurs, the sequence defined last is the one retained.
70+ *
71+ * Sequences of length 1 are allowed.
72+ *
73+ * @endparblock
74+ */
75+
76+/**
77+ * @page compose-cancellation Cancellation Behavior
78+ * @parblock
79+ *
80+ * What should happen when a sequence is cancelled? For example, consider
81+ * there are only the above sequences, and the input keysyms are
82+ * `<dead_acute> <b>`. There are a few approaches:
83+ *
84+ * 1. Swallow the cancelling keysym; that is, no keysym is produced.
85+ * This is the approach taken by libX11.
86+ * 2. Let the cancelling keysym through; that is, `<b>` is produced.
87+ * 3. Replay the entire sequence; that is, `<dead_acute> <b>` is produced.
88+ * This is the approach taken by Microsoft Windows (approximately;
89+ * instead of `<dead_acute>`, the underlying key is used. This is
90+ * difficult to simulate with XKB keymaps).
91+ *
92+ * You can program whichever approach best fits users' expectations.
93+ *
94+ * @endparblock
95+ */
96+
97+/**
98+ * @struct xkb_compose_table
99+ * Opaque Compose table object.
100+ *
101+ * The compose table holds the definitions of the Compose sequences, as
102+ * gathered from Compose files. It is immutable.
103+ */
104+struct xkb_compose_table;
105+
106+/**
107+ * @struct xkb_compose_state
108+ * Opaque Compose state object.
109+ *
110+ * The compose state maintains state for compose sequence matching, such
111+ * as which possible sequences are being matched, and the position within
112+ * these sequences. It acts as a simple state machine wherein keysyms are
113+ * the input, and composed keysyms and strings are the output.
114+ *
115+ * The compose state is usually associated with a keyboard device.
116+ */
117+struct xkb_compose_state;
118+
119+/** Flags affecting Compose file compilation. */
120+enum xkb_compose_compile_flags {
121+ /** Do not apply any flags. */
122+ XKB_COMPOSE_COMPILE_NO_FLAGS = 0
123+};
124+
125+/** The recognized Compose file formats. */
126+enum xkb_compose_format {
127+ /** The classic libX11 Compose text format, described in Compose(5). */
128+ XKB_COMPOSE_FORMAT_TEXT_V1 = 1
129+};
130+
131+/**
132+ * @page compose-locale Compose Locale
133+ * @parblock
134+ *
135+ * Compose files are locale dependent:
136+ * - Compose files are written for a locale, and the locale is used when
137+ * searching for the appropriate file to use.
138+ * - Compose files may reference the locale internally, with directives
139+ * such as `%%L`.
140+ *
141+ * As such, functions like `xkb_compose_table::xkb_compose_table_new_from_locale()`
142+ * require a `locale` parameter. This will usually be the current locale (see
143+ * `locale(7)` for more details). You may also want to allow the user to
144+ * explicitly configure it, so he can use the Compose file of a given
145+ * locale, but not use that locale for other things.
146+ *
147+ * You may query the current locale as follows:
148+ *
149+ * ```c
150+ * const char *locale;
151+ * locale = setlocale(LC_CTYPE, NULL);
152+ * ```
153+ *
154+ * This will only give useful results if the program had previously set
155+ * the current locale using setlocale(3), with `LC_CTYPE` or `LC_ALL`
156+ * and a non-`NULL` argument.
157+ *
158+ * If you prefer not to use the locale system of the C runtime library,
159+ * you may nevertheless obtain the user’s locale directly using
160+ * environment variables, as described in `locale(7)`. For example,
161+ *
162+ * ```c
163+ * const char *locale;
164+ * locale = getenv("LC_ALL");
165+ * if (!locale || !*locale)
166+ * locale = getenv("LC_CTYPE");
167+ * if (!locale || !*locale)
168+ * locale = getenv("LANG");
169+ * if (!locale || !*locale)
170+ * locale = "C";
171+ * ```
172+ *
173+ * Note that some locales supported by the C standard library may not
174+ * have a Compose file assigned.
175+ *
176+ * @endparblock
177+ */
178+
179+/**
180+ * Create a compose table for a given locale.
181+ *
182+ * The locale is used for searching the file-system for an appropriate
183+ * Compose file. The search order is described in Compose(5). It is
184+ * affected by the following environment variables:
185+ *
186+ * 1. `XCOMPOSEFILE` - see Compose(5).
187+ * 2. `XDG_CONFIG_HOME` - before `$HOME/.XCompose` is checked,
188+ * `$XDG_CONFIG_HOME/XCompose` is checked (with a fall back to
189+ * `$HOME/.config/XCompose` if `XDG_CONFIG_HOME` is not defined).
190+ * This is a libxkbcommon extension to the search procedure in
191+ * Compose(5) (since libxkbcommon 1.0.0). Note that other
192+ * implementations, such as libX11, might not find a Compose file in
193+ * this path.
194+ * 3. `HOME` - see Compose(5).
195+ * 4. `XLOCALEDIR` - if set, used as the base directory for the system’s
196+ * X locale files, e.g. `/usr/share/X11/locale`, instead of the
197+ * preconfigured directory.
198+ *
199+ * Since 1.12, system locales not registered in `$XLOCALEDIR` will fallback
200+ * to `en_US.UTF-8`, if missing locale detection is supported by the C standard
201+ * library in use.
202+ *
203+ * @param context
204+ * The library context in which to create the compose table.
205+ * @param locale
206+ * The current locale. See @ref compose-locale.
207+ * \n
208+ * The value is copied, so it is safe to pass the result of `getenv(3)`
209+ * (or similar) without fear of it being invalidated by a subsequent
210+ * `setenv(3)` (or similar).
211+ * @param flags
212+ * Optional flags for the compose table, or 0.
213+ *
214+ * @returns A compose table for the given locale, or `NULL` if the
215+ * compilation failed or a Compose file was not found.
216+ *
217+ * @memberof xkb_compose_table
218+ */
219+XKB_EXPORT struct xkb_compose_table *
220+xkb_compose_table_new_from_locale(struct xkb_context *context,
221+ const char *locale,
222+ enum xkb_compose_compile_flags flags);
223+
224+/**
225+ * Create a new compose table from a Compose file.
226+ *
227+ * @param context
228+ * The library context in which to create the compose table.
229+ * @param file
230+ * The Compose file to compile.
231+ * @param locale
232+ * The current locale. See @ref compose-locale.
233+ * @param format
234+ * The text format of the Compose file to compile.
235+ * @param flags
236+ * Optional flags for the compose table, or 0.
237+ *
238+ * @returns A compose table compiled from the given file, or `NULL` if
239+ * the compilation failed.
240+ *
241+ * @memberof xkb_compose_table
242+ */
243+XKB_EXPORT struct xkb_compose_table *
244+xkb_compose_table_new_from_file(struct xkb_context *context,
245+ FILE *file,
246+ const char *locale,
247+ enum xkb_compose_format format,
248+ enum xkb_compose_compile_flags flags);
249+
250+/**
251+ * Create a new compose table from a memory buffer.
252+ *
253+ * This is just like xkb_compose_table_new_from_file(), but instead of
254+ * a file, gets the table as one enormous string.
255+ *
256+ * @see xkb_compose_table_new_from_file()
257+ * @memberof xkb_compose_table
258+ */
259+XKB_EXPORT struct xkb_compose_table *
260+xkb_compose_table_new_from_buffer(struct xkb_context *context,
261+ const char *buffer, size_t length,
262+ const char *locale,
263+ enum xkb_compose_format format,
264+ enum xkb_compose_compile_flags flags);
265+
266+/**
267+ * Take a new reference on a compose table.
268+ *
269+ * @returns The passed in object.
270+ *
271+ * @memberof xkb_compose_table
272+ */
273+XKB_EXPORT struct xkb_compose_table *
274+xkb_compose_table_ref(struct xkb_compose_table *table);
275+
276+/**
277+ * Release a reference on a compose table, and possibly free it.
278+ *
279+ * @param table The object. If it is `NULL`, this function does nothing.
280+ *
281+ * @memberof xkb_compose_table
282+ */
283+XKB_EXPORT void
284+xkb_compose_table_unref(struct xkb_compose_table *table);
285+
286+/**
287+ * @struct xkb_compose_table_entry
288+ * Opaque Compose table entry object.
289+ *
290+ * Represents a single entry in a Compose file in the iteration API.
291+ * It is immutable.
292+ *
293+ * @sa xkb_compose_table_iterator_new
294+ * @since 1.6.0
295+ */
296+struct xkb_compose_table_entry;
297+
298+/**
299+ * Get the left-hand keysym sequence of a Compose table entry.
300+ *
301+ * For example, given the following entry:
302+ *
303+ * ```
304+ * <dead_tilde> <space> : "~" asciitilde # TILDE
305+ * ```
306+ *
307+ * it will return `{XKB_KEY_dead_tilde, XKB_KEY_space}`.
308+ *
309+ * @param[in] entry The compose table entry object to process.
310+ *
311+ * @param[out] sequence_length Number of keysyms in the sequence.
312+ *
313+ * @returns The array of left-hand side keysyms. The number of keysyms
314+ * is returned in the @p sequence_length out-parameter.
315+ *
316+ * @memberof xkb_compose_table_entry
317+ * @since 1.6.0
318+ */
319+XKB_EXPORT const xkb_keysym_t *
320+xkb_compose_table_entry_sequence(struct xkb_compose_table_entry *entry,
321+ size_t *sequence_length);
322+
323+/**
324+ * Get the right-hand result keysym of a Compose table entry.
325+ *
326+ * For example, given the following entry:
327+ *
328+ * ```
329+ * <dead_tilde> <space> : "~" asciitilde # TILDE
330+ * ```
331+ *
332+ * it will return `XKB_KEY_asciitilde`.
333+ *
334+ * The keysym is optional; if the entry does not specify a keysym,
335+ * returns `XKB_KEY_NoSymbol`.
336+ *
337+ * @memberof xkb_compose_table_entry
338+ * @since 1.6.0
339+ */
340+XKB_EXPORT xkb_keysym_t
341+xkb_compose_table_entry_keysym(struct xkb_compose_table_entry *entry);
342+
343+/**
344+ * Get the right-hand result string of a Compose table entry.
345+ *
346+ * The string is UTF-8 encoded and `NULL`-terminated.
347+ *
348+ * For example, given the following entry:
349+ *
350+ * ```
351+ * <dead_tilde> <space> : "~" asciitilde # TILDE
352+ * ```
353+ *
354+ * it will return `"~"`.
355+ *
356+ * The string is optional; if the entry does not specify a string,
357+ * returns the empty string.
358+ *
359+ * @memberof xkb_compose_table_entry
360+ * @since 1.6.0
361+ */
362+XKB_EXPORT const char *
363+xkb_compose_table_entry_utf8(struct xkb_compose_table_entry *entry);
364+
365+/**
366+ * @struct xkb_compose_table_iterator
367+ * Iterator over a compose table’s entries.
368+ *
369+ * @sa xkb_compose_table_iterator_new()
370+ * @since 1.6.0
371+ */
372+struct xkb_compose_table_iterator;
373+
374+/**
375+ * Create a new iterator for a compose table.
376+ *
377+ * Intended use:
378+ *
379+ * ```c
380+ * struct xkb_compose_table_iterator *iter = xkb_compose_table_iterator_new(compose_table);
381+ * struct xkb_compose_table_entry *entry;
382+ * while ((entry = xkb_compose_table_iterator_next(iter))) {
383+ * // ...
384+ * }
385+ * xkb_compose_table_iterator_free(iter);
386+ * ```
387+ *
388+ * @returns A new compose table iterator, or `NULL` on failure.
389+ *
390+ * @memberof xkb_compose_table_iterator
391+ * @sa xkb_compose_table_iterator_free()
392+ * @since 1.6.0
393+ */
394+XKB_EXPORT struct xkb_compose_table_iterator *
395+xkb_compose_table_iterator_new(struct xkb_compose_table *table);
396+
397+/**
398+ * Free a compose iterator.
399+ *
400+ * @memberof xkb_compose_table_iterator
401+ * @since 1.6.0
402+ */
403+XKB_EXPORT void
404+xkb_compose_table_iterator_free(struct xkb_compose_table_iterator *iter);
405+
406+/**
407+ * Get the next compose entry from a compose table iterator.
408+ *
409+ * The entries are returned in lexicographic order of the left-hand
410+ * side of entries. This does not correspond to the order in which
411+ * the entries appear in the Compose file.
412+ *
413+ * @attention The return value is valid until the next call to this function.
414+ *
415+ * Returns `NULL` in case there is no more entries.
416+ *
417+ * @memberof xkb_compose_table_iterator
418+ * @since 1.6.0
419+ */
420+XKB_EXPORT struct xkb_compose_table_entry *
421+xkb_compose_table_iterator_next(struct xkb_compose_table_iterator *iter);
422+
423+/** Flags for compose state creation. */
424+enum xkb_compose_state_flags {
425+ /** Do not apply any flags. */
426+ XKB_COMPOSE_STATE_NO_FLAGS = 0
427+};
428+
429+/**
430+ * Create a new compose state object.
431+ *
432+ * @param table
433+ * The compose table the state will use.
434+ * @param flags
435+ * Optional flags for the compose state, or 0.
436+ *
437+ * @returns A new compose state, or `NULL` on failure.
438+ *
439+ * @memberof xkb_compose_state
440+ */
441+XKB_EXPORT struct xkb_compose_state *
442+xkb_compose_state_new(struct xkb_compose_table *table,
443+ enum xkb_compose_state_flags flags);
444+
445+/**
446+ * Take a new reference on a compose state object.
447+ *
448+ * @returns The passed in object.
449+ *
450+ * @memberof xkb_compose_state
451+ */
452+XKB_EXPORT struct xkb_compose_state *
453+xkb_compose_state_ref(struct xkb_compose_state *state);
454+
455+/**
456+ * Release a reference on a compose state object, and possibly free it.
457+ *
458+ * @param state The object. If `NULL`, do nothing.
459+ *
460+ * @memberof xkb_compose_state
461+ */
462+XKB_EXPORT void
463+xkb_compose_state_unref(struct xkb_compose_state *state);
464+
465+/**
466+ * Get the compose table which a compose state object is using.
467+ *
468+ * @returns The compose table which was passed to xkb_compose_state_new()
469+ * when creating this state object.
470+ *
471+ * This function does not take a new reference on the compose table; you
472+ * must explicitly reference it yourself if you plan to use it beyond the
473+ * lifetime of the state.
474+ *
475+ * @memberof xkb_compose_state
476+ */
477+XKB_EXPORT struct xkb_compose_table *
478+xkb_compose_state_get_compose_table(struct xkb_compose_state *state);
479+
480+/** Status of the Compose sequence state machine. */
481+enum xkb_compose_status {
482+ /** The initial state; no sequence has started yet. */
483+ XKB_COMPOSE_NOTHING,
484+ /** In the middle of a sequence. */
485+ XKB_COMPOSE_COMPOSING,
486+ /** A complete sequence has been matched. */
487+ XKB_COMPOSE_COMPOSED,
488+ /** The last sequence was cancelled due to an unmatched keysym. */
489+ XKB_COMPOSE_CANCELLED
490+};
491+
492+/** The effect of a keysym fed to xkb_compose_state_feed(). */
493+enum xkb_compose_feed_result {
494+ /** The keysym had no effect - it did not affect the status. */
495+ XKB_COMPOSE_FEED_IGNORED,
496+ /** The keysym started, advanced or cancelled a sequence. */
497+ XKB_COMPOSE_FEED_ACCEPTED
498+};
499+
500+/**
501+ * Feed one keysym to the Compose sequence state machine.
502+ *
503+ * This function can advance into a compose sequence, cancel a sequence,
504+ * start a new sequence, or do nothing in particular. The resulting
505+ * status may be observed with `xkb_compose_state_get_status()`.
506+ *
507+ * Some keysyms, such as keysyms for modifier keys, are ignored - they
508+ * have no effect on the status or otherwise.
509+ *
510+ * The following is a description of the possible status transitions, in
511+ * the format CURRENT STATUS => NEXT STATUS, given a non-ignored input
512+ * keysym `keysym`:
513+ *
514+ @verbatim
515+ NOTHING or CANCELLED or COMPOSED =>
516+ NOTHING if keysym does not start a sequence.
517+ COMPOSING if keysym starts a sequence.
518+ COMPOSED if keysym starts and terminates a single-keysym sequence.
519+
520+ COMPOSING =>
521+ COMPOSING if keysym advances any of the currently possible
522+ sequences but does not terminate any of them.
523+ COMPOSED if keysym terminates one of the currently possible
524+ sequences.
525+ CANCELLED if keysym does not advance any of the currently
526+ possible sequences.
527+ @endverbatim
528+ *
529+ * The current Compose formats do not support multiple-keysyms. Therefore, if
530+ * you are using a function such as `xkb_state::xkb_state_key_get_syms()`
531+ * and it returns more than one keysym, consider feeding `XKB_KEY_NoSymbol`
532+ * instead.
533+ *
534+ * @param state
535+ * The compose state object.
536+ * @param keysym
537+ * A keysym, usually obtained after a key-press event, with a
538+ * function such as `xkb_state::xkb_state_key_get_one_sym()`.
539+ *
540+ * @returns Whether the keysym was ignored. This is useful, for example,
541+ * if you want to keep a record of the sequence matched thus far.
542+ *
543+ * @memberof xkb_compose_state
544+ */
545+XKB_EXPORT enum xkb_compose_feed_result
546+xkb_compose_state_feed(struct xkb_compose_state *state,
547+ xkb_keysym_t keysym);
548+
549+/**
550+ * Reset the Compose sequence state machine.
551+ *
552+ * The status is set to `::XKB_COMPOSE_NOTHING`, and the current sequence
553+ * is discarded.
554+ *
555+ * @memberof xkb_compose_state
556+ */
557+XKB_EXPORT void
558+xkb_compose_state_reset(struct xkb_compose_state *state);
559+
560+/**
561+ * Get the current status of the compose state machine.
562+ *
563+ * @see xkb_compose_status
564+ * @memberof xkb_compose_state
565+ **/
566+XKB_EXPORT enum xkb_compose_status
567+xkb_compose_state_get_status(struct xkb_compose_state *state);
568+
569+/**
570+ * Get the result Unicode/UTF-8 string for a composed sequence.
571+ *
572+ * See @ref compose-overview for more details. This function is only
573+ * useful when the status is `::XKB_COMPOSE_COMPOSED`.
574+ *
575+ * @param[in] state
576+ * The compose state.
577+ * @param[out] buffer
578+ * A buffer to write the string into.
579+ * @param[in] size
580+ * Size of the buffer.
581+ *
582+ * @warning If the buffer passed is too small, the string is truncated
583+ * (though still `NULL`-terminated).
584+ *
585+ * @returns
586+ * The number of bytes required for the string, excluding the `NULL` byte.
587+ * If the sequence is not complete, or does not have a viable result
588+ * string, returns 0, and sets `buffer` to the empty string (if possible).
589+ * @returns
590+ * You may check if truncation has occurred by comparing the return value
591+ * with the size of `buffer`, similarly to the `snprintf(3)` function.
592+ * You may safely pass `NULL` and 0 to `buffer` and `size` to find the
593+ * required size (without the `NULL`-byte).
594+ *
595+ * @memberof xkb_compose_state
596+ **/
597+XKB_EXPORT int
598+xkb_compose_state_get_utf8(struct xkb_compose_state *state,
599+ char *buffer, size_t size);
600+
601+/**
602+ * Get the result keysym for a composed sequence.
603+ *
604+ * See @ref compose-overview for more details. This function is only
605+ * useful when the status is `::XKB_COMPOSE_COMPOSED`.
606+ *
607+ * @returns The result keysym. If the sequence is not complete, or does
608+ * not specify a result keysym, returns `XKB_KEY_NoSymbol`.
609+ *
610+ * @memberof xkb_compose_state
611+ **/
612+XKB_EXPORT xkb_keysym_t
613+xkb_compose_state_get_one_sym(struct xkb_compose_state *state);
614+
615+/** @} */
616+
617+#ifdef __cplusplus
618+} /* extern "C" */
619+#endif
620+
621+#endif /* _XKBCOMMON_COMPOSE_H */
A upstream/xkbcommon-keysyms.h +3545 -0 large diff (3545 lines changed): collapsed
A upstream/xkbcommon-names.h +144 -0
@@ -0,0 +1,144 @@
1+/*
2+ * Copyright © 2012 Intel Corporation
3+ * SPDX-License-Identifier: MIT
4+ *
5+ * Author: Daniel Stone <daniel@fooishbar.org>
6+ */
7+
8+#ifndef _XKBCOMMON_NAMES_H
9+#define _XKBCOMMON_NAMES_H
10+
11+/**
12+ * @file
13+ * @brief Predefined names for common modifiers and LEDs.
14+ */
15+
16+/**
17+ * @defgroup modifier-names Predefined names for common modifiers and LEDs
18+ *
19+ * @{
20+ */
21+
22+/**
23+ * @defgroup real-modifier-names Real modifiers names
24+ *
25+ * [*Real* modifiers][real modifier] names are hardcoded in libxkbcommon.
26+ *
27+ * [real modifier]: @ref real-modifier-def
28+ *
29+ * @{
30+ */
31+#define XKB_MOD_NAME_SHIFT "Shift"
32+#define XKB_MOD_NAME_CAPS "Lock"
33+#define XKB_MOD_NAME_CTRL "Control"
34+#define XKB_MOD_NAME_MOD1 "Mod1"
35+#define XKB_MOD_NAME_MOD2 "Mod2"
36+#define XKB_MOD_NAME_MOD3 "Mod3"
37+#define XKB_MOD_NAME_MOD4 "Mod4"
38+#define XKB_MOD_NAME_MOD5 "Mod5"
39+/** @} */
40+
41+/**
42+ * @defgroup virtual-modifier-names Virtual modifiers names
43+ *
44+ * Common [*virtual* modifiers][virtual modifiers], encoded in [xkeyboard-config]
45+ * in the [compat] and [symbols] files. They have been stable since the beginning
46+ * of the project and are unlikely to ever change.
47+ *
48+ * [virtual modifiers]: @ref virtual-modifier-def
49+ * [xkeyboard-config]: https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config
50+ * [compat]: @ref the-xkb_compat-section
51+ * [symbols]: @ref the-xkb_symbols-section
52+ *
53+ * @{
54+ */
55+/** @since 1.8.0 */
56+#define XKB_VMOD_NAME_ALT "Alt"
57+/** @since 1.8.0 */
58+#define XKB_VMOD_NAME_HYPER "Hyper"
59+/** @since 1.8.0 */
60+#define XKB_VMOD_NAME_LEVEL3 "LevelThree"
61+/** @since 1.8.0 */
62+#define XKB_VMOD_NAME_LEVEL5 "LevelFive"
63+/** @since 1.8.0 */
64+#define XKB_VMOD_NAME_META "Meta"
65+/** @since 1.8.0 */
66+#define XKB_VMOD_NAME_NUM "NumLock"
67+/** @since 1.8.0 */
68+#define XKB_VMOD_NAME_SCROLL "ScrollLock"
69+/** @since 1.8.0 */
70+#define XKB_VMOD_NAME_SUPER "Super"
71+/** @} */
72+
73+/**
74+ * @defgroup legacy-virtual-modifier-names Legacy virtual modifier names
75+ *
76+ * Usual [*virtual* modifiers][virtual modifiers] mappings to
77+ * [*real* modifiers][real modifiers].
78+ *
79+ * @deprecated Use @ref virtual-modifier-names instead
80+ *
81+ * [virtual modifiers]: @ref virtual-modifier-def
82+ * [real modifiers]: @ref real-modifier-def
83+ *
84+ * @{
85+ */
86+/**
87+ * Usual [*real* modifier][real modifier] for the
88+ * [*virtual* modifier][virtual modifier] `Alt`.
89+ *
90+ * @deprecated Use `::XKB_VMOD_NAME_ALT` instead.
91+ * @since 1.10: deprecated
92+ *
93+ * [virtual modifier]: @ref virtual-modifier-def
94+ * [real modifier]: @ref real-modifier-def
95+ */
96+#define XKB_MOD_NAME_ALT "Mod1"
97+/**
98+ * Usual [*real* modifier][real modifier] for the
99+ * [*virtual* modifier][virtual modifier] `Super`.
100+ *
101+ * @deprecated Use `::XKB_VMOD_NAME_SUPER` instead.
102+ * @since 1.10: deprecated
103+ *
104+ * [virtual modifier]: @ref virtual-modifier-def
105+ * [real modifier]: @ref real-modifier-def
106+ */
107+#define XKB_MOD_NAME_LOGO "Mod4"
108+/**
109+ * Usual [*real* modifier][real modifier] for the
110+ * [*virtual* modifier][virtual modifier] `NumLock`.
111+ *
112+ * @deprecated Use `::XKB_VMOD_NAME_NUM` instead.
113+ * @since 1.10: deprecated
114+ *
115+ * [virtual modifier]: @ref virtual-modifier-def
116+ * [real modifier]: @ref real-modifier-def
117+ */
118+#define XKB_MOD_NAME_NUM "Mod2"
119+/** @} */
120+
121+/**
122+ * @defgroup led-names LEDs names
123+ *
124+ * LEDs names are encoded in [xkeyboard-config], in the [keycodes] and [compat]
125+ * files. They have been stable since the beginning of the project and are
126+ * unlikely to ever change.
127+ *
128+ * [xkeyboard-config]: https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config
129+ * [keycodes]: @ref the-xkb_keycodes-section
130+ * [compat]: @ref the-xkb_compat-section
131+ *
132+ * @{
133+ */
134+#define XKB_LED_NAME_NUM "Num Lock"
135+#define XKB_LED_NAME_CAPS "Caps Lock"
136+#define XKB_LED_NAME_SCROLL "Scroll Lock"
137+/** @since 1.8.0 */
138+#define XKB_LED_NAME_COMPOSE "Compose"
139+#define XKB_LED_NAME_KANA "Kana"
140+/** @} */
141+
142+/** @} */
143+
144+#endif
@@ -0,0 +1,144 @@
1+/*
2+ * Copyright © 2012 Intel Corporation
3+ * SPDX-License-Identifier: MIT
4+ *
5+ * Author: Daniel Stone <daniel@fooishbar.org>
6+ */
7+
8+#ifndef _XKBCOMMON_NAMES_H
9+#define _XKBCOMMON_NAMES_H
10+
11+/**
12+ * @file
13+ * @brief Predefined names for common modifiers and LEDs.
14+ */
15+
16+/**
17+ * @defgroup modifier-names Predefined names for common modifiers and LEDs
18+ *
19+ * @{
20+ */
21+
22+/**
23+ * @defgroup real-modifier-names Real modifiers names
24+ *
25+ * [*Real* modifiers][real modifier] names are hardcoded in libxkbcommon.
26+ *
27+ * [real modifier]: @ref real-modifier-def
28+ *
29+ * @{
30+ */
31+#define XKB_MOD_NAME_SHIFT "Shift"
32+#define XKB_MOD_NAME_CAPS "Lock"
33+#define XKB_MOD_NAME_CTRL "Control"
34+#define XKB_MOD_NAME_MOD1 "Mod1"
35+#define XKB_MOD_NAME_MOD2 "Mod2"
36+#define XKB_MOD_NAME_MOD3 "Mod3"
37+#define XKB_MOD_NAME_MOD4 "Mod4"
38+#define XKB_MOD_NAME_MOD5 "Mod5"
39+/** @} */
40+
41+/**
42+ * @defgroup virtual-modifier-names Virtual modifiers names
43+ *
44+ * Common [*virtual* modifiers][virtual modifiers], encoded in [xkeyboard-config]
45+ * in the [compat] and [symbols] files. They have been stable since the beginning
46+ * of the project and are unlikely to ever change.
47+ *
48+ * [virtual modifiers]: @ref virtual-modifier-def
49+ * [xkeyboard-config]: https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config
50+ * [compat]: @ref the-xkb_compat-section
51+ * [symbols]: @ref the-xkb_symbols-section
52+ *
53+ * @{
54+ */
55+/** @since 1.8.0 */
56+#define XKB_VMOD_NAME_ALT "Alt"
57+/** @since 1.8.0 */
58+#define XKB_VMOD_NAME_HYPER "Hyper"
59+/** @since 1.8.0 */
60+#define XKB_VMOD_NAME_LEVEL3 "LevelThree"
61+/** @since 1.8.0 */
62+#define XKB_VMOD_NAME_LEVEL5 "LevelFive"
63+/** @since 1.8.0 */
64+#define XKB_VMOD_NAME_META "Meta"
65+/** @since 1.8.0 */
66+#define XKB_VMOD_NAME_NUM "NumLock"
67+/** @since 1.8.0 */
68+#define XKB_VMOD_NAME_SCROLL "ScrollLock"
69+/** @since 1.8.0 */
70+#define XKB_VMOD_NAME_SUPER "Super"
71+/** @} */
72+
73+/**
74+ * @defgroup legacy-virtual-modifier-names Legacy virtual modifier names
75+ *
76+ * Usual [*virtual* modifiers][virtual modifiers] mappings to
77+ * [*real* modifiers][real modifiers].
78+ *
79+ * @deprecated Use @ref virtual-modifier-names instead
80+ *
81+ * [virtual modifiers]: @ref virtual-modifier-def
82+ * [real modifiers]: @ref real-modifier-def
83+ *
84+ * @{
85+ */
86+/**
87+ * Usual [*real* modifier][real modifier] for the
88+ * [*virtual* modifier][virtual modifier] `Alt`.
89+ *
90+ * @deprecated Use `::XKB_VMOD_NAME_ALT` instead.
91+ * @since 1.10: deprecated
92+ *
93+ * [virtual modifier]: @ref virtual-modifier-def
94+ * [real modifier]: @ref real-modifier-def
95+ */
96+#define XKB_MOD_NAME_ALT "Mod1"
97+/**
98+ * Usual [*real* modifier][real modifier] for the
99+ * [*virtual* modifier][virtual modifier] `Super`.
100+ *
101+ * @deprecated Use `::XKB_VMOD_NAME_SUPER` instead.
102+ * @since 1.10: deprecated
103+ *
104+ * [virtual modifier]: @ref virtual-modifier-def
105+ * [real modifier]: @ref real-modifier-def
106+ */
107+#define XKB_MOD_NAME_LOGO "Mod4"
108+/**
109+ * Usual [*real* modifier][real modifier] for the
110+ * [*virtual* modifier][virtual modifier] `NumLock`.
111+ *
112+ * @deprecated Use `::XKB_VMOD_NAME_NUM` instead.
113+ * @since 1.10: deprecated
114+ *
115+ * [virtual modifier]: @ref virtual-modifier-def
116+ * [real modifier]: @ref real-modifier-def
117+ */
118+#define XKB_MOD_NAME_NUM "Mod2"
119+/** @} */
120+
121+/**
122+ * @defgroup led-names LEDs names
123+ *
124+ * LEDs names are encoded in [xkeyboard-config], in the [keycodes] and [compat]
125+ * files. They have been stable since the beginning of the project and are
126+ * unlikely to ever change.
127+ *
128+ * [xkeyboard-config]: https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config
129+ * [keycodes]: @ref the-xkb_keycodes-section
130+ * [compat]: @ref the-xkb_compat-section
131+ *
132+ * @{
133+ */
134+#define XKB_LED_NAME_NUM "Num Lock"
135+#define XKB_LED_NAME_CAPS "Caps Lock"
136+#define XKB_LED_NAME_SCROLL "Scroll Lock"
137+/** @since 1.8.0 */
138+#define XKB_LED_NAME_COMPOSE "Compose"
139+#define XKB_LED_NAME_KANA "Kana"
140+/** @} */
141+
142+/** @} */
143+
144+#endif
A upstream/xkbcommon.h +2598 -0 large diff (2598 lines changed): collapsed
A xkbcommon.go +492 -0
@@ -0,0 +1,492 @@
1+// Package xkbcommon interprets Wayland keyboard maps with libxkbcommon,
2+// loaded at runtime without cgo. Close owned values when they are no longer used.
3+package xkbcommon
4+
5+import (
6+ "errors"
7+ "fmt"
8+ "runtime"
9+ "strings"
10+ "sync"
11+ "syscall"
12+ "unsafe"
13+
14+ "github.com/bnema/purego-xkbcommon/raw"
15+)
16+
17+var ErrClosed = errors.New("xkbcommon: closed handle")
18+
19+func cstr(s string) []byte { return append([]byte(s), 0) }
20+func ptr(b []byte) uintptr {
21+ if len(b) == 0 {
22+ return 0
23+ }
24+ return uintptr(unsafe.Pointer(&b[0]))
25+}
26+func validString(s string) error {
27+ if strings.IndexByte(s, 0) >= 0 {
28+ return errors.New("xkbcommon: embedded NUL")
29+ }
30+ return nil
31+}
32+
33+// Keycodes from wl_keyboard are evdev codes; add 8 before calling State.KeySym or State.UTF8.
34+func WaylandKeycode(evdev uint32) uint32 { return evdev + 8 }
35+
36+type Context struct {
37+ mu sync.Mutex
38+ h uintptr
39+}
40+
41+func NewContext() (*Context, error) {
42+ if err := Available(); err != nil {
43+ return nil, err
44+ }
45+ h := loader.f.Xkb_context_new(0)
46+ if h == 0 {
47+ return nil, errors.New("xkbcommon: create context failed")
48+ }
49+ return &Context{h: h}, nil
50+}
51+func (c *Context) Close() error {
52+ if c == nil {
53+ return nil
54+ }
55+ c.mu.Lock()
56+ defer c.mu.Unlock()
57+ if c.h != 0 {
58+ loader.f.Xkb_context_unref(c.h)
59+ c.h = 0
60+ }
61+ return nil
62+}
63+func (c *Context) retain() (uintptr, error) {
64+ if c == nil {
65+ return 0, ErrClosed
66+ }
67+ c.mu.Lock()
68+ defer c.mu.Unlock()
69+ if c.h == 0 {
70+ return 0, ErrClosed
71+ }
72+ loader.f.Xkb_context_ref(c.h)
73+ return c.h, nil
74+}
75+func releaseContext(h uintptr) { loader.f.Xkb_context_unref(h) }
76+
77+type Keymap struct {
78+ mu sync.Mutex
79+ h, context uintptr
80+}
81+
82+func newKeymap(ctx *Context, build func(uintptr) uintptr) (*Keymap, error) {
83+ h, e := ctx.retain()
84+ if e != nil {
85+ return nil, e
86+ }
87+ k := build(h)
88+ if k == 0 {
89+ releaseContext(h)
90+ return nil, errors.New("xkbcommon: invalid keymap")
91+ }
92+ return &Keymap{h: k, context: h}, nil
93+}
94+
95+// NewKeymapString compiles a NUL-free xkb_v1 text keymap.
96+func (c *Context) NewKeymapString(text string) (*Keymap, error) {
97+ if e := validString(text); e != nil {
98+ return nil, e
99+ }
100+ b := cstr(text)
101+ return newKeymap(c, func(h uintptr) uintptr {
102+ v := loader.f.Xkb_keymap_new_from_string(h, ptr(b), raw.XKB_KEYMAP_FORMAT_TEXT_V1, 0)
103+ runtime.KeepAlive(b)
104+ return v
105+ })
106+}
107+
108+// NewKeymapRules compiles an evdev rules keymap. Empty rules/model/variant/options
109+// use libxkbcommon defaults; layout is e.g. "us", "fr", "de", or "us,de".
110+func (c *Context) NewKeymapRules(rules, model, layout, variant, options string) (*Keymap, error) {
111+ args := []string{rules, model, layout, variant, options}
112+ var b [5][]byte
113+ var n raw.RuleNames
114+ p := []*uintptr{&n.Rules, &n.Model, &n.Layout, &n.Variant, &n.Options}
115+ for i, s := range args {
116+ if e := validString(s); e != nil {
117+ return nil, e
118+ }
119+ if s != "" {
120+ b[i] = cstr(s)
121+ *p[i] = ptr(b[i])
122+ }
123+ }
124+ return newKeymap(c, func(h uintptr) uintptr {
125+ v := loader.f.Xkb_keymap_new_from_names(h, uintptr(unsafe.Pointer(&n)), 0)
126+ runtime.KeepAlive(b)
127+ runtime.KeepAlive(n)
128+ return v
129+ })
130+}
131+
132+// NewKeymapFD maps exactly size bytes read-only and privately, then compiles the
133+// Wayland xkb_v1 format. The caller retains ownership of fd, including on error;
134+// this function never closes it. The mapping is removed before return. size must
135+// include the terminating NUL required by the Wayland protocol.
136+func (c *Context) NewKeymapFD(fd int, size int) (*Keymap, error) {
137+ if fd < 0 || size < 1 || size > 64<<20 {
138+ return nil, errors.New("xkbcommon: invalid keymap FD or size (limit 64 MiB)")
139+ }
140+ var stat syscall.Stat_t
141+ if e := syscall.Fstat(fd, &stat); e != nil {
142+ return nil, fmt.Errorf("xkbcommon: stat keymap FD: %w", e)
143+ }
144+ if stat.Mode&syscall.S_IFMT != syscall.S_IFREG || stat.Size < int64(size) {
145+ return nil, errors.New("xkbcommon: keymap FD is not a regular file of the supplied size")
146+ }
147+ b, e := syscall.Mmap(fd, 0, size, syscall.PROT_READ, syscall.MAP_PRIVATE)
148+ if e != nil {
149+ return nil, fmt.Errorf("xkbcommon: mmap keymap: %w", e)
150+ }
151+ defer syscall.Munmap(b)
152+ if b[len(b)-1] != 0 {
153+ return nil, errors.New("xkbcommon: Wayland keymap not NUL terminated")
154+ }
155+ return newKeymap(c, func(h uintptr) uintptr {
156+ v := loader.f.Xkb_keymap_new_from_buffer(h, ptr(b), uintptr(size-1), raw.XKB_KEYMAP_FORMAT_TEXT_V1, 0)
157+ runtime.KeepAlive(b)
158+ return v
159+ })
160+}
161+func (k *Keymap) Close() error {
162+ if k == nil {
163+ return nil
164+ }
165+ k.mu.Lock()
166+ h, ctx := k.h, k.context
167+ k.h = 0
168+ k.context = 0
169+ if h != 0 {
170+ loader.f.Xkb_keymap_unref(h)
171+ }
172+ k.mu.Unlock()
173+ if ctx != 0 {
174+ releaseContext(ctx)
175+ }
176+ return nil
177+}
178+func (k *Keymap) retain() (uintptr, error) {
179+ if k == nil {
180+ return 0, ErrClosed
181+ }
182+ k.mu.Lock()
183+ defer k.mu.Unlock()
184+ if k.h == 0 {
185+ return 0, ErrClosed
186+ }
187+ loader.f.Xkb_keymap_ref(k.h)
188+ return k.h, nil
189+}
190+func (k *Keymap) ModIndex(name string) (uint32, error) {
191+ if e := validString(name); e != nil {
192+ return 0, e
193+ }
194+ if k == nil {
195+ return 0, ErrClosed
196+ }
197+ b := cstr(name)
198+ k.mu.Lock()
199+ defer k.mu.Unlock()
200+ if k.h == 0 {
201+ return 0, ErrClosed
202+ }
203+ v := loader.f.Xkb_keymap_mod_get_index(k.h, ptr(b))
204+ runtime.KeepAlive(b)
205+ return v, nil
206+}
207+func (k *Keymap) LayoutCount() (uint32, error) {
208+ if k == nil {
209+ return 0, ErrClosed
210+ }
211+ k.mu.Lock()
212+ defer k.mu.Unlock()
213+ if k.h == 0 {
214+ return 0, ErrClosed
215+ }
216+ return loader.f.Xkb_keymap_num_layouts(k.h), nil
217+}
218+func (k *Keymap) NewState() (*State, error) {
219+ h, e := k.retain()
220+ if e != nil {
221+ return nil, e
222+ }
223+ s := loader.f.Xkb_state_new(h)
224+ if s == 0 {
225+ loader.f.Xkb_keymap_unref(h)
226+ return nil, errors.New("xkbcommon: create state failed")
227+ }
228+ return &State{h: s, keymap: h}, nil
229+}
230+
231+type State struct {
232+ mu sync.Mutex
233+ h, keymap uintptr
234+}
235+
236+func (s *State) Close() error {
237+ if s == nil {
238+ return nil
239+ }
240+ s.mu.Lock()
241+ h, k := s.h, s.keymap
242+ s.h = 0
243+ s.keymap = 0
244+ if h != 0 {
245+ loader.f.Xkb_state_unref(h)
246+ }
247+ s.mu.Unlock()
248+ if k != 0 {
249+ loader.f.Xkb_keymap_unref(k)
250+ }
251+ return nil
252+}
253+
254+// UpdateMask accepts depressed/latched/locked modifier masks and layout group indices
255+// in the order supplied by wl_keyboard.modifiers. It returns changed components.
256+func (s *State) UpdateMask(depressed, latched, locked, groupDepressed, groupLatched, groupLocked uint32) (uint32, error) {
257+ if s == nil {
258+ return 0, ErrClosed
259+ }
260+ s.mu.Lock()
261+ defer s.mu.Unlock()
262+ if s.h == 0 {
263+ return 0, ErrClosed
264+ }
265+ return loader.f.Xkb_state_update_mask(s.h, depressed, latched, locked, groupDepressed, groupLatched, groupLocked), nil
266+}
267+func (s *State) Layout() (uint32, error) {
268+ if s == nil {
269+ return 0, ErrClosed
270+ }
271+ s.mu.Lock()
272+ defer s.mu.Unlock()
273+ if s.h == 0 {
274+ return 0, ErrClosed
275+ }
276+ return loader.f.Xkb_state_serialize_layout(s.h, raw.XKB_STATE_LAYOUT_EFFECTIVE), nil
277+}
278+func (s *State) Mods() (uint32, error) {
279+ if s == nil {
280+ return 0, ErrClosed
281+ }
282+ s.mu.Lock()
283+ defer s.mu.Unlock()
284+ if s.h == 0 {
285+ return 0, ErrClosed
286+ }
287+ return loader.f.Xkb_state_serialize_mods(s.h, raw.XKB_STATE_MODS_EFFECTIVE), nil
288+}
289+func (s *State) KeySym(keycode uint32) (uint32, error) {
290+ if s == nil {
291+ return 0, ErrClosed
292+ }
293+ s.mu.Lock()
294+ defer s.mu.Unlock()
295+ if s.h == 0 {
296+ return 0, ErrClosed
297+ }
298+ return loader.f.Xkb_state_key_get_one_sym(s.h, keycode), nil
299+}
300+
301+// sizedUTF8 probes the required byte count (excluding NUL) and retries if it grows.
302+func sizedUTF8(call func(uintptr, uintptr) int32) (string, error) {
303+ n := call(0, 0)
304+ if n < 0 {
305+ return "", errors.New("xkbcommon: UTF-8 conversion failed")
306+ }
307+ if n == 0 {
308+ return "", nil
309+ }
310+ for attempt := 0; attempt < 3; attempt++ {
311+ if n > 1<<20 {
312+ return "", errors.New("xkbcommon: UTF-8 result exceeds 1 MiB")
313+ }
314+ b := make([]byte, int(n)+1)
315+ got := call(ptr(b), uintptr(len(b)))
316+ runtime.KeepAlive(b)
317+ if got < 0 {
318+ return "", errors.New("xkbcommon: UTF-8 conversion failed")
319+ }
320+ if int(got) < len(b) {
321+ return string(b[:got]), nil
322+ }
323+ n = got
324+ }
325+ return "", errors.New("xkbcommon: UTF-8 result changed during sizing")
326+}
327+func (s *State) UTF8(keycode uint32) (string, error) {
328+ if s == nil {
329+ return "", ErrClosed
330+ }
331+ s.mu.Lock()
332+ defer s.mu.Unlock()
333+ if s.h == 0 {
334+ return "", ErrClosed
335+ }
336+ return sizedUTF8(func(p, n uintptr) int32 { return loader.f.Xkb_state_key_get_utf8(s.h, keycode, p, n) })
337+}
338+func KeysymName(sym uint32) (string, error) {
339+ if e := Available(); e != nil {
340+ return "", e
341+ }
342+ return sizedUTF8(func(p, n uintptr) int32 { return loader.f.Xkb_keysym_get_name(sym, p, n) })
343+}
344+func KeysymFromName(name string) (uint32, error) {
345+ if e := Available(); e != nil {
346+ return 0, e
347+ }
348+ if e := validString(name); e != nil {
349+ return 0, e
350+ }
351+ b := cstr(name)
352+ v := loader.f.Xkb_keysym_from_name(ptr(b), 0)
353+ runtime.KeepAlive(b)
354+ return v, nil
355+}
356+
357+type ComposeTable struct {
358+ mu sync.Mutex
359+ h, context uintptr
360+}
361+
362+func (c *Context) NewComposeTable(locale string) (*ComposeTable, error) {
363+ if e := validString(locale); e != nil {
364+ return nil, e
365+ }
366+ ctx, e := c.retain()
367+ if e != nil {
368+ return nil, e
369+ }
370+ b := cstr(locale)
371+ h := loader.f.Xkb_compose_table_new_from_locale(ctx, ptr(b), 0)
372+ runtime.KeepAlive(b)
373+ if h == 0 {
374+ releaseContext(ctx)
375+ return nil, fmt.Errorf("xkbcommon: no compose table for %q", locale)
376+ }
377+ return &ComposeTable{h: h, context: ctx}, nil
378+}
379+func (t *ComposeTable) Close() error {
380+ if t == nil {
381+ return nil
382+ }
383+ t.mu.Lock()
384+ h, ctx := t.h, t.context
385+ t.h = 0
386+ t.context = 0
387+ if h != 0 {
388+ loader.f.Xkb_compose_table_unref(h)
389+ }
390+ t.mu.Unlock()
391+ if ctx != 0 {
392+ releaseContext(ctx)
393+ }
394+ return nil
395+}
396+func (t *ComposeTable) NewState() (*ComposeState, error) {
397+ if t == nil {
398+ return nil, ErrClosed
399+ }
400+ t.mu.Lock()
401+ if t.h == 0 {
402+ t.mu.Unlock()
403+ return nil, ErrClosed
404+ }
405+ table := loader.f.Xkb_compose_table_ref(t.h)
406+ t.mu.Unlock()
407+ h := loader.f.Xkb_compose_state_new(table, 0)
408+ if h == 0 {
409+ loader.f.Xkb_compose_table_unref(table)
410+ return nil, errors.New("xkbcommon: create compose state failed")
411+ }
412+ return &ComposeState{h: h, table: table}, nil
413+}
414+
415+type ComposeState struct {
416+ mu sync.Mutex
417+ h, table uintptr
418+}
419+
420+func (s *ComposeState) Close() error {
421+ if s == nil {
422+ return nil
423+ }
424+ s.mu.Lock()
425+ h, t := s.h, s.table
426+ s.h = 0
427+ s.table = 0
428+ if h != 0 {
429+ loader.f.Xkb_compose_state_unref(h)
430+ }
431+ s.mu.Unlock()
432+ if t != 0 {
433+ loader.f.Xkb_compose_table_unref(t)
434+ }
435+ return nil
436+}
437+func (s *ComposeState) Feed(sym uint32) (uint32, error) {
438+ if s == nil {
439+ return 0, ErrClosed
440+ }
441+ s.mu.Lock()
442+ defer s.mu.Unlock()
443+ if s.h == 0 {
444+ return 0, ErrClosed
445+ }
446+ return loader.f.Xkb_compose_state_feed(s.h, sym), nil
447+}
448+func (s *ComposeState) Status() (uint32, error) {
449+ if s == nil {
450+ return 0, ErrClosed
451+ }
452+ s.mu.Lock()
453+ defer s.mu.Unlock()
454+ if s.h == 0 {
455+ return 0, ErrClosed
456+ }
457+ return loader.f.Xkb_compose_state_get_status(s.h), nil
458+}
459+func (s *ComposeState) UTF8() (string, error) {
460+ if s == nil {
461+ return "", ErrClosed
462+ }
463+ s.mu.Lock()
464+ defer s.mu.Unlock()
465+ if s.h == 0 {
466+ return "", ErrClosed
467+ }
468+ return sizedUTF8(func(p, n uintptr) int32 { return loader.f.Xkb_compose_state_get_utf8(s.h, p, n) })
469+}
470+func (s *ComposeState) KeySym() (uint32, error) {
471+ if s == nil {
472+ return 0, ErrClosed
473+ }
474+ s.mu.Lock()
475+ defer s.mu.Unlock()
476+ if s.h == 0 {
477+ return 0, ErrClosed
478+ }
479+ return loader.f.Xkb_compose_state_get_one_sym(s.h), nil
480+}
481+func (s *ComposeState) Reset() error {
482+ if s == nil {
483+ return ErrClosed
484+ }
485+ s.mu.Lock()
486+ defer s.mu.Unlock()
487+ if s.h == 0 {
488+ return ErrClosed
489+ }
490+ loader.f.Xkb_compose_state_reset(s.h)
491+ return nil
492+}
@@ -0,0 +1,492 @@
1+// Package xkbcommon interprets Wayland keyboard maps with libxkbcommon,
2+// loaded at runtime without cgo. Close owned values when they are no longer used.
3+package xkbcommon
4+
5+import (
6+ "errors"
7+ "fmt"
8+ "runtime"
9+ "strings"
10+ "sync"
11+ "syscall"
12+ "unsafe"
13+
14+ "github.com/bnema/purego-xkbcommon/raw"
15+)
16+
17+var ErrClosed = errors.New("xkbcommon: closed handle")
18+
19+func cstr(s string) []byte { return append([]byte(s), 0) }
20+func ptr(b []byte) uintptr {
21+ if len(b) == 0 {
22+ return 0
23+ }
24+ return uintptr(unsafe.Pointer(&b[0]))
25+}
26+func validString(s string) error {
27+ if strings.IndexByte(s, 0) >= 0 {
28+ return errors.New("xkbcommon: embedded NUL")
29+ }
30+ return nil
31+}
32+
33+// Keycodes from wl_keyboard are evdev codes; add 8 before calling State.KeySym or State.UTF8.
34+func WaylandKeycode(evdev uint32) uint32 { return evdev + 8 }
35+
36+type Context struct {
37+ mu sync.Mutex
38+ h uintptr
39+}
40+
41+func NewContext() (*Context, error) {
42+ if err := Available(); err != nil {
43+ return nil, err
44+ }
45+ h := loader.f.Xkb_context_new(0)
46+ if h == 0 {
47+ return nil, errors.New("xkbcommon: create context failed")
48+ }
49+ return &Context{h: h}, nil
50+}
51+func (c *Context) Close() error {
52+ if c == nil {
53+ return nil
54+ }
55+ c.mu.Lock()
56+ defer c.mu.Unlock()
57+ if c.h != 0 {
58+ loader.f.Xkb_context_unref(c.h)
59+ c.h = 0
60+ }
61+ return nil
62+}
63+func (c *Context) retain() (uintptr, error) {
64+ if c == nil {
65+ return 0, ErrClosed
66+ }
67+ c.mu.Lock()
68+ defer c.mu.Unlock()
69+ if c.h == 0 {
70+ return 0, ErrClosed
71+ }
72+ loader.f.Xkb_context_ref(c.h)
73+ return c.h, nil
74+}
75+func releaseContext(h uintptr) { loader.f.Xkb_context_unref(h) }
76+
77+type Keymap struct {
78+ mu sync.Mutex
79+ h, context uintptr
80+}
81+
82+func newKeymap(ctx *Context, build func(uintptr) uintptr) (*Keymap, error) {
83+ h, e := ctx.retain()
84+ if e != nil {
85+ return nil, e
86+ }
87+ k := build(h)
88+ if k == 0 {
89+ releaseContext(h)
90+ return nil, errors.New("xkbcommon: invalid keymap")
91+ }
92+ return &Keymap{h: k, context: h}, nil
93+}
94+
95+// NewKeymapString compiles a NUL-free xkb_v1 text keymap.
96+func (c *Context) NewKeymapString(text string) (*Keymap, error) {
97+ if e := validString(text); e != nil {
98+ return nil, e
99+ }
100+ b := cstr(text)
101+ return newKeymap(c, func(h uintptr) uintptr {
102+ v := loader.f.Xkb_keymap_new_from_string(h, ptr(b), raw.XKB_KEYMAP_FORMAT_TEXT_V1, 0)
103+ runtime.KeepAlive(b)
104+ return v
105+ })
106+}
107+
108+// NewKeymapRules compiles an evdev rules keymap. Empty rules/model/variant/options
109+// use libxkbcommon defaults; layout is e.g. "us", "fr", "de", or "us,de".
110+func (c *Context) NewKeymapRules(rules, model, layout, variant, options string) (*Keymap, error) {
111+ args := []string{rules, model, layout, variant, options}
112+ var b [5][]byte
113+ var n raw.RuleNames
114+ p := []*uintptr{&n.Rules, &n.Model, &n.Layout, &n.Variant, &n.Options}
115+ for i, s := range args {
116+ if e := validString(s); e != nil {
117+ return nil, e
118+ }
119+ if s != "" {
120+ b[i] = cstr(s)
121+ *p[i] = ptr(b[i])
122+ }
123+ }
124+ return newKeymap(c, func(h uintptr) uintptr {
125+ v := loader.f.Xkb_keymap_new_from_names(h, uintptr(unsafe.Pointer(&n)), 0)
126+ runtime.KeepAlive(b)
127+ runtime.KeepAlive(n)
128+ return v
129+ })
130+}
131+
132+// NewKeymapFD maps exactly size bytes read-only and privately, then compiles the
133+// Wayland xkb_v1 format. The caller retains ownership of fd, including on error;
134+// this function never closes it. The mapping is removed before return. size must
135+// include the terminating NUL required by the Wayland protocol.
136+func (c *Context) NewKeymapFD(fd int, size int) (*Keymap, error) {
137+ if fd < 0 || size < 1 || size > 64<<20 {
138+ return nil, errors.New("xkbcommon: invalid keymap FD or size (limit 64 MiB)")
139+ }
140+ var stat syscall.Stat_t
141+ if e := syscall.Fstat(fd, &stat); e != nil {
142+ return nil, fmt.Errorf("xkbcommon: stat keymap FD: %w", e)
143+ }
144+ if stat.Mode&syscall.S_IFMT != syscall.S_IFREG || stat.Size < int64(size) {
145+ return nil, errors.New("xkbcommon: keymap FD is not a regular file of the supplied size")
146+ }
147+ b, e := syscall.Mmap(fd, 0, size, syscall.PROT_READ, syscall.MAP_PRIVATE)
148+ if e != nil {
149+ return nil, fmt.Errorf("xkbcommon: mmap keymap: %w", e)
150+ }
151+ defer syscall.Munmap(b)
152+ if b[len(b)-1] != 0 {
153+ return nil, errors.New("xkbcommon: Wayland keymap not NUL terminated")
154+ }
155+ return newKeymap(c, func(h uintptr) uintptr {
156+ v := loader.f.Xkb_keymap_new_from_buffer(h, ptr(b), uintptr(size-1), raw.XKB_KEYMAP_FORMAT_TEXT_V1, 0)
157+ runtime.KeepAlive(b)
158+ return v
159+ })
160+}
161+func (k *Keymap) Close() error {
162+ if k == nil {
163+ return nil
164+ }
165+ k.mu.Lock()
166+ h, ctx := k.h, k.context
167+ k.h = 0
168+ k.context = 0
169+ if h != 0 {
170+ loader.f.Xkb_keymap_unref(h)
171+ }
172+ k.mu.Unlock()
173+ if ctx != 0 {
174+ releaseContext(ctx)
175+ }
176+ return nil
177+}
178+func (k *Keymap) retain() (uintptr, error) {
179+ if k == nil {
180+ return 0, ErrClosed
181+ }
182+ k.mu.Lock()
183+ defer k.mu.Unlock()
184+ if k.h == 0 {
185+ return 0, ErrClosed
186+ }
187+ loader.f.Xkb_keymap_ref(k.h)
188+ return k.h, nil
189+}
190+func (k *Keymap) ModIndex(name string) (uint32, error) {
191+ if e := validString(name); e != nil {
192+ return 0, e
193+ }
194+ if k == nil {
195+ return 0, ErrClosed
196+ }
197+ b := cstr(name)
198+ k.mu.Lock()
199+ defer k.mu.Unlock()
200+ if k.h == 0 {
201+ return 0, ErrClosed
202+ }
203+ v := loader.f.Xkb_keymap_mod_get_index(k.h, ptr(b))
204+ runtime.KeepAlive(b)
205+ return v, nil
206+}
207+func (k *Keymap) LayoutCount() (uint32, error) {
208+ if k == nil {
209+ return 0, ErrClosed
210+ }
211+ k.mu.Lock()
212+ defer k.mu.Unlock()
213+ if k.h == 0 {
214+ return 0, ErrClosed
215+ }
216+ return loader.f.Xkb_keymap_num_layouts(k.h), nil
217+}
218+func (k *Keymap) NewState() (*State, error) {
219+ h, e := k.retain()
220+ if e != nil {
221+ return nil, e
222+ }
223+ s := loader.f.Xkb_state_new(h)
224+ if s == 0 {
225+ loader.f.Xkb_keymap_unref(h)
226+ return nil, errors.New("xkbcommon: create state failed")
227+ }
228+ return &State{h: s, keymap: h}, nil
229+}
230+
231+type State struct {
232+ mu sync.Mutex
233+ h, keymap uintptr
234+}
235+
236+func (s *State) Close() error {
237+ if s == nil {
238+ return nil
239+ }
240+ s.mu.Lock()
241+ h, k := s.h, s.keymap
242+ s.h = 0
243+ s.keymap = 0
244+ if h != 0 {
245+ loader.f.Xkb_state_unref(h)
246+ }
247+ s.mu.Unlock()
248+ if k != 0 {
249+ loader.f.Xkb_keymap_unref(k)
250+ }
251+ return nil
252+}
253+
254+// UpdateMask accepts depressed/latched/locked modifier masks and layout group indices
255+// in the order supplied by wl_keyboard.modifiers. It returns changed components.
256+func (s *State) UpdateMask(depressed, latched, locked, groupDepressed, groupLatched, groupLocked uint32) (uint32, error) {
257+ if s == nil {
258+ return 0, ErrClosed
259+ }
260+ s.mu.Lock()
261+ defer s.mu.Unlock()
262+ if s.h == 0 {
263+ return 0, ErrClosed
264+ }
265+ return loader.f.Xkb_state_update_mask(s.h, depressed, latched, locked, groupDepressed, groupLatched, groupLocked), nil
266+}
267+func (s *State) Layout() (uint32, error) {
268+ if s == nil {
269+ return 0, ErrClosed
270+ }
271+ s.mu.Lock()
272+ defer s.mu.Unlock()
273+ if s.h == 0 {
274+ return 0, ErrClosed
275+ }
276+ return loader.f.Xkb_state_serialize_layout(s.h, raw.XKB_STATE_LAYOUT_EFFECTIVE), nil
277+}
278+func (s *State) Mods() (uint32, error) {
279+ if s == nil {
280+ return 0, ErrClosed
281+ }
282+ s.mu.Lock()
283+ defer s.mu.Unlock()
284+ if s.h == 0 {
285+ return 0, ErrClosed
286+ }
287+ return loader.f.Xkb_state_serialize_mods(s.h, raw.XKB_STATE_MODS_EFFECTIVE), nil
288+}
289+func (s *State) KeySym(keycode uint32) (uint32, error) {
290+ if s == nil {
291+ return 0, ErrClosed
292+ }
293+ s.mu.Lock()
294+ defer s.mu.Unlock()
295+ if s.h == 0 {
296+ return 0, ErrClosed
297+ }
298+ return loader.f.Xkb_state_key_get_one_sym(s.h, keycode), nil
299+}
300+
301+// sizedUTF8 probes the required byte count (excluding NUL) and retries if it grows.
302+func sizedUTF8(call func(uintptr, uintptr) int32) (string, error) {
303+ n := call(0, 0)
304+ if n < 0 {
305+ return "", errors.New("xkbcommon: UTF-8 conversion failed")
306+ }
307+ if n == 0 {
308+ return "", nil
309+ }
310+ for attempt := 0; attempt < 3; attempt++ {
311+ if n > 1<<20 {
312+ return "", errors.New("xkbcommon: UTF-8 result exceeds 1 MiB")
313+ }
314+ b := make([]byte, int(n)+1)
315+ got := call(ptr(b), uintptr(len(b)))
316+ runtime.KeepAlive(b)
317+ if got < 0 {
318+ return "", errors.New("xkbcommon: UTF-8 conversion failed")
319+ }
320+ if int(got) < len(b) {
321+ return string(b[:got]), nil
322+ }
323+ n = got
324+ }
325+ return "", errors.New("xkbcommon: UTF-8 result changed during sizing")
326+}
327+func (s *State) UTF8(keycode uint32) (string, error) {
328+ if s == nil {
329+ return "", ErrClosed
330+ }
331+ s.mu.Lock()
332+ defer s.mu.Unlock()
333+ if s.h == 0 {
334+ return "", ErrClosed
335+ }
336+ return sizedUTF8(func(p, n uintptr) int32 { return loader.f.Xkb_state_key_get_utf8(s.h, keycode, p, n) })
337+}
338+func KeysymName(sym uint32) (string, error) {
339+ if e := Available(); e != nil {
340+ return "", e
341+ }
342+ return sizedUTF8(func(p, n uintptr) int32 { return loader.f.Xkb_keysym_get_name(sym, p, n) })
343+}
344+func KeysymFromName(name string) (uint32, error) {
345+ if e := Available(); e != nil {
346+ return 0, e
347+ }
348+ if e := validString(name); e != nil {
349+ return 0, e
350+ }
351+ b := cstr(name)
352+ v := loader.f.Xkb_keysym_from_name(ptr(b), 0)
353+ runtime.KeepAlive(b)
354+ return v, nil
355+}
356+
357+type ComposeTable struct {
358+ mu sync.Mutex
359+ h, context uintptr
360+}
361+
362+func (c *Context) NewComposeTable(locale string) (*ComposeTable, error) {
363+ if e := validString(locale); e != nil {
364+ return nil, e
365+ }
366+ ctx, e := c.retain()
367+ if e != nil {
368+ return nil, e
369+ }
370+ b := cstr(locale)
371+ h := loader.f.Xkb_compose_table_new_from_locale(ctx, ptr(b), 0)
372+ runtime.KeepAlive(b)
373+ if h == 0 {
374+ releaseContext(ctx)
375+ return nil, fmt.Errorf("xkbcommon: no compose table for %q", locale)
376+ }
377+ return &ComposeTable{h: h, context: ctx}, nil
378+}
379+func (t *ComposeTable) Close() error {
380+ if t == nil {
381+ return nil
382+ }
383+ t.mu.Lock()
384+ h, ctx := t.h, t.context
385+ t.h = 0
386+ t.context = 0
387+ if h != 0 {
388+ loader.f.Xkb_compose_table_unref(h)
389+ }
390+ t.mu.Unlock()
391+ if ctx != 0 {
392+ releaseContext(ctx)
393+ }
394+ return nil
395+}
396+func (t *ComposeTable) NewState() (*ComposeState, error) {
397+ if t == nil {
398+ return nil, ErrClosed
399+ }
400+ t.mu.Lock()
401+ if t.h == 0 {
402+ t.mu.Unlock()
403+ return nil, ErrClosed
404+ }
405+ table := loader.f.Xkb_compose_table_ref(t.h)
406+ t.mu.Unlock()
407+ h := loader.f.Xkb_compose_state_new(table, 0)
408+ if h == 0 {
409+ loader.f.Xkb_compose_table_unref(table)
410+ return nil, errors.New("xkbcommon: create compose state failed")
411+ }
412+ return &ComposeState{h: h, table: table}, nil
413+}
414+
415+type ComposeState struct {
416+ mu sync.Mutex
417+ h, table uintptr
418+}
419+
420+func (s *ComposeState) Close() error {
421+ if s == nil {
422+ return nil
423+ }
424+ s.mu.Lock()
425+ h, t := s.h, s.table
426+ s.h = 0
427+ s.table = 0
428+ if h != 0 {
429+ loader.f.Xkb_compose_state_unref(h)
430+ }
431+ s.mu.Unlock()
432+ if t != 0 {
433+ loader.f.Xkb_compose_table_unref(t)
434+ }
435+ return nil
436+}
437+func (s *ComposeState) Feed(sym uint32) (uint32, error) {
438+ if s == nil {
439+ return 0, ErrClosed
440+ }
441+ s.mu.Lock()
442+ defer s.mu.Unlock()
443+ if s.h == 0 {
444+ return 0, ErrClosed
445+ }
446+ return loader.f.Xkb_compose_state_feed(s.h, sym), nil
447+}
448+func (s *ComposeState) Status() (uint32, error) {
449+ if s == nil {
450+ return 0, ErrClosed
451+ }
452+ s.mu.Lock()
453+ defer s.mu.Unlock()
454+ if s.h == 0 {
455+ return 0, ErrClosed
456+ }
457+ return loader.f.Xkb_compose_state_get_status(s.h), nil
458+}
459+func (s *ComposeState) UTF8() (string, error) {
460+ if s == nil {
461+ return "", ErrClosed
462+ }
463+ s.mu.Lock()
464+ defer s.mu.Unlock()
465+ if s.h == 0 {
466+ return "", ErrClosed
467+ }
468+ return sizedUTF8(func(p, n uintptr) int32 { return loader.f.Xkb_compose_state_get_utf8(s.h, p, n) })
469+}
470+func (s *ComposeState) KeySym() (uint32, error) {
471+ if s == nil {
472+ return 0, ErrClosed
473+ }
474+ s.mu.Lock()
475+ defer s.mu.Unlock()
476+ if s.h == 0 {
477+ return 0, ErrClosed
478+ }
479+ return loader.f.Xkb_compose_state_get_one_sym(s.h), nil
480+}
481+func (s *ComposeState) Reset() error {
482+ if s == nil {
483+ return ErrClosed
484+ }
485+ s.mu.Lock()
486+ defer s.mu.Unlock()
487+ if s.h == 0 {
488+ return ErrClosed
489+ }
490+ loader.f.Xkb_compose_state_reset(s.h)
491+ return nil
492+}
A xkbcommon_test.go +250 -0
@@ -0,0 +1,250 @@
1+package xkbcommon
2+
3+import (
4+ "errors"
5+ "os"
6+ "os/exec"
7+ "strings"
8+ "testing"
9+ "unsafe"
10+
11+ "github.com/bnema/purego-xkbcommon/raw"
12+)
13+
14+func live(t *testing.T) *Context {
15+ t.Helper()
16+ if err := Available(); err != nil {
17+ if strings.Contains(err.Error(), "load libxkbcommon.so.0") {
18+ t.Skipf("live libxkbcommon.so.0 absent: %v", err)
19+ }
20+ t.Fatal(err)
21+ }
22+ c, e := NewContext()
23+ if e != nil {
24+ t.Fatal(e)
25+ }
26+ t.Cleanup(func() { c.Close() })
27+ return c
28+}
29+func rules(t *testing.T, c *Context, layout string) *Keymap {
30+ t.Helper()
31+ k, e := c.NewKeymapRules("evdev", "pc105", layout, "", "")
32+ if e != nil {
33+ t.Fatalf("rules %s: %v", layout, e)
34+ }
35+ t.Cleanup(func() { k.Close() })
36+ return k
37+}
38+func state(t *testing.T, k *Keymap) *State {
39+ t.Helper()
40+ s, e := k.NewState()
41+ if e != nil {
42+ t.Fatal(e)
43+ }
44+ t.Cleanup(func() { s.Close() })
45+ return s
46+}
47+func TestABI(t *testing.T) {
48+ if unsafe.Sizeof(raw.RuleNames{}) != 5*unsafe.Sizeof(uintptr(0)) {
49+ t.Fatal("rule names ABI")
50+ }
51+ if unsafe.Sizeof(raw.Keycode(0)) != 4 || unsafe.Sizeof(raw.Keysym(0)) != 4 || unsafe.Sizeof(raw.ModMask(0)) != 4 {
52+ t.Fatal("scalar ABI")
53+ }
54+}
55+func TestSymbols(t *testing.T) {
56+ live(t)
57+ if len(raw.Symbols) < 25 {
58+ t.Fatal("incomplete required symbol set")
59+ }
60+}
61+func TestMalformedAndLifetime(t *testing.T) {
62+ c := live(t)
63+ if _, e := c.NewKeymapString("garbage"); e == nil {
64+ t.Fatal("malformed keymap compiled")
65+ }
66+ if _, e := c.NewKeymapString("foo\x00bar"); e == nil {
67+ t.Fatal("embedded NUL")
68+ }
69+ k := rules(t, c, "us")
70+ s := state(t, k)
71+ c.Close()
72+ k.Close()
73+ for i := 0; i < 3; i++ {
74+ c.Close()
75+ k.Close()
76+ }
77+ if _, e := k.NewState(); !errors.Is(e, ErrClosed) {
78+ t.Fatal(e)
79+ }
80+ v, e := s.UTF8(38)
81+ if e != nil || v != "a" {
82+ t.Fatalf("state after parent close: %q %v", v, e)
83+ }
84+ s.Close()
85+ s.Close()
86+ if _, e := s.KeySym(38); !errors.Is(e, ErrClosed) {
87+ t.Fatal(e)
88+ }
89+}
90+func TestFD(t *testing.T) {
91+ c := live(t)
92+ out, e := exec.Command("xkbcli", "compile-keymap", "--rules", "evdev", "--model", "pc105", "--layout", "fr", "--output-format", "1").Output()
93+ if e != nil {
94+ t.Fatalf("reference xkbcli: %v", e)
95+ }
96+ f, e := os.CreateTemp(t.TempDir(), "keymap")
97+ if e != nil {
98+ t.Fatal(e)
99+ }
100+ defer f.Close()
101+ data := append(out, 0)
102+ if _, e = f.Write(data); e != nil {
103+ t.Fatal(e)
104+ }
105+ other, e := c.NewKeymapFD(int(f.Fd()), len(data))
106+ if e != nil {
107+ t.Fatal(e)
108+ }
109+ other.Close()
110+ if _, e = f.Stat(); e != nil {
111+ t.Fatalf("FD was closed: %v", e)
112+ }
113+ if _, e = c.NewKeymapFD(int(f.Fd()), len(data)-1); e == nil {
114+ t.Fatal("missing NUL accepted")
115+ }
116+ if _, e = c.NewKeymapFD(-1, len(data)); e == nil {
117+ t.Fatal("negative FD")
118+ }
119+}
120+func TestLayoutsAndDifferential(t *testing.T) {
121+ c := live(t)
122+ for _, tc := range []struct {
123+ layout string
124+ code uint32
125+ want string
126+ alt bool
127+ }{{"us", 38, "a", false}, {"fr", 24, "a", false}, {"de", 29, "z", false}, {"de", 26, "€", true}, {"fr", 26, "€", true}} {
128+ t.Run(tc.layout+tc.want, func(t *testing.T) {
129+ k := rules(t, c, tc.layout)
130+ s := state(t, k)
131+ if tc.alt {
132+ idx, e := k.ModIndex("LevelThree")
133+ if e != nil || idx == ^uint32(0) || idx >= 32 {
134+ t.Fatalf("LevelThree: %d %v", idx, e)
135+ }
136+ s.UpdateMask(1<<idx, 0, 0, 0, 0, 0)
137+ }
138+ got, e := s.UTF8(tc.code)
139+ if e != nil || got != tc.want {
140+ t.Fatalf("utf8 %q want %q: %v", got, tc.want, e)
141+ }
142+ sym, e := s.KeySym(tc.code)
143+ if e != nil {
144+ t.Fatal(e)
145+ }
146+ name, e := KeysymName(sym)
147+ if e != nil || name == "" {
148+ t.Fatalf("name %q: %v", name, e)
149+ }
150+ back, e := KeysymFromName(name)
151+ if e != nil || back != sym {
152+ t.Fatalf("name roundtrip %d %d %v", sym, back, e)
153+ }
154+ // Compare safe calls to the independently resolved native entry points for this real rules keymap.
155+ nativeSym := loader.f.Xkb_state_key_get_one_sym(s.h, tc.code)
156+ if nativeSym != sym {
157+ t.Fatalf("native sym %d != %d", nativeSym, sym)
158+ }
159+ nativeN := loader.f.Xkb_state_key_get_utf8(s.h, tc.code, 0, 0)
160+ if nativeN != int32(len(got)) {
161+ t.Fatalf("native bytes %d != %d", nativeN, len(got))
162+ }
163+ })
164+ }
165+ k := rules(t, c, "us,de")
166+ s := state(t, k)
167+ n, e := k.LayoutCount()
168+ if e != nil || n != 2 {
169+ t.Fatalf("layouts %d %v", n, e)
170+ }
171+ s.UpdateMask(0, 0, 0, 0, 0, 1)
172+ group, e := s.Layout()
173+ if e != nil || group != 1 {
174+ t.Fatalf("group %d %v", group, e)
175+ }
176+ v, _ := s.UTF8(29)
177+ if v != "z" {
178+ t.Fatalf("group 1 %q", v)
179+ }
180+ shift, e := k.ModIndex("Shift")
181+ if e != nil || shift >= 32 {
182+ t.Fatalf("shift %d %v", shift, e)
183+ }
184+ s.UpdateMask(1<<shift, 0, 0, 0, 0, 1)
185+ v, _ = s.UTF8(29)
186+ if v != "Z" {
187+ t.Fatalf("shift group 1 %q", v)
188+ }
189+ mods, _ := s.Mods()
190+ if mods&(1<<shift) == 0 {
191+ t.Fatalf("mods %#x", mods)
192+ }
193+}
194+func TestCompose(t *testing.T) {
195+ c := live(t)
196+ table, e := c.NewComposeTable("en_US.UTF-8")
197+ if e != nil {
198+ t.Fatal(e)
199+ }
200+ s, e := table.NewState()
201+ if e != nil {
202+ t.Fatal(e)
203+ }
204+ table.Close()
205+ c.Close()
206+ if _, e = s.Feed(raw.XKB_KEY_dead_acute); e != nil {
207+ t.Fatal(e)
208+ }
209+ status, _ := s.Status()
210+ if status != raw.XKB_COMPOSE_COMPOSING {
211+ t.Fatalf("status %d", status)
212+ }
213+ s.Feed(raw.XKB_KEY_e)
214+ status, _ = s.Status()
215+ if status != raw.XKB_COMPOSE_COMPOSED {
216+ t.Fatalf("status %d", status)
217+ }
218+ v, e := s.UTF8()
219+ if e != nil || v != "é" {
220+ t.Fatalf("compose %q %v", v, e)
221+ }
222+ s.Reset()
223+ s.Feed(raw.XKB_KEY_Multi_key)
224+ s.Feed(raw.XKB_KEY_apostrophe)
225+ s.Feed(raw.XKB_KEY_e)
226+ v, _ = s.UTF8()
227+ if v != "é" {
228+ t.Fatalf("multikey %q", v)
229+ }
230+ s.Close()
231+ s.Close()
232+ table.Close()
233+ if _, e = s.UTF8(); !errors.Is(e, ErrClosed) {
234+ t.Fatal(e)
235+ }
236+}
237+func TestSizing(t *testing.T) {
238+ v, e := sizedUTF8(func(p, n uintptr) int32 { return 0 })
239+ if e != nil || v != "" {
240+ t.Fatalf("%q %v", v, e)
241+ }
242+ _, e = sizedUTF8(func(p, n uintptr) int32 { return -1 })
243+ if e == nil {
244+ t.Fatal("negative result accepted")
245+ }
246+ v, e = KeysymName(raw.XKB_KEY_EuroSign)
247+ if e != nil || v != "EuroSign" {
248+ t.Fatalf("name %q %v", v, e)
249+ }
250+}
@@ -0,0 +1,250 @@
1+package xkbcommon
2+
3+import (
4+ "errors"
5+ "os"
6+ "os/exec"
7+ "strings"
8+ "testing"
9+ "unsafe"
10+
11+ "github.com/bnema/purego-xkbcommon/raw"
12+)
13+
14+func live(t *testing.T) *Context {
15+ t.Helper()
16+ if err := Available(); err != nil {
17+ if strings.Contains(err.Error(), "load libxkbcommon.so.0") {
18+ t.Skipf("live libxkbcommon.so.0 absent: %v", err)
19+ }
20+ t.Fatal(err)
21+ }
22+ c, e := NewContext()
23+ if e != nil {
24+ t.Fatal(e)
25+ }
26+ t.Cleanup(func() { c.Close() })
27+ return c
28+}
29+func rules(t *testing.T, c *Context, layout string) *Keymap {
30+ t.Helper()
31+ k, e := c.NewKeymapRules("evdev", "pc105", layout, "", "")
32+ if e != nil {
33+ t.Fatalf("rules %s: %v", layout, e)
34+ }
35+ t.Cleanup(func() { k.Close() })
36+ return k
37+}
38+func state(t *testing.T, k *Keymap) *State {
39+ t.Helper()
40+ s, e := k.NewState()
41+ if e != nil {
42+ t.Fatal(e)
43+ }
44+ t.Cleanup(func() { s.Close() })
45+ return s
46+}
47+func TestABI(t *testing.T) {
48+ if unsafe.Sizeof(raw.RuleNames{}) != 5*unsafe.Sizeof(uintptr(0)) {
49+ t.Fatal("rule names ABI")
50+ }
51+ if unsafe.Sizeof(raw.Keycode(0)) != 4 || unsafe.Sizeof(raw.Keysym(0)) != 4 || unsafe.Sizeof(raw.ModMask(0)) != 4 {
52+ t.Fatal("scalar ABI")
53+ }
54+}
55+func TestSymbols(t *testing.T) {
56+ live(t)
57+ if len(raw.Symbols) < 25 {
58+ t.Fatal("incomplete required symbol set")
59+ }
60+}
61+func TestMalformedAndLifetime(t *testing.T) {
62+ c := live(t)
63+ if _, e := c.NewKeymapString("garbage"); e == nil {
64+ t.Fatal("malformed keymap compiled")
65+ }
66+ if _, e := c.NewKeymapString("foo\x00bar"); e == nil {
67+ t.Fatal("embedded NUL")
68+ }
69+ k := rules(t, c, "us")
70+ s := state(t, k)
71+ c.Close()
72+ k.Close()
73+ for i := 0; i < 3; i++ {
74+ c.Close()
75+ k.Close()
76+ }
77+ if _, e := k.NewState(); !errors.Is(e, ErrClosed) {
78+ t.Fatal(e)
79+ }
80+ v, e := s.UTF8(38)
81+ if e != nil || v != "a" {
82+ t.Fatalf("state after parent close: %q %v", v, e)
83+ }
84+ s.Close()
85+ s.Close()
86+ if _, e := s.KeySym(38); !errors.Is(e, ErrClosed) {
87+ t.Fatal(e)
88+ }
89+}
90+func TestFD(t *testing.T) {
91+ c := live(t)
92+ out, e := exec.Command("xkbcli", "compile-keymap", "--rules", "evdev", "--model", "pc105", "--layout", "fr", "--output-format", "1").Output()
93+ if e != nil {
94+ t.Fatalf("reference xkbcli: %v", e)
95+ }
96+ f, e := os.CreateTemp(t.TempDir(), "keymap")
97+ if e != nil {
98+ t.Fatal(e)
99+ }
100+ defer f.Close()
101+ data := append(out, 0)
102+ if _, e = f.Write(data); e != nil {
103+ t.Fatal(e)
104+ }
105+ other, e := c.NewKeymapFD(int(f.Fd()), len(data))
106+ if e != nil {
107+ t.Fatal(e)
108+ }
109+ other.Close()
110+ if _, e = f.Stat(); e != nil {
111+ t.Fatalf("FD was closed: %v", e)
112+ }
113+ if _, e = c.NewKeymapFD(int(f.Fd()), len(data)-1); e == nil {
114+ t.Fatal("missing NUL accepted")
115+ }
116+ if _, e = c.NewKeymapFD(-1, len(data)); e == nil {
117+ t.Fatal("negative FD")
118+ }
119+}
120+func TestLayoutsAndDifferential(t *testing.T) {
121+ c := live(t)
122+ for _, tc := range []struct {
123+ layout string
124+ code uint32
125+ want string
126+ alt bool
127+ }{{"us", 38, "a", false}, {"fr", 24, "a", false}, {"de", 29, "z", false}, {"de", 26, "€", true}, {"fr", 26, "€", true}} {
128+ t.Run(tc.layout+tc.want, func(t *testing.T) {
129+ k := rules(t, c, tc.layout)
130+ s := state(t, k)
131+ if tc.alt {
132+ idx, e := k.ModIndex("LevelThree")
133+ if e != nil || idx == ^uint32(0) || idx >= 32 {
134+ t.Fatalf("LevelThree: %d %v", idx, e)
135+ }
136+ s.UpdateMask(1<<idx, 0, 0, 0, 0, 0)
137+ }
138+ got, e := s.UTF8(tc.code)
139+ if e != nil || got != tc.want {
140+ t.Fatalf("utf8 %q want %q: %v", got, tc.want, e)
141+ }
142+ sym, e := s.KeySym(tc.code)
143+ if e != nil {
144+ t.Fatal(e)
145+ }
146+ name, e := KeysymName(sym)
147+ if e != nil || name == "" {
148+ t.Fatalf("name %q: %v", name, e)
149+ }
150+ back, e := KeysymFromName(name)
151+ if e != nil || back != sym {
152+ t.Fatalf("name roundtrip %d %d %v", sym, back, e)
153+ }
154+ // Compare safe calls to the independently resolved native entry points for this real rules keymap.
155+ nativeSym := loader.f.Xkb_state_key_get_one_sym(s.h, tc.code)
156+ if nativeSym != sym {
157+ t.Fatalf("native sym %d != %d", nativeSym, sym)
158+ }
159+ nativeN := loader.f.Xkb_state_key_get_utf8(s.h, tc.code, 0, 0)
160+ if nativeN != int32(len(got)) {
161+ t.Fatalf("native bytes %d != %d", nativeN, len(got))
162+ }
163+ })
164+ }
165+ k := rules(t, c, "us,de")
166+ s := state(t, k)
167+ n, e := k.LayoutCount()
168+ if e != nil || n != 2 {
169+ t.Fatalf("layouts %d %v", n, e)
170+ }
171+ s.UpdateMask(0, 0, 0, 0, 0, 1)
172+ group, e := s.Layout()
173+ if e != nil || group != 1 {
174+ t.Fatalf("group %d %v", group, e)
175+ }
176+ v, _ := s.UTF8(29)
177+ if v != "z" {
178+ t.Fatalf("group 1 %q", v)
179+ }
180+ shift, e := k.ModIndex("Shift")
181+ if e != nil || shift >= 32 {
182+ t.Fatalf("shift %d %v", shift, e)
183+ }
184+ s.UpdateMask(1<<shift, 0, 0, 0, 0, 1)
185+ v, _ = s.UTF8(29)
186+ if v != "Z" {
187+ t.Fatalf("shift group 1 %q", v)
188+ }
189+ mods, _ := s.Mods()
190+ if mods&(1<<shift) == 0 {
191+ t.Fatalf("mods %#x", mods)
192+ }
193+}
194+func TestCompose(t *testing.T) {
195+ c := live(t)
196+ table, e := c.NewComposeTable("en_US.UTF-8")
197+ if e != nil {
198+ t.Fatal(e)
199+ }
200+ s, e := table.NewState()
201+ if e != nil {
202+ t.Fatal(e)
203+ }
204+ table.Close()
205+ c.Close()
206+ if _, e = s.Feed(raw.XKB_KEY_dead_acute); e != nil {
207+ t.Fatal(e)
208+ }
209+ status, _ := s.Status()
210+ if status != raw.XKB_COMPOSE_COMPOSING {
211+ t.Fatalf("status %d", status)
212+ }
213+ s.Feed(raw.XKB_KEY_e)
214+ status, _ = s.Status()
215+ if status != raw.XKB_COMPOSE_COMPOSED {
216+ t.Fatalf("status %d", status)
217+ }
218+ v, e := s.UTF8()
219+ if e != nil || v != "é" {
220+ t.Fatalf("compose %q %v", v, e)
221+ }
222+ s.Reset()
223+ s.Feed(raw.XKB_KEY_Multi_key)
224+ s.Feed(raw.XKB_KEY_apostrophe)
225+ s.Feed(raw.XKB_KEY_e)
226+ v, _ = s.UTF8()
227+ if v != "é" {
228+ t.Fatalf("multikey %q", v)
229+ }
230+ s.Close()
231+ s.Close()
232+ table.Close()
233+ if _, e = s.UTF8(); !errors.Is(e, ErrClosed) {
234+ t.Fatal(e)
235+ }
236+}
237+func TestSizing(t *testing.T) {
238+ v, e := sizedUTF8(func(p, n uintptr) int32 { return 0 })
239+ if e != nil || v != "" {
240+ t.Fatalf("%q %v", v, e)
241+ }
242+ _, e = sizedUTF8(func(p, n uintptr) int32 { return -1 })
243+ if e == nil {
244+ t.Fatal("negative result accepted")
245+ }
246+ v, e = KeysymName(raw.XKB_KEY_EuroSign)
247+ if e != nil || v != "EuroSign" {
248+ t.Fatalf("name %q %v", v, e)
249+ }
250+}

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 2

↑↓ moveenter openesc close