feat(bindings): generate xkbcommon ABI and implement safe keyboard bindings
- commit
- 9e679793fbcb1259aab513fda45a913edaf43536
- author
- brice · · 2026-09-28 07:01 UTC
- parent
- a7f62f3
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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 @@ | |||
| 1 | 1 | # 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. | |
| @@ -1,3 +1,20 @@ | |||||
| 1 | # purego-xkbcommon | 1 | # 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 | + | } | |||