Merge pull request #3 from bnema/perf/syscall-hotpath
perf: allocation-free libxkbcommon dispatch through fixed-arity syscalls
9 files · +765 -110
A .github/workflows/ci.yml +36 -0
| @@ -0,0 +1,36 @@ | |||
| 1 | + | name: CI | |
| 2 | + | on: | |
| 3 | + | push: | |
| 4 | + | branches: [main] | |
| 5 | + | pull_request: | |
| 6 | + | concurrency: | |
| 7 | + | group: ci-${{ github.ref }} | |
| 8 | + | cancel-in-progress: true | |
| 9 | + | jobs: | |
| 10 | + | # Arch ships the libxkbcommon the bindings are pinned against; the live | |
| 11 | + | # tests load it through purego. gcc is for the race detector. | |
| 12 | + | check: | |
| 13 | + | runs-on: ubuntu-latest | |
| 14 | + | container: | |
| 15 | + | image: archlinux:latest | |
| 16 | + | options: --init | |
| 17 | + | env: | |
| 18 | + | LANG: en_US.UTF-8 | |
| 19 | + | steps: | |
| 20 | + | # Compose tests need the en_US.UTF-8 locale and its Compose file (libx11). | |
| 21 | + | - run: | | |
| 22 | + | pacman -Syu --noconfirm --needed git go gcc glibc-locales libxkbcommon libx11 xkeyboard-config | |
| 23 | + | echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen | |
| 24 | + | locale-gen | |
| 25 | + | - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| 26 | + | with: | |
| 27 | + | persist-credentials: false | |
| 28 | + | - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" | |
| 29 | + | - run: CGO_ENABLED=0 go vet ./... | |
| 30 | + | - run: go run honnef.co/go/tools/cmd/staticcheck@latest ./... | |
| 31 | + | - run: CGO_ENABLED=0 go test -count=1 ./... | |
| 32 | + | - run: CGO_ENABLED=1 go test -race -count=1 ./... | |
| 33 | + | - name: generated code is current | |
| 34 | + | run: | | |
| 35 | + | GOWORK=off go generate ./... | |
| 36 | + | git diff --exit-code | |
| @@ -0,0 +1,36 @@ | |||||
| 1 | + | name: CI | |||
| 2 | + | on: | |||
| 3 | + | push: | |||
| 4 | + | branches: [main] | |||
| 5 | + | pull_request: | |||
| 6 | + | concurrency: | |||
| 7 | + | group: ci-${{ github.ref }} | |||
| 8 | + | cancel-in-progress: true | |||
| 9 | + | jobs: | |||
| 10 | + | # Arch ships the libxkbcommon the bindings are pinned against; the live | |||
| 11 | + | # tests load it through purego. gcc is for the race detector. | |||
| 12 | + | check: | |||
| 13 | + | runs-on: ubuntu-latest | |||
| 14 | + | container: | |||
| 15 | + | image: archlinux:latest | |||
| 16 | + | options: --init | |||
| 17 | + | env: | |||
| 18 | + | LANG: en_US.UTF-8 | |||
| 19 | + | steps: | |||
| 20 | + | # Compose tests need the en_US.UTF-8 locale and its Compose file (libx11). | |||
| 21 | + | - run: | | |||
| 22 | + | pacman -Syu --noconfirm --needed git go gcc glibc-locales libxkbcommon libx11 xkeyboard-config | |||
| 23 | + | echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen | |||
| 24 | + | locale-gen | |||
| 25 | + | - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |||
| 26 | + | with: | |||
| 27 | + | persist-credentials: false | |||
| 28 | + | - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" | |||
| 29 | + | - run: CGO_ENABLED=0 go vet ./... | |||
| 30 | + | - run: go run honnef.co/go/tools/cmd/staticcheck@latest ./... | |||
| 31 | + | - run: CGO_ENABLED=0 go test -count=1 ./... | |||
| 32 | + | - run: CGO_ENABLED=1 go test -race -count=1 ./... | |||
| 33 | + | - name: generated code is current | |||
| 34 | + | run: | | |||
| 35 | + | GOWORK=off go generate ./... | |||
| 36 | + | git diff --exit-code | |||
M README.md +17 -2
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | # purego-xkbcommon | |
| 2 | 2 | ||
| 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`. | |
| 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 bindings. Install libxkbcommon at runtime and xkeyboard-config for rules-based keymaps. The loader reports missing libraries and required symbols via `Available`. | |
| 4 | 4 | ||
| 5 | 5 | ```go | |
| 6 | 6 | ctx, err := xkbcommon.NewContext() | |
| @@ -19,4 +19,19 @@ Use `NewKeymapFD(fd, size)` for Wayland `xkb_v1` keymaps; it copies exactly `siz | |||
| 19 | 19 | ||
| 20 | 20 | `State.UTF8Into` and `ComposeState.UTF8Into` write into a caller-owned buffer of 1..`MaxUTF8Buffer` bytes and create no Go string. On `io.ErrShortBuffer` the whole buffer is wiped and n+1 bytes are needed; the caller wipes the buffer after use. | |
| 21 | 21 | ||
| 22 | - | 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. | |
| 22 | + | 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 the fixed-arity `Syscall6`/`Syscall15` with cgo disabled. | |
| 23 | + | ||
| 24 | + | ## Allocation-free hot path | |
| 25 | + | ||
| 26 | + | `raw.Funcs` holds symbol addresses resolved once by `Available`; every generated method calls `purego.Syscall6` (or `Syscall15` for more than six arguments) directly, so no reflection or return boxing happens per call. Per-key calls — `State.KeySym`, `State.UTF8Into`, `State.UpdateMask`, `State.Layout`, `State.Mods`, `State.ModIndexActive`, `State.ModNameActive`, `Keymap.KeyRepeats`, `KeysymNameInto` and the `ComposeState` `Feed`/`Status`/`KeySym`/`UTF8Into`/`Reset` methods — perform 0 allocations (asserted by `TestHotPathAllocs`). The `*Into` methods stay allocation-free only with a long-lived `dst` reused across calls: a buffer passed to C escapes, so a fresh local array costs one allocation per call. The string-returning `UTF8` and `KeysymName` still allocate their result. | |
| 27 | + | ||
| 28 | + | ||
| 29 | + | ## v0.2.0: `raw` API change | |
| 30 | + | ||
| 31 | + | Only the `raw` package changed incompatibly; the root `xkbcommon` package is source-compatible (it gains `State.ModNameActive`, `State.ModIndexActive`, `Keymap.KeyRepeats` and `KeysymNameInto`). | |
| 32 | + | ||
| 33 | + | - `raw.Funcs` fields are no longer `func` values. Each function is now a method with the same exported name (`f.Xkb_state_key_get_one_sym(...)`), and the struct holds unexported symbol addresses. | |
| 34 | + | - Pointer parameters (strings, buffers, `*RuleNames`) are `unsafe.Pointer` instead of `uintptr`, and `RuleNames` fields are `unsafe.Pointer`. Pass the pointer directly; never pre-convert it to `uintptr`, which would not keep the memory alive. | |
| 35 | + | - `raw.Register(f, addresses, register)` is now `raw.Register(f, addresses)`; there is no `purego.RegisterFunc` hook. | |
| 36 | + | - A zero `raw.Funcs` is unusable: calling any method panics (`purego: fn is nil`) until `Register` has filled it. `xkbcommon.Available` does this for the package's own loader. | |
| 37 | + | - The generator only accepts `uintptr`, `uint32`, `int32` and `unsafe.Pointer` parameters and `uintptr`, `uint32`, `int32` or no return value, and rejects floats, bools and varargs. | |
| @@ -1,6 +1,6 @@ | |||||
| 1 | # purego-xkbcommon | 1 | # purego-xkbcommon | ||
| 2 | 2 | ||||
| 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`. | 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 bindings. Install libxkbcommon at runtime and xkeyboard-config for rules-based keymaps. The loader reports missing libraries and required symbols via `Available`. |
| 4 | 4 | ||||
| 5 | ```go | 5 | ```go | ||
| 6 | ctx, err := xkbcommon.NewContext() | 6 | ctx, err := xkbcommon.NewContext() | ||
| @@ -19,4 +19,19 @@ Use `NewKeymapFD(fd, size)` for Wayland `xkb_v1` keymaps; it copies exactly `siz | |||||
| 19 | 19 | ||||
| 20 | `State.UTF8Into` and `ComposeState.UTF8Into` write into a caller-owned buffer of 1..`MaxUTF8Buffer` bytes and create no Go string. On `io.ErrShortBuffer` the whole buffer is wiped and n+1 bytes are needed; the caller wipes the buffer after use. | 20 | `State.UTF8Into` and `ComposeState.UTF8Into` write into a caller-owned buffer of 1..`MaxUTF8Buffer` bytes and create no Go string. On `io.ErrShortBuffer` the whole buffer is wiped and n+1 bytes are needed; the caller wipes the buffer after use. | ||
| 21 | 21 | ||||
| 22 | - | 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. | 22 | + | 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 the fixed-arity `Syscall6`/`Syscall15` with cgo disabled. |
| 23 | + | ||||
| 24 | + | ## Allocation-free hot path | |||
| 25 | + | ||||
| 26 | + | `raw.Funcs` holds symbol addresses resolved once by `Available`; every generated method calls `purego.Syscall6` (or `Syscall15` for more than six arguments) directly, so no reflection or return boxing happens per call. Per-key calls — `State.KeySym`, `State.UTF8Into`, `State.UpdateMask`, `State.Layout`, `State.Mods`, `State.ModIndexActive`, `State.ModNameActive`, `Keymap.KeyRepeats`, `KeysymNameInto` and the `ComposeState` `Feed`/`Status`/`KeySym`/`UTF8Into`/`Reset` methods — perform 0 allocations (asserted by `TestHotPathAllocs`). The `*Into` methods stay allocation-free only with a long-lived `dst` reused across calls: a buffer passed to C escapes, so a fresh local array costs one allocation per call. The string-returning `UTF8` and `KeysymName` still allocate their result. | |||
| 27 | + | ||||
| 28 | + | ||||
| 29 | + | ## v0.2.0: `raw` API change | |||
| 30 | + | ||||
| 31 | + | Only the `raw` package changed incompatibly; the root `xkbcommon` package is source-compatible (it gains `State.ModNameActive`, `State.ModIndexActive`, `Keymap.KeyRepeats` and `KeysymNameInto`). | |||
| 32 | + | ||||
| 33 | + | - `raw.Funcs` fields are no longer `func` values. Each function is now a method with the same exported name (`f.Xkb_state_key_get_one_sym(...)`), and the struct holds unexported symbol addresses. | |||
| 34 | + | - Pointer parameters (strings, buffers, `*RuleNames`) are `unsafe.Pointer` instead of `uintptr`, and `RuleNames` fields are `unsafe.Pointer`. Pass the pointer directly; never pre-convert it to `uintptr`, which would not keep the memory alive. | |||
| 35 | + | - `raw.Register(f, addresses, register)` is now `raw.Register(f, addresses)`; there is no `purego.RegisterFunc` hook. | |||
| 36 | + | - A zero `raw.Funcs` is unusable: calling any method panics (`purego: fn is nil`) until `Register` has filled it. `xkbcommon.Available` does this for the package's own loader. | |||
| 37 | + | - The generator only accepts `uintptr`, `uint32`, `int32` and `unsafe.Pointer` parameters and `uintptr`, `uint32`, `int32` or no return value, and rejects floats, bools and varargs. | |||
A alloc_test.go +221 -0
| @@ -0,0 +1,221 @@ | |||
| 1 | + | package xkbcommon | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "bytes" | |
| 5 | + | "errors" | |
| 6 | + | "io" | |
| 7 | + | "os" | |
| 8 | + | "runtime" | |
| 9 | + | "strings" | |
| 10 | + | "testing" | |
| 11 | + | ||
| 12 | + | "github.com/bnema/purego-xkbcommon/raw" | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | // fdKeymap compiles a pinned Wayland-format keymap from testdata. | |
| 16 | + | func fdKeymap(t *testing.T, c *Context, layout string) *Keymap { | |
| 17 | + | t.Helper() | |
| 18 | + | text, e := os.ReadFile("testdata/keymaps/" + layout + ".xkb") | |
| 19 | + | if e != nil { | |
| 20 | + | t.Fatal(e) | |
| 21 | + | } | |
| 22 | + | f := keymapFile(t, append(text, 0)) | |
| 23 | + | k, e := c.NewKeymapFD(int(f.Fd()), len(text)+1) | |
| 24 | + | if e != nil { | |
| 25 | + | t.Fatal(e) | |
| 26 | + | } | |
| 27 | + | t.Cleanup(func() { k.Close() }) | |
| 28 | + | return k | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | func assertNoAllocs(t *testing.T, name string, fn func()) { | |
| 32 | + | t.Helper() | |
| 33 | + | fn() // warm up lazy runtime state | |
| 34 | + | if n := testing.AllocsPerRun(200, fn); n != 0 { | |
| 35 | + | t.Errorf("%s: %v allocs/op, want 0", name, n) | |
| 36 | + | } else { | |
| 37 | + | t.Logf("%s: 0 allocs/op", name) | |
| 38 | + | } | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | func TestHotPathAllocs(t *testing.T) { | |
| 42 | + | c := live(t) | |
| 43 | + | k := fdKeymap(t, c, "fr") | |
| 44 | + | s := state(t, k) | |
| 45 | + | code := WaylandKeycode(16) | |
| 46 | + | var buf [MaxUTF8Buffer]byte | |
| 47 | + | var fail error | |
| 48 | + | check := func(e error) { | |
| 49 | + | if e != nil { | |
| 50 | + | fail = e | |
| 51 | + | } | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | table, e := c.NewComposeTable("en_US.UTF-8") | |
| 55 | + | if e != nil { | |
| 56 | + | t.Fatal(e) | |
| 57 | + | } | |
| 58 | + | defer table.Close() | |
| 59 | + | compose, e := table.NewState() | |
| 60 | + | if e != nil { | |
| 61 | + | t.Fatal(e) | |
| 62 | + | } | |
| 63 | + | defer compose.Close() | |
| 64 | + | ||
| 65 | + | assertNoAllocs(t, "State.KeySym", func() { _, e := s.KeySym(code); check(e) }) | |
| 66 | + | assertNoAllocs(t, "State.UTF8Into", func() { _, e := s.UTF8Into(code, buf[:]); check(e) }) | |
| 67 | + | assertNoAllocs(t, "State.UpdateMask", func() { _, e := s.UpdateMask(1, 0, 2, 0, 0, 0); check(e) }) | |
| 68 | + | assertNoAllocs(t, "State.Layout", func() { _, e := s.Layout(); check(e) }) | |
| 69 | + | assertNoAllocs(t, "State.Mods", func() { _, e := s.Mods(); check(e) }) | |
| 70 | + | assertNoAllocs(t, "State.ModIndexActive", func() { _, e := s.ModIndexActive(0); check(e) }) | |
| 71 | + | assertNoAllocs(t, "State.ModNameActive", func() { _, e := s.ModNameActive(raw.XKB_MOD_NAME_CTRL); check(e) }) | |
| 72 | + | assertNoAllocs(t, "Keymap.KeyRepeats", func() { _, e := k.KeyRepeats(code); check(e) }) | |
| 73 | + | assertNoAllocs(t, "KeysymNameInto", func() { _, e := KeysymNameInto(raw.XKB_KEY_a, buf[:]); check(e) }) | |
| 74 | + | assertNoAllocs(t, "compose key sequence", func() { | |
| 75 | + | _, e := compose.Feed(raw.XKB_KEY_dead_circumflex) | |
| 76 | + | check(e) | |
| 77 | + | _, e = compose.Feed(raw.XKB_KEY_e) | |
| 78 | + | check(e) | |
| 79 | + | _, e = compose.Status() | |
| 80 | + | check(e) | |
| 81 | + | _, e = compose.KeySym() | |
| 82 | + | check(e) | |
| 83 | + | _, e = compose.UTF8Into(buf[:]) | |
| 84 | + | check(e) | |
| 85 | + | check(compose.Reset()) | |
| 86 | + | }) | |
| 87 | + | if fail != nil { | |
| 88 | + | t.Fatal(fail) | |
| 89 | + | } | |
| 90 | + | } | |
| 91 | + | ||
| 92 | + | // TestHotPathValues guards the fixed-arity dispatch against argument/return | |
| 93 | + | // conversion mistakes. | |
| 94 | + | func TestHotPathValues(t *testing.T) { | |
| 95 | + | c := live(t) | |
| 96 | + | k := fdKeymap(t, c, "us") | |
| 97 | + | s := state(t, k) | |
| 98 | + | if r, e := k.KeyRepeats(WaylandKeycode(30)); e != nil || !r { | |
| 99 | + | t.Fatalf("KeyRepeats(a) = %v, %v", r, e) | |
| 100 | + | } | |
| 101 | + | ctrl, e := k.ModIndex(raw.XKB_MOD_NAME_CTRL) | |
| 102 | + | if e != nil { | |
| 103 | + | t.Fatal(e) | |
| 104 | + | } | |
| 105 | + | if a, e := s.ModNameActive(raw.XKB_MOD_NAME_CTRL); e != nil || a { | |
| 106 | + | t.Fatalf("ctrl active before update: %v %v", a, e) | |
| 107 | + | } | |
| 108 | + | if _, e := s.UpdateMask(1<<ctrl, 0, 0, 0, 0, 0); e != nil { | |
| 109 | + | t.Fatal(e) | |
| 110 | + | } | |
| 111 | + | if a, e := s.ModNameActive(raw.XKB_MOD_NAME_CTRL); e != nil || !a { | |
| 112 | + | t.Fatalf("ctrl name active: %v %v", a, e) | |
| 113 | + | } | |
| 114 | + | if a, e := s.ModIndexActive(ctrl); e != nil || !a { | |
| 115 | + | t.Fatalf("ctrl index active: %v %v", a, e) | |
| 116 | + | } | |
| 117 | + | if a, e := s.ModNameActive("NoSuchModifier"); e != nil || a { | |
| 118 | + | t.Fatalf("unknown modifier active: %v %v", a, e) | |
| 119 | + | } | |
| 120 | + | var buf [16]byte | |
| 121 | + | n, e := KeysymNameInto(raw.XKB_KEY_EuroSign, buf[:]) | |
| 122 | + | if e != nil || string(buf[:n]) != "EuroSign" { | |
| 123 | + | t.Fatalf("KeysymNameInto = %q %v", buf[:n], e) | |
| 124 | + | } | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | func TestModNameActiveLongName(t *testing.T) { | |
| 128 | + | c := live(t) | |
| 129 | + | k := fdKeymap(t, c, "us") | |
| 130 | + | s := state(t, k) | |
| 131 | + | for _, n := range []int{62, 63, 64, 65, 200} { | |
| 132 | + | name := strings.Repeat("x", n) | |
| 133 | + | if a, e := s.ModNameActive(name); e != nil || a { | |
| 134 | + | t.Fatalf("len %d: %v %v", n, a, e) | |
| 135 | + | } | |
| 136 | + | } | |
| 137 | + | // A short name after a long one must not see stale scratch bytes. | |
| 138 | + | ctrl, e := k.ModIndex(raw.XKB_MOD_NAME_CTRL) | |
| 139 | + | if e != nil { | |
| 140 | + | t.Fatal(e) | |
| 141 | + | } | |
| 142 | + | if _, e := s.UpdateMask(1<<ctrl, 0, 0, 0, 0, 0); e != nil { | |
| 143 | + | t.Fatal(e) | |
| 144 | + | } | |
| 145 | + | if a, e := s.ModNameActive(strings.Repeat("y", 63)); e != nil || a { | |
| 146 | + | t.Fatal(a, e) | |
| 147 | + | } | |
| 148 | + | if a, e := s.ModNameActive(raw.XKB_MOD_NAME_CTRL); e != nil || !a { | |
| 149 | + | t.Fatal(a, e) | |
| 150 | + | } | |
| 151 | + | if _, e := s.ModNameActive("a\x00b"); e == nil { | |
| 152 | + | t.Fatal("embedded NUL accepted") | |
| 153 | + | } | |
| 154 | + | } | |
| 155 | + | ||
| 156 | + | func TestKeysymNameIntoShortBuffer(t *testing.T) { | |
| 157 | + | live(t) | |
| 158 | + | buf := []byte{0xff, 0xff, 0xff} | |
| 159 | + | n, e := KeysymNameInto(raw.XKB_KEY_EuroSign, buf) | |
| 160 | + | if !errors.Is(e, io.ErrShortBuffer) || n != len("EuroSign") || !bytes.Equal(buf, make([]byte, 3)) { | |
| 161 | + | t.Fatalf("n=%d err=%v buf=%v", n, e, buf) | |
| 162 | + | } | |
| 163 | + | if _, e = KeysymNameInto(raw.XKB_KEY_a, nil); !errors.Is(e, ErrUTF8Buffer) { | |
| 164 | + | t.Fatal(e) | |
| 165 | + | } | |
| 166 | + | } | |
| 167 | + | ||
| 168 | + | func TestModIndexActiveInvalid(t *testing.T) { | |
| 169 | + | c := live(t) | |
| 170 | + | s := state(t, fdKeymap(t, c, "us")) | |
| 171 | + | for _, idx := range []uint32{64, 1 << 20, ^uint32(0)} { | |
| 172 | + | if a, e := s.ModIndexActive(idx); e != nil || a { | |
| 173 | + | t.Fatalf("idx %d: %v %v", idx, a, e) | |
| 174 | + | } | |
| 175 | + | } | |
| 176 | + | s.Close() | |
| 177 | + | if _, e := s.ModIndexActive(0); !errors.Is(e, ErrClosed) { | |
| 178 | + | t.Fatal(e) | |
| 179 | + | } | |
| 180 | + | if _, e := s.ModNameActive("Shift"); !errors.Is(e, ErrClosed) { | |
| 181 | + | t.Fatal(e) | |
| 182 | + | } | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | func TestNewKeymapRulesNames(t *testing.T) { | |
| 186 | + | c := live(t) | |
| 187 | + | // Every RMLVO component is exercised so each pointer in RuleNames must stay valid. | |
| 188 | + | k, e := c.NewKeymapRules("evdev", "pc105", "us,fr", "dvorak,", "grp:alt_shift_toggle") | |
| 189 | + | if e != nil { | |
| 190 | + | t.Fatal(e) | |
| 191 | + | } | |
| 192 | + | defer k.Close() | |
| 193 | + | if n, e := k.LayoutCount(); e != nil || n != 2 { | |
| 194 | + | t.Fatalf("layouts %d %v", n, e) | |
| 195 | + | } | |
| 196 | + | s := state(t, k) | |
| 197 | + | runtime.GC() | |
| 198 | + | // Dvorak puts apostrophe on the evdev KEY_Q position. | |
| 199 | + | if sym, e := s.KeySym(WaylandKeycode(16)); e != nil || sym != raw.XKB_KEY_apostrophe { | |
| 200 | + | t.Fatalf("dvorak sym %#x %v", sym, e) | |
| 201 | + | } | |
| 202 | + | // Defaults (all empty) still work, and an unknown layout is rejected. | |
| 203 | + | d, e := c.NewKeymapRules("", "", "", "", "") | |
| 204 | + | if e != nil { | |
| 205 | + | t.Fatal(e) | |
| 206 | + | } | |
| 207 | + | d.Close() | |
| 208 | + | if _, e = c.NewKeymapRules("evdev", "pc105", "nosuchlayout", "", ""); e == nil { | |
| 209 | + | t.Fatal("unknown layout accepted") | |
| 210 | + | } | |
| 211 | + | } | |
| 212 | + | ||
| 213 | + | func TestZeroFuncsPanics(t *testing.T) { | |
| 214 | + | defer func() { | |
| 215 | + | if recover() == nil { | |
| 216 | + | t.Fatal("zero raw.Funcs must panic, not call address 0") | |
| 217 | + | } | |
| 218 | + | }() | |
| 219 | + | var f raw.Funcs | |
| 220 | + | f.Xkb_keymap_num_layouts(0) | |
| 221 | + | } | |
| @@ -0,0 +1,221 @@ | |||||
| 1 | + | package xkbcommon | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "bytes" | |||
| 5 | + | "errors" | |||
| 6 | + | "io" | |||
| 7 | + | "os" | |||
| 8 | + | "runtime" | |||
| 9 | + | "strings" | |||
| 10 | + | "testing" | |||
| 11 | + | ||||
| 12 | + | "github.com/bnema/purego-xkbcommon/raw" | |||
| 13 | + | ) | |||
| 14 | + | ||||
| 15 | + | // fdKeymap compiles a pinned Wayland-format keymap from testdata. | |||
| 16 | + | func fdKeymap(t *testing.T, c *Context, layout string) *Keymap { | |||
| 17 | + | t.Helper() | |||
| 18 | + | text, e := os.ReadFile("testdata/keymaps/" + layout + ".xkb") | |||
| 19 | + | if e != nil { | |||
| 20 | + | t.Fatal(e) | |||
| 21 | + | } | |||
| 22 | + | f := keymapFile(t, append(text, 0)) | |||
| 23 | + | k, e := c.NewKeymapFD(int(f.Fd()), len(text)+1) | |||
| 24 | + | if e != nil { | |||
| 25 | + | t.Fatal(e) | |||
| 26 | + | } | |||
| 27 | + | t.Cleanup(func() { k.Close() }) | |||
| 28 | + | return k | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | func assertNoAllocs(t *testing.T, name string, fn func()) { | |||
| 32 | + | t.Helper() | |||
| 33 | + | fn() // warm up lazy runtime state | |||
| 34 | + | if n := testing.AllocsPerRun(200, fn); n != 0 { | |||
| 35 | + | t.Errorf("%s: %v allocs/op, want 0", name, n) | |||
| 36 | + | } else { | |||
| 37 | + | t.Logf("%s: 0 allocs/op", name) | |||
| 38 | + | } | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | func TestHotPathAllocs(t *testing.T) { | |||
| 42 | + | c := live(t) | |||
| 43 | + | k := fdKeymap(t, c, "fr") | |||
| 44 | + | s := state(t, k) | |||
| 45 | + | code := WaylandKeycode(16) | |||
| 46 | + | var buf [MaxUTF8Buffer]byte | |||
| 47 | + | var fail error | |||
| 48 | + | check := func(e error) { | |||
| 49 | + | if e != nil { | |||
| 50 | + | fail = e | |||
| 51 | + | } | |||
| 52 | + | } | |||
| 53 | + | ||||
| 54 | + | table, e := c.NewComposeTable("en_US.UTF-8") | |||
| 55 | + | if e != nil { | |||
| 56 | + | t.Fatal(e) | |||
| 57 | + | } | |||
| 58 | + | defer table.Close() | |||
| 59 | + | compose, e := table.NewState() | |||
| 60 | + | if e != nil { | |||
| 61 | + | t.Fatal(e) | |||
| 62 | + | } | |||
| 63 | + | defer compose.Close() | |||
| 64 | + | ||||
| 65 | + | assertNoAllocs(t, "State.KeySym", func() { _, e := s.KeySym(code); check(e) }) | |||
| 66 | + | assertNoAllocs(t, "State.UTF8Into", func() { _, e := s.UTF8Into(code, buf[:]); check(e) }) | |||
| 67 | + | assertNoAllocs(t, "State.UpdateMask", func() { _, e := s.UpdateMask(1, 0, 2, 0, 0, 0); check(e) }) | |||
| 68 | + | assertNoAllocs(t, "State.Layout", func() { _, e := s.Layout(); check(e) }) | |||
| 69 | + | assertNoAllocs(t, "State.Mods", func() { _, e := s.Mods(); check(e) }) | |||
| 70 | + | assertNoAllocs(t, "State.ModIndexActive", func() { _, e := s.ModIndexActive(0); check(e) }) | |||
| 71 | + | assertNoAllocs(t, "State.ModNameActive", func() { _, e := s.ModNameActive(raw.XKB_MOD_NAME_CTRL); check(e) }) | |||
| 72 | + | assertNoAllocs(t, "Keymap.KeyRepeats", func() { _, e := k.KeyRepeats(code); check(e) }) | |||
| 73 | + | assertNoAllocs(t, "KeysymNameInto", func() { _, e := KeysymNameInto(raw.XKB_KEY_a, buf[:]); check(e) }) | |||
| 74 | + | assertNoAllocs(t, "compose key sequence", func() { | |||
| 75 | + | _, e := compose.Feed(raw.XKB_KEY_dead_circumflex) | |||
| 76 | + | check(e) | |||
| 77 | + | _, e = compose.Feed(raw.XKB_KEY_e) | |||
| 78 | + | check(e) | |||
| 79 | + | _, e = compose.Status() | |||
| 80 | + | check(e) | |||
| 81 | + | _, e = compose.KeySym() | |||
| 82 | + | check(e) | |||
| 83 | + | _, e = compose.UTF8Into(buf[:]) | |||
| 84 | + | check(e) | |||
| 85 | + | check(compose.Reset()) | |||
| 86 | + | }) | |||
| 87 | + | if fail != nil { | |||
| 88 | + | t.Fatal(fail) | |||
| 89 | + | } | |||
| 90 | + | } | |||
| 91 | + | ||||
| 92 | + | // TestHotPathValues guards the fixed-arity dispatch against argument/return | |||
| 93 | + | // conversion mistakes. | |||
| 94 | + | func TestHotPathValues(t *testing.T) { | |||
| 95 | + | c := live(t) | |||
| 96 | + | k := fdKeymap(t, c, "us") | |||
| 97 | + | s := state(t, k) | |||
| 98 | + | if r, e := k.KeyRepeats(WaylandKeycode(30)); e != nil || !r { | |||
| 99 | + | t.Fatalf("KeyRepeats(a) = %v, %v", r, e) | |||
| 100 | + | } | |||
| 101 | + | ctrl, e := k.ModIndex(raw.XKB_MOD_NAME_CTRL) | |||
| 102 | + | if e != nil { | |||
| 103 | + | t.Fatal(e) | |||
| 104 | + | } | |||
| 105 | + | if a, e := s.ModNameActive(raw.XKB_MOD_NAME_CTRL); e != nil || a { | |||
| 106 | + | t.Fatalf("ctrl active before update: %v %v", a, e) | |||
| 107 | + | } | |||
| 108 | + | if _, e := s.UpdateMask(1<<ctrl, 0, 0, 0, 0, 0); e != nil { | |||
| 109 | + | t.Fatal(e) | |||
| 110 | + | } | |||
| 111 | + | if a, e := s.ModNameActive(raw.XKB_MOD_NAME_CTRL); e != nil || !a { | |||
| 112 | + | t.Fatalf("ctrl name active: %v %v", a, e) | |||
| 113 | + | } | |||
| 114 | + | if a, e := s.ModIndexActive(ctrl); e != nil || !a { | |||
| 115 | + | t.Fatalf("ctrl index active: %v %v", a, e) | |||
| 116 | + | } | |||
| 117 | + | if a, e := s.ModNameActive("NoSuchModifier"); e != nil || a { | |||
| 118 | + | t.Fatalf("unknown modifier active: %v %v", a, e) | |||
| 119 | + | } | |||
| 120 | + | var buf [16]byte | |||
| 121 | + | n, e := KeysymNameInto(raw.XKB_KEY_EuroSign, buf[:]) | |||
| 122 | + | if e != nil || string(buf[:n]) != "EuroSign" { | |||
| 123 | + | t.Fatalf("KeysymNameInto = %q %v", buf[:n], e) | |||
| 124 | + | } | |||
| 125 | + | } | |||
| 126 | + | ||||
| 127 | + | func TestModNameActiveLongName(t *testing.T) { | |||
| 128 | + | c := live(t) | |||
| 129 | + | k := fdKeymap(t, c, "us") | |||
| 130 | + | s := state(t, k) | |||
| 131 | + | for _, n := range []int{62, 63, 64, 65, 200} { | |||
| 132 | + | name := strings.Repeat("x", n) | |||
| 133 | + | if a, e := s.ModNameActive(name); e != nil || a { | |||
| 134 | + | t.Fatalf("len %d: %v %v", n, a, e) | |||
| 135 | + | } | |||
| 136 | + | } | |||
| 137 | + | // A short name after a long one must not see stale scratch bytes. | |||
| 138 | + | ctrl, e := k.ModIndex(raw.XKB_MOD_NAME_CTRL) | |||
| 139 | + | if e != nil { | |||
| 140 | + | t.Fatal(e) | |||
| 141 | + | } | |||
| 142 | + | if _, e := s.UpdateMask(1<<ctrl, 0, 0, 0, 0, 0); e != nil { | |||
| 143 | + | t.Fatal(e) | |||
| 144 | + | } | |||
| 145 | + | if a, e := s.ModNameActive(strings.Repeat("y", 63)); e != nil || a { | |||
| 146 | + | t.Fatal(a, e) | |||
| 147 | + | } | |||
| 148 | + | if a, e := s.ModNameActive(raw.XKB_MOD_NAME_CTRL); e != nil || !a { | |||
| 149 | + | t.Fatal(a, e) | |||
| 150 | + | } | |||
| 151 | + | if _, e := s.ModNameActive("a\x00b"); e == nil { | |||
| 152 | + | t.Fatal("embedded NUL accepted") | |||
| 153 | + | } | |||
| 154 | + | } | |||
| 155 | + | ||||
| 156 | + | func TestKeysymNameIntoShortBuffer(t *testing.T) { | |||
| 157 | + | live(t) | |||
| 158 | + | buf := []byte{0xff, 0xff, 0xff} | |||
| 159 | + | n, e := KeysymNameInto(raw.XKB_KEY_EuroSign, buf) | |||
| 160 | + | if !errors.Is(e, io.ErrShortBuffer) || n != len("EuroSign") || !bytes.Equal(buf, make([]byte, 3)) { | |||
| 161 | + | t.Fatalf("n=%d err=%v buf=%v", n, e, buf) | |||
| 162 | + | } | |||
| 163 | + | if _, e = KeysymNameInto(raw.XKB_KEY_a, nil); !errors.Is(e, ErrUTF8Buffer) { | |||
| 164 | + | t.Fatal(e) | |||
| 165 | + | } | |||
| 166 | + | } | |||
| 167 | + | ||||
| 168 | + | func TestModIndexActiveInvalid(t *testing.T) { | |||
| 169 | + | c := live(t) | |||
| 170 | + | s := state(t, fdKeymap(t, c, "us")) | |||
| 171 | + | for _, idx := range []uint32{64, 1 << 20, ^uint32(0)} { | |||
| 172 | + | if a, e := s.ModIndexActive(idx); e != nil || a { | |||
| 173 | + | t.Fatalf("idx %d: %v %v", idx, a, e) | |||
| 174 | + | } | |||
| 175 | + | } | |||
| 176 | + | s.Close() | |||
| 177 | + | if _, e := s.ModIndexActive(0); !errors.Is(e, ErrClosed) { | |||
| 178 | + | t.Fatal(e) | |||
| 179 | + | } | |||
| 180 | + | if _, e := s.ModNameActive("Shift"); !errors.Is(e, ErrClosed) { | |||
| 181 | + | t.Fatal(e) | |||
| 182 | + | } | |||
| 183 | + | } | |||
| 184 | + | ||||
| 185 | + | func TestNewKeymapRulesNames(t *testing.T) { | |||
| 186 | + | c := live(t) | |||
| 187 | + | // Every RMLVO component is exercised so each pointer in RuleNames must stay valid. | |||
| 188 | + | k, e := c.NewKeymapRules("evdev", "pc105", "us,fr", "dvorak,", "grp:alt_shift_toggle") | |||
| 189 | + | if e != nil { | |||
| 190 | + | t.Fatal(e) | |||
| 191 | + | } | |||
| 192 | + | defer k.Close() | |||
| 193 | + | if n, e := k.LayoutCount(); e != nil || n != 2 { | |||
| 194 | + | t.Fatalf("layouts %d %v", n, e) | |||
| 195 | + | } | |||
| 196 | + | s := state(t, k) | |||
| 197 | + | runtime.GC() | |||
| 198 | + | // Dvorak puts apostrophe on the evdev KEY_Q position. | |||
| 199 | + | if sym, e := s.KeySym(WaylandKeycode(16)); e != nil || sym != raw.XKB_KEY_apostrophe { | |||
| 200 | + | t.Fatalf("dvorak sym %#x %v", sym, e) | |||
| 201 | + | } | |||
| 202 | + | // Defaults (all empty) still work, and an unknown layout is rejected. | |||
| 203 | + | d, e := c.NewKeymapRules("", "", "", "", "") | |||
| 204 | + | if e != nil { | |||
| 205 | + | t.Fatal(e) | |||
| 206 | + | } | |||
| 207 | + | d.Close() | |||
| 208 | + | if _, e = c.NewKeymapRules("evdev", "pc105", "nosuchlayout", "", ""); e == nil { | |||
| 209 | + | t.Fatal("unknown layout accepted") | |||
| 210 | + | } | |||
| 211 | + | } | |||
| 212 | + | ||||
| 213 | + | func TestZeroFuncsPanics(t *testing.T) { | |||
| 214 | + | defer func() { | |||
| 215 | + | if recover() == nil { | |||
| 216 | + | t.Fatal("zero raw.Funcs must panic, not call address 0") | |||
| 217 | + | } | |||
| 218 | + | }() | |||
| 219 | + | var f raw.Funcs | |||
| 220 | + | f.Xkb_keymap_num_layouts(0) | |||
| 221 | + | } | |||
M internal/gen/gen.go +86 -15
| @@ -54,24 +54,89 @@ func constants(h string) string { | |||
| 54 | 54 | } | |
| 55 | 55 | ||
| 56 | 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. | |
| 57 | + | // Go memory is passed as unsafe.Pointer and converted inside the Syscall call | |
| 58 | + | // expression; native handles (contexts, keymaps, states) are uintptr. | |
| 58 | 59 | var Functions = []struct{ Name, Signature string }{ | |
| 59 | 60 | {"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_new_from_names", "func(uintptr, unsafe.Pointer, uint32) uintptr"}, {"xkb_keymap_new_from_string", "func(uintptr, unsafe.Pointer, uint32, uint32) uintptr"}, {"xkb_keymap_new_from_buffer", "func(uintptr, unsafe.Pointer, uintptr, uint32, uint32) uintptr"}, | |
| 61 | 62 | {"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_keymap_mod_get_index", "func(uintptr,unsafe.Pointer) uint32"}, {"xkb_keymap_key_repeats", "func(uintptr,uint32) int32"}, {"xkb_keymap_num_layouts", "func(uintptr) uint32"}, | |
| 63 | 64 | {"xkb_state_new", "func(uintptr) uintptr"}, {"xkb_state_ref", "func(uintptr) uintptr"}, {"xkb_state_unref", "func(uintptr)"}, | |
| 64 | 65 | {"xkb_state_update_mask", "func(uintptr,uint32,uint32,uint32,uint32,uint32,uint32) uint32"}, | |
| 65 | 66 | {"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)"}, | |
| 67 | + | {"xkb_state_mod_name_is_active", "func(uintptr,unsafe.Pointer,uint32) int32"}, {"xkb_state_mod_index_is_active", "func(uintptr,uint32,uint32) int32"}, | |
| 68 | + | {"xkb_state_key_get_one_sym", "func(uintptr,uint32) uint32"}, {"xkb_state_key_get_utf8", "func(uintptr,uint32,unsafe.Pointer,uintptr) int32"}, | |
| 69 | + | {"xkb_keysym_get_name", "func(uint32,unsafe.Pointer,uintptr) int32"}, {"xkb_keysym_from_name", "func(unsafe.Pointer,uint32) uint32"}, | |
| 70 | + | {"xkb_compose_table_new_from_locale", "func(uintptr,unsafe.Pointer,uint32) uintptr"}, {"xkb_compose_table_ref", "func(uintptr) uintptr"}, {"xkb_compose_table_unref", "func(uintptr)"}, | |
| 69 | 71 | {"xkb_compose_state_new", "func(uintptr,uint32) uintptr"}, {"xkb_compose_state_ref", "func(uintptr) uintptr"}, {"xkb_compose_state_unref", "func(uintptr)"}, | |
| 70 | 72 | {"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"}, | |
| 73 | + | {"xkb_compose_state_reset", "func(uintptr)"}, {"xkb_compose_state_get_utf8", "func(uintptr,unsafe.Pointer,uintptr) int32"}, {"xkb_compose_state_get_one_sym", "func(uintptr) uint32"}, | |
| 72 | 74 | } | |
| 73 | 75 | ||
| 74 | 76 | func field(name string) string { return strings.ToUpper(name[:1]) + name[1:] } | |
| 77 | + | ||
| 78 | + | // Only fixed-width integer-class values reach Syscall6/Syscall15: floats, bools, | |
| 79 | + | // varargs and structs would be silently mis-passed, so the generator rejects them. | |
| 80 | + | // unsafe.Pointer is accepted for parameters only (Go memory handed to C); | |
| 81 | + | // uintptr is for C-owned handles and sizes. | |
| 82 | + | var allowedParams = map[string]bool{"uintptr": true, "uint32": true, "int32": true, "unsafe.Pointer": true} | |
| 83 | + | var allowedReturns = map[string]bool{"": true, "uintptr": true, "uint32": true, "int32": true} | |
| 84 | + | ||
| 85 | + | // method renders an exported, allocation-free wrapper calling the resolved | |
| 86 | + | // address through purego.Syscall6 (up to 6 arguments) or Syscall15. | |
| 87 | + | func method(name, sig string) (string, error) { | |
| 88 | + | rest, ok := strings.CutPrefix(sig, "func(") | |
| 89 | + | if !ok { | |
| 90 | + | return "", fmt.Errorf("bad signature for %s: %s", name, sig) | |
| 91 | + | } | |
| 92 | + | params, ret, _ := strings.Cut(rest, ")") | |
| 93 | + | ret = strings.TrimSpace(ret) | |
| 94 | + | var types []string | |
| 95 | + | if strings.TrimSpace(params) != "" { | |
| 96 | + | for _, p := range strings.Split(params, ",") { | |
| 97 | + | types = append(types, strings.TrimSpace(p)) | |
| 98 | + | } | |
| 99 | + | } | |
| 100 | + | if len(types) > 15 { | |
| 101 | + | return "", fmt.Errorf("%s: more than 15 arguments", name) | |
| 102 | + | } | |
| 103 | + | if !allowedReturns[ret] { | |
| 104 | + | return "", fmt.Errorf("%s: unsupported return type %q", name, ret) | |
| 105 | + | } | |
| 106 | + | var decl, args []string | |
| 107 | + | for i, t := range types { | |
| 108 | + | if !allowedParams[t] { | |
| 109 | + | return "", fmt.Errorf("%s: unsupported parameter type %q", name, t) | |
| 110 | + | } | |
| 111 | + | decl = append(decl, fmt.Sprintf("a%d %s", i+1, t)) | |
| 112 | + | // The uintptr conversion must stay inside the Syscall call expression so | |
| 113 | + | // //go:uintptrescapes keeps unsafe.Pointer arguments alive and unmoved. | |
| 114 | + | if t == "uintptr" { | |
| 115 | + | args = append(args, fmt.Sprintf("a%d", i+1)) | |
| 116 | + | } else { | |
| 117 | + | args = append(args, fmt.Sprintf("uintptr(a%d)", i+1)) | |
| 118 | + | } | |
| 119 | + | } | |
| 120 | + | call, size := "purego.Syscall6", 6 | |
| 121 | + | if len(types) > 6 { | |
| 122 | + | call, size = "purego.Syscall15", 15 | |
| 123 | + | } | |
| 124 | + | for len(args) < size { | |
| 125 | + | args = append(args, "0") | |
| 126 | + | } | |
| 127 | + | var b strings.Builder | |
| 128 | + | fmt.Fprintf(&b, "\n// %s calls %s without allocating.\nfunc (f *Funcs) %s(%s) %s {\n", field(name), name, field(name), strings.Join(decl, ", "), ret) | |
| 129 | + | switch ret { | |
| 130 | + | case "": | |
| 131 | + | fmt.Fprintf(&b, "%s(f.%s, %s)\n", call, name, strings.Join(args, ", ")) | |
| 132 | + | case "uintptr": | |
| 133 | + | fmt.Fprintf(&b, "r, _, _ := %s(f.%s, %s)\nreturn r\n", call, name, strings.Join(args, ", ")) | |
| 134 | + | default: | |
| 135 | + | fmt.Fprintf(&b, "r, _, _ := %s(f.%s, %s)\nreturn %s(r)\n", call, name, strings.Join(args, ", "), ret) | |
| 136 | + | } | |
| 137 | + | b.WriteString("}\n") | |
| 138 | + | return b.String(), nil | |
| 139 | + | } | |
| 75 | 140 | func Generate(root string) error { | |
| 76 | 141 | h1, err := os.ReadFile(root + "/upstream/xkbcommon.h") | |
| 77 | 142 | if err != nil { | |
| @@ -83,9 +148,9 @@ func Generate(root string) error { | |||
| 83 | 148 | } | |
| 84 | 149 | hdr := string(h1) + "\n" + string(h2) | |
| 85 | 150 | var b strings.Builder | |
| 86 | - | b.WriteString("// Code generated by go generate; DO NOT EDIT.\npackage raw\n\n") | |
| 151 | + | b.WriteString("// Code generated by go generate; DO NOT EDIT.\npackage raw\n\nimport (\n\"unsafe\"\n\n\"github.com/bnema/purego\"\n)\n\n") | |
| 87 | 152 | 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") | |
| 153 | + | b.WriteString("// RuleNames matches struct xkb_rule_names (five const char pointers). The\n// fields are unsafe.Pointer so the GC sees the NUL-terminated strings they reference.\ntype RuleNames struct { Rules, Model, Layout, Variant, Options unsafe.Pointer }\n") | |
| 89 | 154 | b.WriteString("const (\n" + constants(hdr)) | |
| 90 | 155 | for _, f := range []string{"xkbcommon-keysyms.h", "xkbcommon-names.h"} { | |
| 91 | 156 | data, e := os.ReadFile(root + "/upstream/" + f) | |
| @@ -94,24 +159,30 @@ func Generate(root string) error { | |||
| 94 | 159 | } | |
| 95 | 160 | b.WriteString(constants(string(data))) | |
| 96 | 161 | } | |
| 97 | - | b.WriteString(")\n\n// Funcs holds resolved library functions.\ntype Funcs struct {\n") | |
| 162 | + | b.WriteString(")\n\n// Funcs holds resolved library function addresses. Each function is invoked\n// through purego.Syscall6/Syscall15 (fixed arity, no reflection, no allocation).\ntype Funcs struct {\n") | |
| 98 | 163 | for _, f := range Functions { | |
| 99 | 164 | if !declared(hdr, f.Name) { | |
| 100 | 165 | return fmt.Errorf("missing pinned declaration: %s", f.Name) | |
| 101 | 166 | } | |
| 102 | - | fmt.Fprintf(&b, "%s %s\n", field(f.Name), f.Signature) | |
| 167 | + | fmt.Fprintf(&b, "%s uintptr\n", f.Name) | |
| 103 | 168 | } | |
| 104 | 169 | b.WriteString("}\n\n// Symbols is the complete required symbol set.\nvar Symbols = []string{\n") | |
| 105 | 170 | for _, f := range Functions { | |
| 106 | 171 | fmt.Fprintf(&b, "%q,\n", f.Name) | |
| 107 | 172 | } | |
| 108 | - | b.WriteString("}\n\n// Register registers prevalidated addresses in a Funcs value.\nfunc Register(f *Funcs, register func(any,uintptr)) {\n") | |
| 173 | + | b.WriteString("}\n\n// Register stores prevalidated symbol addresses in a Funcs value.\nfunc Register(f *Funcs, addresses map[string]uintptr) {\n") | |
| 109 | 174 | for _, f := range Functions { | |
| 110 | - | fmt.Fprintf(&b, "register(&f.%s, addresses[%q])\n", field(f.Name), f.Name) | |
| 175 | + | fmt.Fprintf(&b, "f.%s = addresses[%q]\n", f.Name, f.Name) | |
| 111 | 176 | } | |
| 112 | 177 | 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) | |
| 178 | + | for _, f := range Functions { | |
| 179 | + | m, err := method(f.Name, f.Signature) | |
| 180 | + | if err != nil { | |
| 181 | + | return err | |
| 182 | + | } | |
| 183 | + | b.WriteString(m) | |
| 184 | + | } | |
| 185 | + | s := b.String() | |
| 115 | 186 | formatted, err := format.Source([]byte(s)) | |
| 116 | 187 | if err != nil { | |
| 117 | 188 | return fmt.Errorf("format generated file: %w", err) | |
| @@ -54,24 +54,89 @@ func constants(h string) string { | |||||
| 54 | } | 54 | } | ||
| 55 | 55 | ||||
| 56 | // Functions describes the bound ABI; each name is checked against the pinned headers. | 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. | 57 | + | // Go memory is passed as unsafe.Pointer and converted inside the Syscall call |
| 58 | + | // expression; native handles (contexts, keymaps, states) are uintptr. | |||
| 58 | var Functions = []struct{ Name, Signature string }{ | 59 | 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_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_new_from_names", "func(uintptr, unsafe.Pointer, uint32) uintptr"}, {"xkb_keymap_new_from_string", "func(uintptr, unsafe.Pointer, uint32, uint32) uintptr"}, {"xkb_keymap_new_from_buffer", "func(uintptr, unsafe.Pointer, 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_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_keymap_mod_get_index", "func(uintptr,unsafe.Pointer) uint32"}, {"xkb_keymap_key_repeats", "func(uintptr,uint32) int32"}, {"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_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_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_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_state_mod_name_is_active", "func(uintptr,unsafe.Pointer,uint32) int32"}, {"xkb_state_mod_index_is_active", "func(uintptr,uint32,uint32) int32"}, |
| 67 | - | {"xkb_keysym_get_name", "func(uint32,uintptr,uintptr) int32"}, {"xkb_keysym_from_name", "func(uintptr,uint32) uint32"}, | 68 | + | {"xkb_state_key_get_one_sym", "func(uintptr,uint32) uint32"}, {"xkb_state_key_get_utf8", "func(uintptr,uint32,unsafe.Pointer,uintptr) int32"}, |
| 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_keysym_get_name", "func(uint32,unsafe.Pointer,uintptr) int32"}, {"xkb_keysym_from_name", "func(unsafe.Pointer,uint32) uint32"}, |
| 70 | + | {"xkb_compose_table_new_from_locale", "func(uintptr,unsafe.Pointer,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)"}, | 71 | {"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"}, | 72 | {"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"}, | 73 | + | {"xkb_compose_state_reset", "func(uintptr)"}, {"xkb_compose_state_get_utf8", "func(uintptr,unsafe.Pointer,uintptr) int32"}, {"xkb_compose_state_get_one_sym", "func(uintptr) uint32"}, |
| 72 | } | 74 | } | ||
| 73 | 75 | ||||
| 74 | func field(name string) string { return strings.ToUpper(name[:1]) + name[1:] } | 76 | func field(name string) string { return strings.ToUpper(name[:1]) + name[1:] } | ||
| 77 | + | ||||
| 78 | + | // Only fixed-width integer-class values reach Syscall6/Syscall15: floats, bools, | |||
| 79 | + | // varargs and structs would be silently mis-passed, so the generator rejects them. | |||
| 80 | + | // unsafe.Pointer is accepted for parameters only (Go memory handed to C); | |||
| 81 | + | // uintptr is for C-owned handles and sizes. | |||
| 82 | + | var allowedParams = map[string]bool{"uintptr": true, "uint32": true, "int32": true, "unsafe.Pointer": true} | |||
| 83 | + | var allowedReturns = map[string]bool{"": true, "uintptr": true, "uint32": true, "int32": true} | |||
| 84 | + | ||||
| 85 | + | // method renders an exported, allocation-free wrapper calling the resolved | |||
| 86 | + | // address through purego.Syscall6 (up to 6 arguments) or Syscall15. | |||
| 87 | + | func method(name, sig string) (string, error) { | |||
| 88 | + | rest, ok := strings.CutPrefix(sig, "func(") | |||
| 89 | + | if !ok { | |||
| 90 | + | return "", fmt.Errorf("bad signature for %s: %s", name, sig) | |||
| 91 | + | } | |||
| 92 | + | params, ret, _ := strings.Cut(rest, ")") | |||
| 93 | + | ret = strings.TrimSpace(ret) | |||
| 94 | + | var types []string | |||
| 95 | + | if strings.TrimSpace(params) != "" { | |||
| 96 | + | for _, p := range strings.Split(params, ",") { | |||
| 97 | + | types = append(types, strings.TrimSpace(p)) | |||
| 98 | + | } | |||
| 99 | + | } | |||
| 100 | + | if len(types) > 15 { | |||
| 101 | + | return "", fmt.Errorf("%s: more than 15 arguments", name) | |||
| 102 | + | } | |||
| 103 | + | if !allowedReturns[ret] { | |||
| 104 | + | return "", fmt.Errorf("%s: unsupported return type %q", name, ret) | |||
| 105 | + | } | |||
| 106 | + | var decl, args []string | |||
| 107 | + | for i, t := range types { | |||
| 108 | + | if !allowedParams[t] { | |||
| 109 | + | return "", fmt.Errorf("%s: unsupported parameter type %q", name, t) | |||
| 110 | + | } | |||
| 111 | + | decl = append(decl, fmt.Sprintf("a%d %s", i+1, t)) | |||
| 112 | + | // The uintptr conversion must stay inside the Syscall call expression so | |||
| 113 | + | // //go:uintptrescapes keeps unsafe.Pointer arguments alive and unmoved. | |||
| 114 | + | if t == "uintptr" { | |||
| 115 | + | args = append(args, fmt.Sprintf("a%d", i+1)) | |||
| 116 | + | } else { | |||
| 117 | + | args = append(args, fmt.Sprintf("uintptr(a%d)", i+1)) | |||
| 118 | + | } | |||
| 119 | + | } | |||
| 120 | + | call, size := "purego.Syscall6", 6 | |||
| 121 | + | if len(types) > 6 { | |||
| 122 | + | call, size = "purego.Syscall15", 15 | |||
| 123 | + | } | |||
| 124 | + | for len(args) < size { | |||
| 125 | + | args = append(args, "0") | |||
| 126 | + | } | |||
| 127 | + | var b strings.Builder | |||
| 128 | + | fmt.Fprintf(&b, "\n// %s calls %s without allocating.\nfunc (f *Funcs) %s(%s) %s {\n", field(name), name, field(name), strings.Join(decl, ", "), ret) | |||
| 129 | + | switch ret { | |||
| 130 | + | case "": | |||
| 131 | + | fmt.Fprintf(&b, "%s(f.%s, %s)\n", call, name, strings.Join(args, ", ")) | |||
| 132 | + | case "uintptr": | |||
| 133 | + | fmt.Fprintf(&b, "r, _, _ := %s(f.%s, %s)\nreturn r\n", call, name, strings.Join(args, ", ")) | |||
| 134 | + | default: | |||
| 135 | + | fmt.Fprintf(&b, "r, _, _ := %s(f.%s, %s)\nreturn %s(r)\n", call, name, strings.Join(args, ", "), ret) | |||
| 136 | + | } | |||
| 137 | + | b.WriteString("}\n") | |||
| 138 | + | return b.String(), nil | |||
| 139 | + | } | |||
| 75 | func Generate(root string) error { | 140 | func Generate(root string) error { | ||
| 76 | h1, err := os.ReadFile(root + "/upstream/xkbcommon.h") | 141 | h1, err := os.ReadFile(root + "/upstream/xkbcommon.h") | ||
| 77 | if err != nil { | 142 | if err != nil { | ||
| @@ -83,9 +148,9 @@ func Generate(root string) error { | |||||
| 83 | } | 148 | } | ||
| 84 | hdr := string(h1) + "\n" + string(h2) | 149 | hdr := string(h1) + "\n" + string(h2) | ||
| 85 | var b strings.Builder | 150 | var b strings.Builder | ||
| 86 | - | b.WriteString("// Code generated by go generate; DO NOT EDIT.\npackage raw\n\n") | 151 | + | b.WriteString("// Code generated by go generate; DO NOT EDIT.\npackage raw\n\nimport (\n\"unsafe\"\n\n\"github.com/bnema/purego\"\n)\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") | 152 | 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") | 153 | + | b.WriteString("// RuleNames matches struct xkb_rule_names (five const char pointers). The\n// fields are unsafe.Pointer so the GC sees the NUL-terminated strings they reference.\ntype RuleNames struct { Rules, Model, Layout, Variant, Options unsafe.Pointer }\n") |
| 89 | b.WriteString("const (\n" + constants(hdr)) | 154 | b.WriteString("const (\n" + constants(hdr)) | ||
| 90 | for _, f := range []string{"xkbcommon-keysyms.h", "xkbcommon-names.h"} { | 155 | for _, f := range []string{"xkbcommon-keysyms.h", "xkbcommon-names.h"} { | ||
| 91 | data, e := os.ReadFile(root + "/upstream/" + f) | 156 | data, e := os.ReadFile(root + "/upstream/" + f) | ||
| @@ -94,24 +159,30 @@ func Generate(root string) error { | |||||
| 94 | } | 159 | } | ||
| 95 | b.WriteString(constants(string(data))) | 160 | b.WriteString(constants(string(data))) | ||
| 96 | } | 161 | } | ||
| 97 | - | b.WriteString(")\n\n// Funcs holds resolved library functions.\ntype Funcs struct {\n") | 162 | + | b.WriteString(")\n\n// Funcs holds resolved library function addresses. Each function is invoked\n// through purego.Syscall6/Syscall15 (fixed arity, no reflection, no allocation).\ntype Funcs struct {\n") |
| 98 | for _, f := range Functions { | 163 | for _, f := range Functions { | ||
| 99 | if !declared(hdr, f.Name) { | 164 | if !declared(hdr, f.Name) { | ||
| 100 | return fmt.Errorf("missing pinned declaration: %s", f.Name) | 165 | return fmt.Errorf("missing pinned declaration: %s", f.Name) | ||
| 101 | } | 166 | } | ||
| 102 | - | fmt.Fprintf(&b, "%s %s\n", field(f.Name), f.Signature) | 167 | + | fmt.Fprintf(&b, "%s uintptr\n", f.Name) |
| 103 | } | 168 | } | ||
| 104 | b.WriteString("}\n\n// Symbols is the complete required symbol set.\nvar Symbols = []string{\n") | 169 | b.WriteString("}\n\n// Symbols is the complete required symbol set.\nvar Symbols = []string{\n") | ||
| 105 | for _, f := range Functions { | 170 | for _, f := range Functions { | ||
| 106 | fmt.Fprintf(&b, "%q,\n", f.Name) | 171 | fmt.Fprintf(&b, "%q,\n", f.Name) | ||
| 107 | } | 172 | } | ||
| 108 | - | b.WriteString("}\n\n// Register registers prevalidated addresses in a Funcs value.\nfunc Register(f *Funcs, register func(any,uintptr)) {\n") | 173 | + | b.WriteString("}\n\n// Register stores prevalidated symbol addresses in a Funcs value.\nfunc Register(f *Funcs, addresses map[string]uintptr) {\n") |
| 109 | for _, f := range Functions { | 174 | for _, f := range Functions { | ||
| 110 | - | fmt.Fprintf(&b, "register(&f.%s, addresses[%q])\n", field(f.Name), f.Name) | 175 | + | fmt.Fprintf(&b, "f.%s = addresses[%q]\n", f.Name, f.Name) |
| 111 | } | 176 | } | ||
| 112 | b.WriteString("}\n") | 177 | b.WriteString("}\n") | ||
| 113 | - | // address map is passed in, not a global mutable singleton. | 178 | + | for _, f := range Functions { |
| 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) | 179 | + | m, err := method(f.Name, f.Signature) |
| 180 | + | if err != nil { | |||
| 181 | + | return err | |||
| 182 | + | } | |||
| 183 | + | b.WriteString(m) | |||
| 184 | + | } | |||
| 185 | + | s := b.String() | |||
| 115 | formatted, err := format.Source([]byte(s)) | 186 | formatted, err := format.Source([]byte(s)) | ||
| 116 | if err != nil { | 187 | if err != nil { | ||
| 117 | return fmt.Errorf("format generated file: %w", err) | 188 | return fmt.Errorf("format generated file: %w", err) | ||
M internal/gen/gen_test.go +20 -0
| @@ -23,3 +23,23 @@ func TestConstants(t *testing.T) { | |||
| 23 | 23 | } | |
| 24 | 24 | } | |
| 25 | 25 | } | |
| 26 | + | ||
| 27 | + | func TestMethodWhitelist(t *testing.T) { | |
| 28 | + | for _, sig := range []string{"func(float64) uint32", "func(bool)", "func(uintptr, ...uintptr) uint32", "func(uint32) float32", "func(uint32) unsafe.Pointer", "func(*int)", "func(uint32) bool"} { | |
| 29 | + | if _, err := method("xkb_bad", sig); err == nil { | |
| 30 | + | t.Fatalf("accepted %s", sig) | |
| 31 | + | } | |
| 32 | + | } | |
| 33 | + | got, err := method("xkb_ok", "func(uintptr, unsafe.Pointer, int32) uint32") | |
| 34 | + | if err != nil { | |
| 35 | + | t.Fatal(err) | |
| 36 | + | } | |
| 37 | + | for _, want := range []string{"purego.Syscall6(f.xkb_ok, a1, uintptr(a2), uintptr(a3), 0, 0, 0)", "return uint32(r)", "a2 unsafe.Pointer"} { | |
| 38 | + | if !strings.Contains(got, want) { | |
| 39 | + | t.Fatalf("missing %q in %s", want, got) | |
| 40 | + | } | |
| 41 | + | } | |
| 42 | + | if got, _ = method("xkb_seven", "func(uintptr,uint32,uint32,uint32,uint32,uint32,uint32)"); !strings.Contains(got, "purego.Syscall15(") { | |
| 43 | + | t.Fatal("seven args must use Syscall15") | |
| 44 | + | } | |
| 45 | + | } | |
| @@ -23,3 +23,23 @@ func TestConstants(t *testing.T) { | |||||
| 23 | } | 23 | } | ||
| 24 | } | 24 | } | ||
| 25 | } | 25 | } | ||
| 26 | + | ||||
| 27 | + | func TestMethodWhitelist(t *testing.T) { | |||
| 28 | + | for _, sig := range []string{"func(float64) uint32", "func(bool)", "func(uintptr, ...uintptr) uint32", "func(uint32) float32", "func(uint32) unsafe.Pointer", "func(*int)", "func(uint32) bool"} { | |||
| 29 | + | if _, err := method("xkb_bad", sig); err == nil { | |||
| 30 | + | t.Fatalf("accepted %s", sig) | |||
| 31 | + | } | |||
| 32 | + | } | |||
| 33 | + | got, err := method("xkb_ok", "func(uintptr, unsafe.Pointer, int32) uint32") | |||
| 34 | + | if err != nil { | |||
| 35 | + | t.Fatal(err) | |||
| 36 | + | } | |||
| 37 | + | for _, want := range []string{"purego.Syscall6(f.xkb_ok, a1, uintptr(a2), uintptr(a3), 0, 0, 0)", "return uint32(r)", "a2 unsafe.Pointer"} { | |||
| 38 | + | if !strings.Contains(got, want) { | |||
| 39 | + | t.Fatalf("missing %q in %s", want, got) | |||
| 40 | + | } | |||
| 41 | + | } | |||
| 42 | + | if got, _ = method("xkb_seven", "func(uintptr,uint32,uint32,uint32,uint32,uint32,uint32)"); !strings.Contains(got, "purego.Syscall15(") { | |||
| 43 | + | t.Fatal("seven args must use Syscall15") | |||
| 44 | + | } | |||
| 45 | + | } | |||
M loader.go +3 -3
| @@ -15,7 +15,7 @@ var loader struct { | |||
| 15 | 15 | } | |
| 16 | 16 | ||
| 17 | 17 | // resolveLibrary keeps path and required symbols injectable for diagnostic tests. | |
| 18 | - | // On success it leaves the library open for the lifetime of registered functions. | |
| 18 | + | // On success it leaves the library open for the lifetime of resolved function addresses. | |
| 19 | 19 | func resolveLibrary(path string, symbols []string) (map[string]uintptr, error) { | |
| 20 | 20 | lib, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_LOCAL) | |
| 21 | 21 | if err != nil { | |
| @@ -42,8 +42,8 @@ func Available() error { | |||
| 42 | 42 | loader.err = err | |
| 43 | 43 | return | |
| 44 | 44 | } | |
| 45 | - | raw.Register(&loader.f, addresses, purego.RegisterFunc) | |
| 46 | - | // Intentionally keep the library open: registered function pointers and live | |
| 45 | + | raw.Register(&loader.f, addresses) | |
| 46 | + | // Intentionally keep the library open: resolved function addresses and live | |
| 47 | 47 | // native objects remain valid for the process lifetime. | |
| 48 | 48 | }) | |
| 49 | 49 | return loader.err | |
| @@ -15,7 +15,7 @@ var loader struct { | |||||
| 15 | } | 15 | } | ||
| 16 | 16 | ||||
| 17 | // resolveLibrary keeps path and required symbols injectable for diagnostic tests. | 17 | // resolveLibrary keeps path and required symbols injectable for diagnostic tests. | ||
| 18 | - | // On success it leaves the library open for the lifetime of registered functions. | 18 | + | // On success it leaves the library open for the lifetime of resolved function addresses. |
| 19 | func resolveLibrary(path string, symbols []string) (map[string]uintptr, error) { | 19 | func resolveLibrary(path string, symbols []string) (map[string]uintptr, error) { | ||
| 20 | lib, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_LOCAL) | 20 | lib, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_LOCAL) | ||
| 21 | if err != nil { | 21 | if err != nil { | ||
| @@ -42,8 +42,8 @@ func Available() error { | |||||
| 42 | loader.err = err | 42 | loader.err = err | ||
| 43 | return | 43 | return | ||
| 44 | } | 44 | } | ||
| 45 | - | raw.Register(&loader.f, addresses, purego.RegisterFunc) | 45 | + | raw.Register(&loader.f, addresses) |
| 46 | - | // Intentionally keep the library open: registered function pointers and live | 46 | + | // Intentionally keep the library open: resolved function addresses and live |
| 47 | // native objects remain valid for the process lifetime. | 47 | // native objects remain valid for the process lifetime. | ||
| 48 | }) | 48 | }) | ||
| 49 | return loader.err | 49 | return loader.err | ||
M raw/generated.go +290 -69
| @@ -1,6 +1,12 @@ | |||
| 1 | 1 | // Code generated by go generate; DO NOT EDIT. | |
| 2 | 2 | package raw | |
| 3 | 3 | ||
| 4 | + | import ( | |
| 5 | + | "unsafe" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/purego" | |
| 8 | + | ) | |
| 9 | + | ||
| 4 | 10 | // Opaque C handles and scalar typedefs. | |
| 5 | 11 | type Context uintptr | |
| 6 | 12 | type Keymap uintptr | |
| @@ -14,8 +20,9 @@ type ModIndex uint32 | |||
| 14 | 20 | type ModMask uint32 | |
| 15 | 21 | type LayoutMask uint32 | |
| 16 | 22 | ||
| 17 | - | // RuleNames matches struct xkb_rule_names (five const char pointers). | |
| 18 | - | type RuleNames struct{ Rules, Model, Layout, Variant, Options uintptr } | |
| 23 | + | // RuleNames matches struct xkb_rule_names (five const char pointers). The | |
| 24 | + | // fields are unsafe.Pointer so the GC sees the NUL-terminated strings they reference. | |
| 25 | + | type RuleNames struct{ Rules, Model, Layout, Variant, Options unsafe.Pointer } | |
| 19 | 26 | ||
| 20 | 27 | const ( | |
| 21 | 28 | XKB_RMLVO_BUILDER_NO_FLAGS = 0 | |
| @@ -2725,40 +2732,44 @@ const ( | |||
| 2725 | 2732 | XKB_LED_NAME_KANA = "Kana" | |
| 2726 | 2733 | ) | |
| 2727 | 2734 | ||
| 2728 | - | // Funcs holds resolved library functions. | |
| 2735 | + | // Funcs holds resolved library function addresses. Each function is invoked | |
| 2736 | + | // through purego.Syscall6/Syscall15 (fixed arity, no reflection, no allocation). | |
| 2729 | 2737 | type Funcs struct { | |
| 2730 | - | Xkb_context_new func(uint32) uintptr | |
| 2731 | - | Xkb_context_ref func(uintptr) uintptr | |
| 2732 | - | Xkb_context_unref func(uintptr) | |
| 2733 | - | Xkb_keymap_new_from_names func(uintptr, uintptr, uint32) uintptr | |
| 2734 | - | Xkb_keymap_new_from_string func(uintptr, uintptr, uint32, uint32) uintptr | |
| 2735 | - | Xkb_keymap_new_from_buffer func(uintptr, uintptr, uintptr, uint32, uint32) uintptr | |
| 2736 | - | Xkb_keymap_ref func(uintptr) uintptr | |
| 2737 | - | Xkb_keymap_unref func(uintptr) | |
| 2738 | - | Xkb_keymap_get_as_string func(uintptr, uint32) uintptr | |
| 2739 | - | Xkb_keymap_mod_get_index func(uintptr, uintptr) uint32 | |
| 2740 | - | Xkb_keymap_num_layouts func(uintptr) uint32 | |
| 2741 | - | Xkb_state_new func(uintptr) uintptr | |
| 2742 | - | Xkb_state_ref func(uintptr) uintptr | |
| 2743 | - | Xkb_state_unref func(uintptr) | |
| 2744 | - | Xkb_state_update_mask func(uintptr, uint32, uint32, uint32, uint32, uint32, uint32) uint32 | |
| 2745 | - | Xkb_state_serialize_layout func(uintptr, uint32) uint32 | |
| 2746 | - | Xkb_state_serialize_mods func(uintptr, uint32) uint32 | |
| 2747 | - | Xkb_state_key_get_one_sym func(uintptr, uint32) uint32 | |
| 2748 | - | Xkb_state_key_get_utf8 func(uintptr, uint32, uintptr, uintptr) int32 | |
| 2749 | - | Xkb_keysym_get_name func(uint32, uintptr, uintptr) int32 | |
| 2750 | - | Xkb_keysym_from_name func(uintptr, uint32) uint32 | |
| 2751 | - | Xkb_compose_table_new_from_locale func(uintptr, uintptr, uint32) uintptr | |
| 2752 | - | Xkb_compose_table_ref func(uintptr) uintptr | |
| 2753 | - | Xkb_compose_table_unref func(uintptr) | |
| 2754 | - | Xkb_compose_state_new func(uintptr, uint32) uintptr | |
| 2755 | - | Xkb_compose_state_ref func(uintptr) uintptr | |
| 2756 | - | Xkb_compose_state_unref func(uintptr) | |
| 2757 | - | Xkb_compose_state_feed func(uintptr, uint32) uint32 | |
| 2758 | - | Xkb_compose_state_get_status func(uintptr) uint32 | |
| 2759 | - | Xkb_compose_state_reset func(uintptr) | |
| 2760 | - | Xkb_compose_state_get_utf8 func(uintptr, uintptr, uintptr) int32 | |
| 2761 | - | Xkb_compose_state_get_one_sym func(uintptr) uint32 | |
| 2738 | + | xkb_context_new uintptr | |
| 2739 | + | xkb_context_ref uintptr | |
| 2740 | + | xkb_context_unref uintptr | |
| 2741 | + | xkb_keymap_new_from_names uintptr | |
| 2742 | + | xkb_keymap_new_from_string uintptr | |
| 2743 | + | xkb_keymap_new_from_buffer uintptr | |
| 2744 | + | xkb_keymap_ref uintptr | |
| 2745 | + | xkb_keymap_unref uintptr | |
| 2746 | + | xkb_keymap_get_as_string uintptr | |
| 2747 | + | xkb_keymap_mod_get_index uintptr | |
| 2748 | + | xkb_keymap_key_repeats uintptr | |
| 2749 | + | xkb_keymap_num_layouts uintptr | |
| 2750 | + | xkb_state_new uintptr | |
| 2751 | + | xkb_state_ref uintptr | |
| 2752 | + | xkb_state_unref uintptr | |
| 2753 | + | xkb_state_update_mask uintptr | |
| 2754 | + | xkb_state_serialize_layout uintptr | |
| 2755 | + | xkb_state_serialize_mods uintptr | |
| 2756 | + | xkb_state_mod_name_is_active uintptr | |
| 2757 | + | xkb_state_mod_index_is_active uintptr | |
| 2758 | + | xkb_state_key_get_one_sym uintptr | |
| 2759 | + | xkb_state_key_get_utf8 uintptr | |
| 2760 | + | xkb_keysym_get_name uintptr | |
| 2761 | + | xkb_keysym_from_name uintptr | |
| 2762 | + | xkb_compose_table_new_from_locale uintptr | |
| 2763 | + | xkb_compose_table_ref uintptr | |
| 2764 | + | xkb_compose_table_unref uintptr | |
| 2765 | + | xkb_compose_state_new uintptr | |
| 2766 | + | xkb_compose_state_ref uintptr | |
| 2767 | + | xkb_compose_state_unref uintptr | |
| 2768 | + | xkb_compose_state_feed uintptr | |
| 2769 | + | xkb_compose_state_get_status uintptr | |
| 2770 | + | xkb_compose_state_reset uintptr | |
| 2771 | + | xkb_compose_state_get_utf8 uintptr | |
| 2772 | + | xkb_compose_state_get_one_sym uintptr | |
| 2762 | 2773 | } | |
| 2763 | 2774 | ||
| 2764 | 2775 | // Symbols is the complete required symbol set. | |
| @@ -2773,6 +2784,7 @@ var Symbols = []string{ | |||
| 2773 | 2784 | "xkb_keymap_unref", | |
| 2774 | 2785 | "xkb_keymap_get_as_string", | |
| 2775 | 2786 | "xkb_keymap_mod_get_index", | |
| 2787 | + | "xkb_keymap_key_repeats", | |
| 2776 | 2788 | "xkb_keymap_num_layouts", | |
| 2777 | 2789 | "xkb_state_new", | |
| 2778 | 2790 | "xkb_state_ref", | |
| @@ -2780,6 +2792,8 @@ var Symbols = []string{ | |||
| 2780 | 2792 | "xkb_state_update_mask", | |
| 2781 | 2793 | "xkb_state_serialize_layout", | |
| 2782 | 2794 | "xkb_state_serialize_mods", | |
| 2795 | + | "xkb_state_mod_name_is_active", | |
| 2796 | + | "xkb_state_mod_index_is_active", | |
| 2783 | 2797 | "xkb_state_key_get_one_sym", | |
| 2784 | 2798 | "xkb_state_key_get_utf8", | |
| 2785 | 2799 | "xkb_keysym_get_name", | |
| @@ -2797,38 +2811,245 @@ var Symbols = []string{ | |||
| 2797 | 2811 | "xkb_compose_state_get_one_sym", | |
| 2798 | 2812 | } | |
| 2799 | 2813 | ||
| 2800 | - | // Register registers prevalidated addresses in a Funcs value. | |
| 2801 | - | func Register(f *Funcs, addresses map[string]uintptr, register func(any, uintptr)) { | |
| 2802 | - | register(&f.Xkb_context_new, addresses["xkb_context_new"]) | |
| 2803 | - | register(&f.Xkb_context_ref, addresses["xkb_context_ref"]) | |
| 2804 | - | register(&f.Xkb_context_unref, addresses["xkb_context_unref"]) | |
| 2805 | - | register(&f.Xkb_keymap_new_from_names, addresses["xkb_keymap_new_from_names"]) | |
| 2806 | - | register(&f.Xkb_keymap_new_from_string, addresses["xkb_keymap_new_from_string"]) | |
| 2807 | - | register(&f.Xkb_keymap_new_from_buffer, addresses["xkb_keymap_new_from_buffer"]) | |
| 2808 | - | register(&f.Xkb_keymap_ref, addresses["xkb_keymap_ref"]) | |
| 2809 | - | register(&f.Xkb_keymap_unref, addresses["xkb_keymap_unref"]) | |
| 2810 | - | register(&f.Xkb_keymap_get_as_string, addresses["xkb_keymap_get_as_string"]) | |
| 2811 | - | register(&f.Xkb_keymap_mod_get_index, addresses["xkb_keymap_mod_get_index"]) | |
| 2812 | - | register(&f.Xkb_keymap_num_layouts, addresses["xkb_keymap_num_layouts"]) | |
| 2813 | - | register(&f.Xkb_state_new, addresses["xkb_state_new"]) | |
| 2814 | - | register(&f.Xkb_state_ref, addresses["xkb_state_ref"]) | |
| 2815 | - | register(&f.Xkb_state_unref, addresses["xkb_state_unref"]) | |
| 2816 | - | register(&f.Xkb_state_update_mask, addresses["xkb_state_update_mask"]) | |
| 2817 | - | register(&f.Xkb_state_serialize_layout, addresses["xkb_state_serialize_layout"]) | |
| 2818 | - | register(&f.Xkb_state_serialize_mods, addresses["xkb_state_serialize_mods"]) | |
| 2819 | - | register(&f.Xkb_state_key_get_one_sym, addresses["xkb_state_key_get_one_sym"]) | |
| 2820 | - | register(&f.Xkb_state_key_get_utf8, addresses["xkb_state_key_get_utf8"]) | |
| 2821 | - | register(&f.Xkb_keysym_get_name, addresses["xkb_keysym_get_name"]) | |
| 2822 | - | register(&f.Xkb_keysym_from_name, addresses["xkb_keysym_from_name"]) | |
| 2823 | - | register(&f.Xkb_compose_table_new_from_locale, addresses["xkb_compose_table_new_from_locale"]) | |
| 2824 | - | register(&f.Xkb_compose_table_ref, addresses["xkb_compose_table_ref"]) | |
| 2825 | - | register(&f.Xkb_compose_table_unref, addresses["xkb_compose_table_unref"]) | |
| 2826 | - | register(&f.Xkb_compose_state_new, addresses["xkb_compose_state_new"]) | |
| 2827 | - | register(&f.Xkb_compose_state_ref, addresses["xkb_compose_state_ref"]) | |
| 2828 | - | register(&f.Xkb_compose_state_unref, addresses["xkb_compose_state_unref"]) | |
| 2829 | - | register(&f.Xkb_compose_state_feed, addresses["xkb_compose_state_feed"]) | |
| 2830 | - | register(&f.Xkb_compose_state_get_status, addresses["xkb_compose_state_get_status"]) | |
| 2831 | - | register(&f.Xkb_compose_state_reset, addresses["xkb_compose_state_reset"]) | |
| 2832 | - | register(&f.Xkb_compose_state_get_utf8, addresses["xkb_compose_state_get_utf8"]) | |
| 2833 | - | register(&f.Xkb_compose_state_get_one_sym, addresses["xkb_compose_state_get_one_sym"]) | |
| 2814 | + | // Register stores prevalidated symbol addresses in a Funcs value. | |
| 2815 | + | func Register(f *Funcs, addresses map[string]uintptr) { | |
| 2816 | + | f.xkb_context_new = addresses["xkb_context_new"] | |
| 2817 | + | f.xkb_context_ref = addresses["xkb_context_ref"] | |
| 2818 | + | f.xkb_context_unref = addresses["xkb_context_unref"] | |
| 2819 | + | f.xkb_keymap_new_from_names = addresses["xkb_keymap_new_from_names"] | |
| 2820 | + | f.xkb_keymap_new_from_string = addresses["xkb_keymap_new_from_string"] | |
| 2821 | + | f.xkb_keymap_new_from_buffer = addresses["xkb_keymap_new_from_buffer"] | |
| 2822 | + | f.xkb_keymap_ref = addresses["xkb_keymap_ref"] | |
| 2823 | + | f.xkb_keymap_unref = addresses["xkb_keymap_unref"] | |
| 2824 | + | f.xkb_keymap_get_as_string = addresses["xkb_keymap_get_as_string"] | |
| 2825 | + | f.xkb_keymap_mod_get_index = addresses["xkb_keymap_mod_get_index"] | |
| 2826 | + | f.xkb_keymap_key_repeats = addresses["xkb_keymap_key_repeats"] | |
| 2827 | + | f.xkb_keymap_num_layouts = addresses["xkb_keymap_num_layouts"] | |
| 2828 | + | f.xkb_state_new = addresses["xkb_state_new"] | |
| 2829 | + | f.xkb_state_ref = addresses["xkb_state_ref"] | |
| 2830 | + | f.xkb_state_unref = addresses["xkb_state_unref"] | |
| 2831 | + | f.xkb_state_update_mask = addresses["xkb_state_update_mask"] | |
| 2832 | + | f.xkb_state_serialize_layout = addresses["xkb_state_serialize_layout"] | |
| 2833 | + | f.xkb_state_serialize_mods = addresses["xkb_state_serialize_mods"] | |
| 2834 | + | f.xkb_state_mod_name_is_active = addresses["xkb_state_mod_name_is_active"] | |
| 2835 | + | f.xkb_state_mod_index_is_active = addresses["xkb_state_mod_index_is_active"] | |
| 2836 | + | f.xkb_state_key_get_one_sym = addresses["xkb_state_key_get_one_sym"] | |
| 2837 | + | f.xkb_state_key_get_utf8 = addresses["xkb_state_key_get_utf8"] | |
| 2838 | + | f.xkb_keysym_get_name = addresses["xkb_keysym_get_name"] | |
| 2839 | + | f.xkb_keysym_from_name = addresses["xkb_keysym_from_name"] | |
| 2840 | + | f.xkb_compose_table_new_from_locale = addresses["xkb_compose_table_new_from_locale"] | |
| 2841 | + | f.xkb_compose_table_ref = addresses["xkb_compose_table_ref"] | |
| 2842 | + | f.xkb_compose_table_unref = addresses["xkb_compose_table_unref"] | |
| 2843 | + | f.xkb_compose_state_new = addresses["xkb_compose_state_new"] | |
| 2844 | + | f.xkb_compose_state_ref = addresses["xkb_compose_state_ref"] | |
| 2845 | + | f.xkb_compose_state_unref = addresses["xkb_compose_state_unref"] | |
| 2846 | + | f.xkb_compose_state_feed = addresses["xkb_compose_state_feed"] | |
| 2847 | + | f.xkb_compose_state_get_status = addresses["xkb_compose_state_get_status"] | |
| 2848 | + | f.xkb_compose_state_reset = addresses["xkb_compose_state_reset"] | |
| 2849 | + | f.xkb_compose_state_get_utf8 = addresses["xkb_compose_state_get_utf8"] | |
| 2850 | + | f.xkb_compose_state_get_one_sym = addresses["xkb_compose_state_get_one_sym"] | |
| 2851 | + | } | |
| 2852 | + | ||
| 2853 | + | // Xkb_context_new calls xkb_context_new without allocating. | |
| 2854 | + | func (f *Funcs) Xkb_context_new(a1 uint32) uintptr { | |
| 2855 | + | r, _, _ := purego.Syscall6(f.xkb_context_new, uintptr(a1), 0, 0, 0, 0, 0) | |
| 2856 | + | return r | |
| 2857 | + | } | |
| 2858 | + | ||
| 2859 | + | // Xkb_context_ref calls xkb_context_ref without allocating. | |
| 2860 | + | func (f *Funcs) Xkb_context_ref(a1 uintptr) uintptr { | |
| 2861 | + | r, _, _ := purego.Syscall6(f.xkb_context_ref, a1, 0, 0, 0, 0, 0) | |
| 2862 | + | return r | |
| 2863 | + | } | |
| 2864 | + | ||
| 2865 | + | // Xkb_context_unref calls xkb_context_unref without allocating. | |
| 2866 | + | func (f *Funcs) Xkb_context_unref(a1 uintptr) { | |
| 2867 | + | purego.Syscall6(f.xkb_context_unref, a1, 0, 0, 0, 0, 0) | |
| 2868 | + | } | |
| 2869 | + | ||
| 2870 | + | // Xkb_keymap_new_from_names calls xkb_keymap_new_from_names without allocating. | |
| 2871 | + | func (f *Funcs) Xkb_keymap_new_from_names(a1 uintptr, a2 unsafe.Pointer, a3 uint32) uintptr { | |
| 2872 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_new_from_names, a1, uintptr(a2), uintptr(a3), 0, 0, 0) | |
| 2873 | + | return r | |
| 2874 | + | } | |
| 2875 | + | ||
| 2876 | + | // Xkb_keymap_new_from_string calls xkb_keymap_new_from_string without allocating. | |
| 2877 | + | func (f *Funcs) Xkb_keymap_new_from_string(a1 uintptr, a2 unsafe.Pointer, a3 uint32, a4 uint32) uintptr { | |
| 2878 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_new_from_string, a1, uintptr(a2), uintptr(a3), uintptr(a4), 0, 0) | |
| 2879 | + | return r | |
| 2880 | + | } | |
| 2881 | + | ||
| 2882 | + | // Xkb_keymap_new_from_buffer calls xkb_keymap_new_from_buffer without allocating. | |
| 2883 | + | func (f *Funcs) Xkb_keymap_new_from_buffer(a1 uintptr, a2 unsafe.Pointer, a3 uintptr, a4 uint32, a5 uint32) uintptr { | |
| 2884 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_new_from_buffer, a1, uintptr(a2), a3, uintptr(a4), uintptr(a5), 0) | |
| 2885 | + | return r | |
| 2886 | + | } | |
| 2887 | + | ||
| 2888 | + | // Xkb_keymap_ref calls xkb_keymap_ref without allocating. | |
| 2889 | + | func (f *Funcs) Xkb_keymap_ref(a1 uintptr) uintptr { | |
| 2890 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_ref, a1, 0, 0, 0, 0, 0) | |
| 2891 | + | return r | |
| 2892 | + | } | |
| 2893 | + | ||
| 2894 | + | // Xkb_keymap_unref calls xkb_keymap_unref without allocating. | |
| 2895 | + | func (f *Funcs) Xkb_keymap_unref(a1 uintptr) { | |
| 2896 | + | purego.Syscall6(f.xkb_keymap_unref, a1, 0, 0, 0, 0, 0) | |
| 2897 | + | } | |
| 2898 | + | ||
| 2899 | + | // Xkb_keymap_get_as_string calls xkb_keymap_get_as_string without allocating. | |
| 2900 | + | func (f *Funcs) Xkb_keymap_get_as_string(a1 uintptr, a2 uint32) uintptr { | |
| 2901 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_get_as_string, a1, uintptr(a2), 0, 0, 0, 0) | |
| 2902 | + | return r | |
| 2903 | + | } | |
| 2904 | + | ||
| 2905 | + | // Xkb_keymap_mod_get_index calls xkb_keymap_mod_get_index without allocating. | |
| 2906 | + | func (f *Funcs) Xkb_keymap_mod_get_index(a1 uintptr, a2 unsafe.Pointer) uint32 { | |
| 2907 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_mod_get_index, a1, uintptr(a2), 0, 0, 0, 0) | |
| 2908 | + | return uint32(r) | |
| 2909 | + | } | |
| 2910 | + | ||
| 2911 | + | // Xkb_keymap_key_repeats calls xkb_keymap_key_repeats without allocating. | |
| 2912 | + | func (f *Funcs) Xkb_keymap_key_repeats(a1 uintptr, a2 uint32) int32 { | |
| 2913 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_key_repeats, a1, uintptr(a2), 0, 0, 0, 0) | |
| 2914 | + | return int32(r) | |
| 2915 | + | } | |
| 2916 | + | ||
| 2917 | + | // Xkb_keymap_num_layouts calls xkb_keymap_num_layouts without allocating. | |
| 2918 | + | func (f *Funcs) Xkb_keymap_num_layouts(a1 uintptr) uint32 { | |
| 2919 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_num_layouts, a1, 0, 0, 0, 0, 0) | |
| 2920 | + | return uint32(r) | |
| 2921 | + | } | |
| 2922 | + | ||
| 2923 | + | // Xkb_state_new calls xkb_state_new without allocating. | |
| 2924 | + | func (f *Funcs) Xkb_state_new(a1 uintptr) uintptr { | |
| 2925 | + | r, _, _ := purego.Syscall6(f.xkb_state_new, a1, 0, 0, 0, 0, 0) | |
| 2926 | + | return r | |
| 2927 | + | } | |
| 2928 | + | ||
| 2929 | + | // Xkb_state_ref calls xkb_state_ref without allocating. | |
| 2930 | + | func (f *Funcs) Xkb_state_ref(a1 uintptr) uintptr { | |
| 2931 | + | r, _, _ := purego.Syscall6(f.xkb_state_ref, a1, 0, 0, 0, 0, 0) | |
| 2932 | + | return r | |
| 2933 | + | } | |
| 2934 | + | ||
| 2935 | + | // Xkb_state_unref calls xkb_state_unref without allocating. | |
| 2936 | + | func (f *Funcs) Xkb_state_unref(a1 uintptr) { | |
| 2937 | + | purego.Syscall6(f.xkb_state_unref, a1, 0, 0, 0, 0, 0) | |
| 2938 | + | } | |
| 2939 | + | ||
| 2940 | + | // Xkb_state_update_mask calls xkb_state_update_mask without allocating. | |
| 2941 | + | func (f *Funcs) Xkb_state_update_mask(a1 uintptr, a2 uint32, a3 uint32, a4 uint32, a5 uint32, a6 uint32, a7 uint32) uint32 { | |
| 2942 | + | r, _, _ := purego.Syscall15(f.xkb_state_update_mask, a1, uintptr(a2), uintptr(a3), uintptr(a4), uintptr(a5), uintptr(a6), uintptr(a7), 0, 0, 0, 0, 0, 0, 0, 0) | |
| 2943 | + | return uint32(r) | |
| 2944 | + | } | |
| 2945 | + | ||
| 2946 | + | // Xkb_state_serialize_layout calls xkb_state_serialize_layout without allocating. | |
| 2947 | + | func (f *Funcs) Xkb_state_serialize_layout(a1 uintptr, a2 uint32) uint32 { | |
| 2948 | + | r, _, _ := purego.Syscall6(f.xkb_state_serialize_layout, a1, uintptr(a2), 0, 0, 0, 0) | |
| 2949 | + | return uint32(r) | |
| 2950 | + | } | |
| 2951 | + | ||
| 2952 | + | // Xkb_state_serialize_mods calls xkb_state_serialize_mods without allocating. | |
| 2953 | + | func (f *Funcs) Xkb_state_serialize_mods(a1 uintptr, a2 uint32) uint32 { | |
| 2954 | + | r, _, _ := purego.Syscall6(f.xkb_state_serialize_mods, a1, uintptr(a2), 0, 0, 0, 0) | |
| 2955 | + | return uint32(r) | |
| 2956 | + | } | |
| 2957 | + | ||
| 2958 | + | // Xkb_state_mod_name_is_active calls xkb_state_mod_name_is_active without allocating. | |
| 2959 | + | func (f *Funcs) Xkb_state_mod_name_is_active(a1 uintptr, a2 unsafe.Pointer, a3 uint32) int32 { | |
| 2960 | + | r, _, _ := purego.Syscall6(f.xkb_state_mod_name_is_active, a1, uintptr(a2), uintptr(a3), 0, 0, 0) | |
| 2961 | + | return int32(r) | |
| 2962 | + | } | |
| 2963 | + | ||
| 2964 | + | // Xkb_state_mod_index_is_active calls xkb_state_mod_index_is_active without allocating. | |
| 2965 | + | func (f *Funcs) Xkb_state_mod_index_is_active(a1 uintptr, a2 uint32, a3 uint32) int32 { | |
| 2966 | + | r, _, _ := purego.Syscall6(f.xkb_state_mod_index_is_active, a1, uintptr(a2), uintptr(a3), 0, 0, 0) | |
| 2967 | + | return int32(r) | |
| 2968 | + | } | |
| 2969 | + | ||
| 2970 | + | // Xkb_state_key_get_one_sym calls xkb_state_key_get_one_sym without allocating. | |
| 2971 | + | func (f *Funcs) Xkb_state_key_get_one_sym(a1 uintptr, a2 uint32) uint32 { | |
| 2972 | + | r, _, _ := purego.Syscall6(f.xkb_state_key_get_one_sym, a1, uintptr(a2), 0, 0, 0, 0) | |
| 2973 | + | return uint32(r) | |
| 2974 | + | } | |
| 2975 | + | ||
| 2976 | + | // Xkb_state_key_get_utf8 calls xkb_state_key_get_utf8 without allocating. | |
| 2977 | + | func (f *Funcs) Xkb_state_key_get_utf8(a1 uintptr, a2 uint32, a3 unsafe.Pointer, a4 uintptr) int32 { | |
| 2978 | + | r, _, _ := purego.Syscall6(f.xkb_state_key_get_utf8, a1, uintptr(a2), uintptr(a3), a4, 0, 0) | |
| 2979 | + | return int32(r) | |
| 2980 | + | } | |
| 2981 | + | ||
| 2982 | + | // Xkb_keysym_get_name calls xkb_keysym_get_name without allocating. | |
| 2983 | + | func (f *Funcs) Xkb_keysym_get_name(a1 uint32, a2 unsafe.Pointer, a3 uintptr) int32 { | |
| 2984 | + | r, _, _ := purego.Syscall6(f.xkb_keysym_get_name, uintptr(a1), uintptr(a2), a3, 0, 0, 0) | |
| 2985 | + | return int32(r) | |
| 2986 | + | } | |
| 2987 | + | ||
| 2988 | + | // Xkb_keysym_from_name calls xkb_keysym_from_name without allocating. | |
| 2989 | + | func (f *Funcs) Xkb_keysym_from_name(a1 unsafe.Pointer, a2 uint32) uint32 { | |
| 2990 | + | r, _, _ := purego.Syscall6(f.xkb_keysym_from_name, uintptr(a1), uintptr(a2), 0, 0, 0, 0) | |
| 2991 | + | return uint32(r) | |
| 2992 | + | } | |
| 2993 | + | ||
| 2994 | + | // Xkb_compose_table_new_from_locale calls xkb_compose_table_new_from_locale without allocating. | |
| 2995 | + | func (f *Funcs) Xkb_compose_table_new_from_locale(a1 uintptr, a2 unsafe.Pointer, a3 uint32) uintptr { | |
| 2996 | + | r, _, _ := purego.Syscall6(f.xkb_compose_table_new_from_locale, a1, uintptr(a2), uintptr(a3), 0, 0, 0) | |
| 2997 | + | return r | |
| 2998 | + | } | |
| 2999 | + | ||
| 3000 | + | // Xkb_compose_table_ref calls xkb_compose_table_ref without allocating. | |
| 3001 | + | func (f *Funcs) Xkb_compose_table_ref(a1 uintptr) uintptr { | |
| 3002 | + | r, _, _ := purego.Syscall6(f.xkb_compose_table_ref, a1, 0, 0, 0, 0, 0) | |
| 3003 | + | return r | |
| 3004 | + | } | |
| 3005 | + | ||
| 3006 | + | // Xkb_compose_table_unref calls xkb_compose_table_unref without allocating. | |
| 3007 | + | func (f *Funcs) Xkb_compose_table_unref(a1 uintptr) { | |
| 3008 | + | purego.Syscall6(f.xkb_compose_table_unref, a1, 0, 0, 0, 0, 0) | |
| 3009 | + | } | |
| 3010 | + | ||
| 3011 | + | // Xkb_compose_state_new calls xkb_compose_state_new without allocating. | |
| 3012 | + | func (f *Funcs) Xkb_compose_state_new(a1 uintptr, a2 uint32) uintptr { | |
| 3013 | + | r, _, _ := purego.Syscall6(f.xkb_compose_state_new, a1, uintptr(a2), 0, 0, 0, 0) | |
| 3014 | + | return r | |
| 3015 | + | } | |
| 3016 | + | ||
| 3017 | + | // Xkb_compose_state_ref calls xkb_compose_state_ref without allocating. | |
| 3018 | + | func (f *Funcs) Xkb_compose_state_ref(a1 uintptr) uintptr { | |
| 3019 | + | r, _, _ := purego.Syscall6(f.xkb_compose_state_ref, a1, 0, 0, 0, 0, 0) | |
| 3020 | + | return r | |
| 3021 | + | } | |
| 3022 | + | ||
| 3023 | + | // Xkb_compose_state_unref calls xkb_compose_state_unref without allocating. | |
| 3024 | + | func (f *Funcs) Xkb_compose_state_unref(a1 uintptr) { | |
| 3025 | + | purego.Syscall6(f.xkb_compose_state_unref, a1, 0, 0, 0, 0, 0) | |
| 3026 | + | } | |
| 3027 | + | ||
| 3028 | + | // Xkb_compose_state_feed calls xkb_compose_state_feed without allocating. | |
| 3029 | + | func (f *Funcs) Xkb_compose_state_feed(a1 uintptr, a2 uint32) uint32 { | |
| 3030 | + | r, _, _ := purego.Syscall6(f.xkb_compose_state_feed, a1, uintptr(a2), 0, 0, 0, 0) | |
| 3031 | + | return uint32(r) | |
| 3032 | + | } | |
| 3033 | + | ||
| 3034 | + | // Xkb_compose_state_get_status calls xkb_compose_state_get_status without allocating. | |
| 3035 | + | func (f *Funcs) Xkb_compose_state_get_status(a1 uintptr) uint32 { | |
| 3036 | + | r, _, _ := purego.Syscall6(f.xkb_compose_state_get_status, a1, 0, 0, 0, 0, 0) | |
| 3037 | + | return uint32(r) | |
| 3038 | + | } | |
| 3039 | + | ||
| 3040 | + | // Xkb_compose_state_reset calls xkb_compose_state_reset without allocating. | |
| 3041 | + | func (f *Funcs) Xkb_compose_state_reset(a1 uintptr) { | |
| 3042 | + | purego.Syscall6(f.xkb_compose_state_reset, a1, 0, 0, 0, 0, 0) | |
| 3043 | + | } | |
| 3044 | + | ||
| 3045 | + | // Xkb_compose_state_get_utf8 calls xkb_compose_state_get_utf8 without allocating. | |
| 3046 | + | func (f *Funcs) Xkb_compose_state_get_utf8(a1 uintptr, a2 unsafe.Pointer, a3 uintptr) int32 { | |
| 3047 | + | r, _, _ := purego.Syscall6(f.xkb_compose_state_get_utf8, a1, uintptr(a2), a3, 0, 0, 0) | |
| 3048 | + | return int32(r) | |
| 3049 | + | } | |
| 3050 | + | ||
| 3051 | + | // Xkb_compose_state_get_one_sym calls xkb_compose_state_get_one_sym without allocating. | |
| 3052 | + | func (f *Funcs) Xkb_compose_state_get_one_sym(a1 uintptr) uint32 { | |
| 3053 | + | r, _, _ := purego.Syscall6(f.xkb_compose_state_get_one_sym, a1, 0, 0, 0, 0, 0) | |
| 3054 | + | return uint32(r) | |
| 2834 | 3055 | } | |
| @@ -1,6 +1,12 @@ | |||||
| 1 | // Code generated by go generate; DO NOT EDIT. | 1 | // Code generated by go generate; DO NOT EDIT. | ||
| 2 | package raw | 2 | package raw | ||
| 3 | 3 | ||||
| 4 | + | import ( | |||
| 5 | + | "unsafe" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/purego" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 4 | // Opaque C handles and scalar typedefs. | 10 | // Opaque C handles and scalar typedefs. | ||
| 5 | type Context uintptr | 11 | type Context uintptr | ||
| 6 | type Keymap uintptr | 12 | type Keymap uintptr | ||
| @@ -14,8 +20,9 @@ type ModIndex uint32 | |||||
| 14 | type ModMask uint32 | 20 | type ModMask uint32 | ||
| 15 | type LayoutMask uint32 | 21 | type LayoutMask uint32 | ||
| 16 | 22 | ||||
| 17 | - | // RuleNames matches struct xkb_rule_names (five const char pointers). | 23 | + | // RuleNames matches struct xkb_rule_names (five const char pointers). The |
| 18 | - | type RuleNames struct{ Rules, Model, Layout, Variant, Options uintptr } | 24 | + | // fields are unsafe.Pointer so the GC sees the NUL-terminated strings they reference. |
| 25 | + | type RuleNames struct{ Rules, Model, Layout, Variant, Options unsafe.Pointer } | |||
| 19 | 26 | ||||
| 20 | const ( | 27 | const ( | ||
| 21 | XKB_RMLVO_BUILDER_NO_FLAGS = 0 | 28 | XKB_RMLVO_BUILDER_NO_FLAGS = 0 | ||
| @@ -2725,40 +2732,44 @@ const ( | |||||
| 2725 | XKB_LED_NAME_KANA = "Kana" | 2732 | XKB_LED_NAME_KANA = "Kana" | ||
| 2726 | ) | 2733 | ) | ||
| 2727 | 2734 | ||||
| 2728 | - | // Funcs holds resolved library functions. | 2735 | + | // Funcs holds resolved library function addresses. Each function is invoked |
| 2736 | + | // through purego.Syscall6/Syscall15 (fixed arity, no reflection, no allocation). | |||
| 2729 | type Funcs struct { | 2737 | type Funcs struct { | ||
| 2730 | - | Xkb_context_new func(uint32) uintptr | 2738 | + | xkb_context_new uintptr |
| 2731 | - | Xkb_context_ref func(uintptr) uintptr | 2739 | + | xkb_context_ref uintptr |
| 2732 | - | Xkb_context_unref func(uintptr) | 2740 | + | xkb_context_unref uintptr |
| 2733 | - | Xkb_keymap_new_from_names func(uintptr, uintptr, uint32) uintptr | 2741 | + | xkb_keymap_new_from_names uintptr |
| 2734 | - | Xkb_keymap_new_from_string func(uintptr, uintptr, uint32, uint32) uintptr | 2742 | + | xkb_keymap_new_from_string uintptr |
| 2735 | - | Xkb_keymap_new_from_buffer func(uintptr, uintptr, uintptr, uint32, uint32) uintptr | 2743 | + | xkb_keymap_new_from_buffer uintptr |
| 2736 | - | Xkb_keymap_ref func(uintptr) uintptr | 2744 | + | xkb_keymap_ref uintptr |
| 2737 | - | Xkb_keymap_unref func(uintptr) | 2745 | + | xkb_keymap_unref uintptr |
| 2738 | - | Xkb_keymap_get_as_string func(uintptr, uint32) uintptr | 2746 | + | xkb_keymap_get_as_string uintptr |
| 2739 | - | Xkb_keymap_mod_get_index func(uintptr, uintptr) uint32 | 2747 | + | xkb_keymap_mod_get_index uintptr |
| 2740 | - | Xkb_keymap_num_layouts func(uintptr) uint32 | 2748 | + | xkb_keymap_key_repeats uintptr |
| 2741 | - | Xkb_state_new func(uintptr) uintptr | 2749 | + | xkb_keymap_num_layouts uintptr |
| 2742 | - | Xkb_state_ref func(uintptr) uintptr | 2750 | + | xkb_state_new uintptr |
| 2743 | - | Xkb_state_unref func(uintptr) | 2751 | + | xkb_state_ref uintptr |
| 2744 | - | Xkb_state_update_mask func(uintptr, uint32, uint32, uint32, uint32, uint32, uint32) uint32 | 2752 | + | xkb_state_unref uintptr |
| 2745 | - | Xkb_state_serialize_layout func(uintptr, uint32) uint32 | 2753 | + | xkb_state_update_mask uintptr |
| 2746 | - | Xkb_state_serialize_mods func(uintptr, uint32) uint32 | 2754 | + | xkb_state_serialize_layout uintptr |
| 2747 | - | Xkb_state_key_get_one_sym func(uintptr, uint32) uint32 | 2755 | + | xkb_state_serialize_mods uintptr |
| 2748 | - | Xkb_state_key_get_utf8 func(uintptr, uint32, uintptr, uintptr) int32 | 2756 | + | xkb_state_mod_name_is_active uintptr |
| 2749 | - | Xkb_keysym_get_name func(uint32, uintptr, uintptr) int32 | 2757 | + | xkb_state_mod_index_is_active uintptr |
| 2750 | - | Xkb_keysym_from_name func(uintptr, uint32) uint32 | 2758 | + | xkb_state_key_get_one_sym uintptr |
| 2751 | - | Xkb_compose_table_new_from_locale func(uintptr, uintptr, uint32) uintptr | 2759 | + | xkb_state_key_get_utf8 uintptr |
| 2752 | - | Xkb_compose_table_ref func(uintptr) uintptr | 2760 | + | xkb_keysym_get_name uintptr |
| 2753 | - | Xkb_compose_table_unref func(uintptr) | 2761 | + | xkb_keysym_from_name uintptr |
| 2754 | - | Xkb_compose_state_new func(uintptr, uint32) uintptr | 2762 | + | xkb_compose_table_new_from_locale uintptr |
| 2755 | - | Xkb_compose_state_ref func(uintptr) uintptr | 2763 | + | xkb_compose_table_ref uintptr |
| 2756 | - | Xkb_compose_state_unref func(uintptr) | 2764 | + | xkb_compose_table_unref uintptr |
| 2757 | - | Xkb_compose_state_feed func(uintptr, uint32) uint32 | 2765 | + | xkb_compose_state_new uintptr |
| 2758 | - | Xkb_compose_state_get_status func(uintptr) uint32 | 2766 | + | xkb_compose_state_ref uintptr |
| 2759 | - | Xkb_compose_state_reset func(uintptr) | 2767 | + | xkb_compose_state_unref uintptr |
| 2760 | - | Xkb_compose_state_get_utf8 func(uintptr, uintptr, uintptr) int32 | 2768 | + | xkb_compose_state_feed uintptr |
| 2761 | - | Xkb_compose_state_get_one_sym func(uintptr) uint32 | 2769 | + | xkb_compose_state_get_status uintptr |
| 2770 | + | xkb_compose_state_reset uintptr | |||
| 2771 | + | xkb_compose_state_get_utf8 uintptr | |||
| 2772 | + | xkb_compose_state_get_one_sym uintptr | |||
| 2762 | } | 2773 | } | ||
| 2763 | 2774 | ||||
| 2764 | // Symbols is the complete required symbol set. | 2775 | // Symbols is the complete required symbol set. | ||
| @@ -2773,6 +2784,7 @@ var Symbols = []string{ | |||||
| 2773 | "xkb_keymap_unref", | 2784 | "xkb_keymap_unref", | ||
| 2774 | "xkb_keymap_get_as_string", | 2785 | "xkb_keymap_get_as_string", | ||
| 2775 | "xkb_keymap_mod_get_index", | 2786 | "xkb_keymap_mod_get_index", | ||
| 2787 | + | "xkb_keymap_key_repeats", | |||
| 2776 | "xkb_keymap_num_layouts", | 2788 | "xkb_keymap_num_layouts", | ||
| 2777 | "xkb_state_new", | 2789 | "xkb_state_new", | ||
| 2778 | "xkb_state_ref", | 2790 | "xkb_state_ref", | ||
| @@ -2780,6 +2792,8 @@ var Symbols = []string{ | |||||
| 2780 | "xkb_state_update_mask", | 2792 | "xkb_state_update_mask", | ||
| 2781 | "xkb_state_serialize_layout", | 2793 | "xkb_state_serialize_layout", | ||
| 2782 | "xkb_state_serialize_mods", | 2794 | "xkb_state_serialize_mods", | ||
| 2795 | + | "xkb_state_mod_name_is_active", | |||
| 2796 | + | "xkb_state_mod_index_is_active", | |||
| 2783 | "xkb_state_key_get_one_sym", | 2797 | "xkb_state_key_get_one_sym", | ||
| 2784 | "xkb_state_key_get_utf8", | 2798 | "xkb_state_key_get_utf8", | ||
| 2785 | "xkb_keysym_get_name", | 2799 | "xkb_keysym_get_name", | ||
| @@ -2797,38 +2811,245 @@ var Symbols = []string{ | |||||
| 2797 | "xkb_compose_state_get_one_sym", | 2811 | "xkb_compose_state_get_one_sym", | ||
| 2798 | } | 2812 | } | ||
| 2799 | 2813 | ||||
| 2800 | - | // Register registers prevalidated addresses in a Funcs value. | 2814 | + | // Register stores prevalidated symbol addresses in a Funcs value. |
| 2801 | - | func Register(f *Funcs, addresses map[string]uintptr, register func(any, uintptr)) { | 2815 | + | func Register(f *Funcs, addresses map[string]uintptr) { |
| 2802 | - | register(&f.Xkb_context_new, addresses["xkb_context_new"]) | 2816 | + | f.xkb_context_new = addresses["xkb_context_new"] |
| 2803 | - | register(&f.Xkb_context_ref, addresses["xkb_context_ref"]) | 2817 | + | f.xkb_context_ref = addresses["xkb_context_ref"] |
| 2804 | - | register(&f.Xkb_context_unref, addresses["xkb_context_unref"]) | 2818 | + | f.xkb_context_unref = addresses["xkb_context_unref"] |
| 2805 | - | register(&f.Xkb_keymap_new_from_names, addresses["xkb_keymap_new_from_names"]) | 2819 | + | f.xkb_keymap_new_from_names = addresses["xkb_keymap_new_from_names"] |
| 2806 | - | register(&f.Xkb_keymap_new_from_string, addresses["xkb_keymap_new_from_string"]) | 2820 | + | f.xkb_keymap_new_from_string = addresses["xkb_keymap_new_from_string"] |
| 2807 | - | register(&f.Xkb_keymap_new_from_buffer, addresses["xkb_keymap_new_from_buffer"]) | 2821 | + | f.xkb_keymap_new_from_buffer = addresses["xkb_keymap_new_from_buffer"] |
| 2808 | - | register(&f.Xkb_keymap_ref, addresses["xkb_keymap_ref"]) | 2822 | + | f.xkb_keymap_ref = addresses["xkb_keymap_ref"] |
| 2809 | - | register(&f.Xkb_keymap_unref, addresses["xkb_keymap_unref"]) | 2823 | + | f.xkb_keymap_unref = addresses["xkb_keymap_unref"] |
| 2810 | - | register(&f.Xkb_keymap_get_as_string, addresses["xkb_keymap_get_as_string"]) | 2824 | + | f.xkb_keymap_get_as_string = addresses["xkb_keymap_get_as_string"] |
| 2811 | - | register(&f.Xkb_keymap_mod_get_index, addresses["xkb_keymap_mod_get_index"]) | 2825 | + | f.xkb_keymap_mod_get_index = addresses["xkb_keymap_mod_get_index"] |
| 2812 | - | register(&f.Xkb_keymap_num_layouts, addresses["xkb_keymap_num_layouts"]) | 2826 | + | f.xkb_keymap_key_repeats = addresses["xkb_keymap_key_repeats"] |
| 2813 | - | register(&f.Xkb_state_new, addresses["xkb_state_new"]) | 2827 | + | f.xkb_keymap_num_layouts = addresses["xkb_keymap_num_layouts"] |
| 2814 | - | register(&f.Xkb_state_ref, addresses["xkb_state_ref"]) | 2828 | + | f.xkb_state_new = addresses["xkb_state_new"] |
| 2815 | - | register(&f.Xkb_state_unref, addresses["xkb_state_unref"]) | 2829 | + | f.xkb_state_ref = addresses["xkb_state_ref"] |
| 2816 | - | register(&f.Xkb_state_update_mask, addresses["xkb_state_update_mask"]) | 2830 | + | f.xkb_state_unref = addresses["xkb_state_unref"] |
| 2817 | - | register(&f.Xkb_state_serialize_layout, addresses["xkb_state_serialize_layout"]) | 2831 | + | f.xkb_state_update_mask = addresses["xkb_state_update_mask"] |
| 2818 | - | register(&f.Xkb_state_serialize_mods, addresses["xkb_state_serialize_mods"]) | 2832 | + | f.xkb_state_serialize_layout = addresses["xkb_state_serialize_layout"] |
| 2819 | - | register(&f.Xkb_state_key_get_one_sym, addresses["xkb_state_key_get_one_sym"]) | 2833 | + | f.xkb_state_serialize_mods = addresses["xkb_state_serialize_mods"] |
| 2820 | - | register(&f.Xkb_state_key_get_utf8, addresses["xkb_state_key_get_utf8"]) | 2834 | + | f.xkb_state_mod_name_is_active = addresses["xkb_state_mod_name_is_active"] |
| 2821 | - | register(&f.Xkb_keysym_get_name, addresses["xkb_keysym_get_name"]) | 2835 | + | f.xkb_state_mod_index_is_active = addresses["xkb_state_mod_index_is_active"] |
| 2822 | - | register(&f.Xkb_keysym_from_name, addresses["xkb_keysym_from_name"]) | 2836 | + | f.xkb_state_key_get_one_sym = addresses["xkb_state_key_get_one_sym"] |
| 2823 | - | register(&f.Xkb_compose_table_new_from_locale, addresses["xkb_compose_table_new_from_locale"]) | 2837 | + | f.xkb_state_key_get_utf8 = addresses["xkb_state_key_get_utf8"] |
| 2824 | - | register(&f.Xkb_compose_table_ref, addresses["xkb_compose_table_ref"]) | 2838 | + | f.xkb_keysym_get_name = addresses["xkb_keysym_get_name"] |
| 2825 | - | register(&f.Xkb_compose_table_unref, addresses["xkb_compose_table_unref"]) | 2839 | + | f.xkb_keysym_from_name = addresses["xkb_keysym_from_name"] |
| 2826 | - | register(&f.Xkb_compose_state_new, addresses["xkb_compose_state_new"]) | 2840 | + | f.xkb_compose_table_new_from_locale = addresses["xkb_compose_table_new_from_locale"] |
| 2827 | - | register(&f.Xkb_compose_state_ref, addresses["xkb_compose_state_ref"]) | 2841 | + | f.xkb_compose_table_ref = addresses["xkb_compose_table_ref"] |
| 2828 | - | register(&f.Xkb_compose_state_unref, addresses["xkb_compose_state_unref"]) | 2842 | + | f.xkb_compose_table_unref = addresses["xkb_compose_table_unref"] |
| 2829 | - | register(&f.Xkb_compose_state_feed, addresses["xkb_compose_state_feed"]) | 2843 | + | f.xkb_compose_state_new = addresses["xkb_compose_state_new"] |
| 2830 | - | register(&f.Xkb_compose_state_get_status, addresses["xkb_compose_state_get_status"]) | 2844 | + | f.xkb_compose_state_ref = addresses["xkb_compose_state_ref"] |
| 2831 | - | register(&f.Xkb_compose_state_reset, addresses["xkb_compose_state_reset"]) | 2845 | + | f.xkb_compose_state_unref = addresses["xkb_compose_state_unref"] |
| 2832 | - | register(&f.Xkb_compose_state_get_utf8, addresses["xkb_compose_state_get_utf8"]) | 2846 | + | f.xkb_compose_state_feed = addresses["xkb_compose_state_feed"] |
| 2833 | - | register(&f.Xkb_compose_state_get_one_sym, addresses["xkb_compose_state_get_one_sym"]) | 2847 | + | f.xkb_compose_state_get_status = addresses["xkb_compose_state_get_status"] |
| 2848 | + | f.xkb_compose_state_reset = addresses["xkb_compose_state_reset"] | |||
| 2849 | + | f.xkb_compose_state_get_utf8 = addresses["xkb_compose_state_get_utf8"] | |||
| 2850 | + | f.xkb_compose_state_get_one_sym = addresses["xkb_compose_state_get_one_sym"] | |||
| 2851 | + | } | |||
| 2852 | + | ||||
| 2853 | + | // Xkb_context_new calls xkb_context_new without allocating. | |||
| 2854 | + | func (f *Funcs) Xkb_context_new(a1 uint32) uintptr { | |||
| 2855 | + | r, _, _ := purego.Syscall6(f.xkb_context_new, uintptr(a1), 0, 0, 0, 0, 0) | |||
| 2856 | + | return r | |||
| 2857 | + | } | |||
| 2858 | + | ||||
| 2859 | + | // Xkb_context_ref calls xkb_context_ref without allocating. | |||
| 2860 | + | func (f *Funcs) Xkb_context_ref(a1 uintptr) uintptr { | |||
| 2861 | + | r, _, _ := purego.Syscall6(f.xkb_context_ref, a1, 0, 0, 0, 0, 0) | |||
| 2862 | + | return r | |||
| 2863 | + | } | |||
| 2864 | + | ||||
| 2865 | + | // Xkb_context_unref calls xkb_context_unref without allocating. | |||
| 2866 | + | func (f *Funcs) Xkb_context_unref(a1 uintptr) { | |||
| 2867 | + | purego.Syscall6(f.xkb_context_unref, a1, 0, 0, 0, 0, 0) | |||
| 2868 | + | } | |||
| 2869 | + | ||||
| 2870 | + | // Xkb_keymap_new_from_names calls xkb_keymap_new_from_names without allocating. | |||
| 2871 | + | func (f *Funcs) Xkb_keymap_new_from_names(a1 uintptr, a2 unsafe.Pointer, a3 uint32) uintptr { | |||
| 2872 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_new_from_names, a1, uintptr(a2), uintptr(a3), 0, 0, 0) | |||
| 2873 | + | return r | |||
| 2874 | + | } | |||
| 2875 | + | ||||
| 2876 | + | // Xkb_keymap_new_from_string calls xkb_keymap_new_from_string without allocating. | |||
| 2877 | + | func (f *Funcs) Xkb_keymap_new_from_string(a1 uintptr, a2 unsafe.Pointer, a3 uint32, a4 uint32) uintptr { | |||
| 2878 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_new_from_string, a1, uintptr(a2), uintptr(a3), uintptr(a4), 0, 0) | |||
| 2879 | + | return r | |||
| 2880 | + | } | |||
| 2881 | + | ||||
| 2882 | + | // Xkb_keymap_new_from_buffer calls xkb_keymap_new_from_buffer without allocating. | |||
| 2883 | + | func (f *Funcs) Xkb_keymap_new_from_buffer(a1 uintptr, a2 unsafe.Pointer, a3 uintptr, a4 uint32, a5 uint32) uintptr { | |||
| 2884 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_new_from_buffer, a1, uintptr(a2), a3, uintptr(a4), uintptr(a5), 0) | |||
| 2885 | + | return r | |||
| 2886 | + | } | |||
| 2887 | + | ||||
| 2888 | + | // Xkb_keymap_ref calls xkb_keymap_ref without allocating. | |||
| 2889 | + | func (f *Funcs) Xkb_keymap_ref(a1 uintptr) uintptr { | |||
| 2890 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_ref, a1, 0, 0, 0, 0, 0) | |||
| 2891 | + | return r | |||
| 2892 | + | } | |||
| 2893 | + | ||||
| 2894 | + | // Xkb_keymap_unref calls xkb_keymap_unref without allocating. | |||
| 2895 | + | func (f *Funcs) Xkb_keymap_unref(a1 uintptr) { | |||
| 2896 | + | purego.Syscall6(f.xkb_keymap_unref, a1, 0, 0, 0, 0, 0) | |||
| 2897 | + | } | |||
| 2898 | + | ||||
| 2899 | + | // Xkb_keymap_get_as_string calls xkb_keymap_get_as_string without allocating. | |||
| 2900 | + | func (f *Funcs) Xkb_keymap_get_as_string(a1 uintptr, a2 uint32) uintptr { | |||
| 2901 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_get_as_string, a1, uintptr(a2), 0, 0, 0, 0) | |||
| 2902 | + | return r | |||
| 2903 | + | } | |||
| 2904 | + | ||||
| 2905 | + | // Xkb_keymap_mod_get_index calls xkb_keymap_mod_get_index without allocating. | |||
| 2906 | + | func (f *Funcs) Xkb_keymap_mod_get_index(a1 uintptr, a2 unsafe.Pointer) uint32 { | |||
| 2907 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_mod_get_index, a1, uintptr(a2), 0, 0, 0, 0) | |||
| 2908 | + | return uint32(r) | |||
| 2909 | + | } | |||
| 2910 | + | ||||
| 2911 | + | // Xkb_keymap_key_repeats calls xkb_keymap_key_repeats without allocating. | |||
| 2912 | + | func (f *Funcs) Xkb_keymap_key_repeats(a1 uintptr, a2 uint32) int32 { | |||
| 2913 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_key_repeats, a1, uintptr(a2), 0, 0, 0, 0) | |||
| 2914 | + | return int32(r) | |||
| 2915 | + | } | |||
| 2916 | + | ||||
| 2917 | + | // Xkb_keymap_num_layouts calls xkb_keymap_num_layouts without allocating. | |||
| 2918 | + | func (f *Funcs) Xkb_keymap_num_layouts(a1 uintptr) uint32 { | |||
| 2919 | + | r, _, _ := purego.Syscall6(f.xkb_keymap_num_layouts, a1, 0, 0, 0, 0, 0) | |||
| 2920 | + | return uint32(r) | |||
| 2921 | + | } | |||
| 2922 | + | ||||
| 2923 | + | // Xkb_state_new calls xkb_state_new without allocating. | |||
| 2924 | + | func (f *Funcs) Xkb_state_new(a1 uintptr) uintptr { | |||
| 2925 | + | r, _, _ := purego.Syscall6(f.xkb_state_new, a1, 0, 0, 0, 0, 0) | |||
| 2926 | + | return r | |||
| 2927 | + | } | |||
| 2928 | + | ||||
| 2929 | + | // Xkb_state_ref calls xkb_state_ref without allocating. | |||
| 2930 | + | func (f *Funcs) Xkb_state_ref(a1 uintptr) uintptr { | |||
| 2931 | + | r, _, _ := purego.Syscall6(f.xkb_state_ref, a1, 0, 0, 0, 0, 0) | |||
| 2932 | + | return r | |||
| 2933 | + | } | |||
| 2934 | + | ||||
| 2935 | + | // Xkb_state_unref calls xkb_state_unref without allocating. | |||
| 2936 | + | func (f *Funcs) Xkb_state_unref(a1 uintptr) { | |||
| 2937 | + | purego.Syscall6(f.xkb_state_unref, a1, 0, 0, 0, 0, 0) | |||
| 2938 | + | } | |||
| 2939 | + | ||||
| 2940 | + | // Xkb_state_update_mask calls xkb_state_update_mask without allocating. | |||
| 2941 | + | func (f *Funcs) Xkb_state_update_mask(a1 uintptr, a2 uint32, a3 uint32, a4 uint32, a5 uint32, a6 uint32, a7 uint32) uint32 { | |||
| 2942 | + | r, _, _ := purego.Syscall15(f.xkb_state_update_mask, a1, uintptr(a2), uintptr(a3), uintptr(a4), uintptr(a5), uintptr(a6), uintptr(a7), 0, 0, 0, 0, 0, 0, 0, 0) | |||
| 2943 | + | return uint32(r) | |||
| 2944 | + | } | |||
| 2945 | + | ||||
| 2946 | + | // Xkb_state_serialize_layout calls xkb_state_serialize_layout without allocating. | |||
| 2947 | + | func (f *Funcs) Xkb_state_serialize_layout(a1 uintptr, a2 uint32) uint32 { | |||
| 2948 | + | r, _, _ := purego.Syscall6(f.xkb_state_serialize_layout, a1, uintptr(a2), 0, 0, 0, 0) | |||
| 2949 | + | return uint32(r) | |||
| 2950 | + | } | |||
| 2951 | + | ||||
| 2952 | + | // Xkb_state_serialize_mods calls xkb_state_serialize_mods without allocating. | |||
| 2953 | + | func (f *Funcs) Xkb_state_serialize_mods(a1 uintptr, a2 uint32) uint32 { | |||
| 2954 | + | r, _, _ := purego.Syscall6(f.xkb_state_serialize_mods, a1, uintptr(a2), 0, 0, 0, 0) | |||
| 2955 | + | return uint32(r) | |||
| 2956 | + | } | |||
| 2957 | + | ||||
| 2958 | + | // Xkb_state_mod_name_is_active calls xkb_state_mod_name_is_active without allocating. | |||
| 2959 | + | func (f *Funcs) Xkb_state_mod_name_is_active(a1 uintptr, a2 unsafe.Pointer, a3 uint32) int32 { | |||
| 2960 | + | r, _, _ := purego.Syscall6(f.xkb_state_mod_name_is_active, a1, uintptr(a2), uintptr(a3), 0, 0, 0) | |||
| 2961 | + | return int32(r) | |||
| 2962 | + | } | |||
| 2963 | + | ||||
| 2964 | + | // Xkb_state_mod_index_is_active calls xkb_state_mod_index_is_active without allocating. | |||
| 2965 | + | func (f *Funcs) Xkb_state_mod_index_is_active(a1 uintptr, a2 uint32, a3 uint32) int32 { | |||
| 2966 | + | r, _, _ := purego.Syscall6(f.xkb_state_mod_index_is_active, a1, uintptr(a2), uintptr(a3), 0, 0, 0) | |||
| 2967 | + | return int32(r) | |||
| 2968 | + | } | |||
| 2969 | + | ||||
| 2970 | + | // Xkb_state_key_get_one_sym calls xkb_state_key_get_one_sym without allocating. | |||
| 2971 | + | func (f *Funcs) Xkb_state_key_get_one_sym(a1 uintptr, a2 uint32) uint32 { | |||
| 2972 | + | r, _, _ := purego.Syscall6(f.xkb_state_key_get_one_sym, a1, uintptr(a2), 0, 0, 0, 0) | |||
| 2973 | + | return uint32(r) | |||
| 2974 | + | } | |||
| 2975 | + | ||||
| 2976 | + | // Xkb_state_key_get_utf8 calls xkb_state_key_get_utf8 without allocating. | |||
| 2977 | + | func (f *Funcs) Xkb_state_key_get_utf8(a1 uintptr, a2 uint32, a3 unsafe.Pointer, a4 uintptr) int32 { | |||
| 2978 | + | r, _, _ := purego.Syscall6(f.xkb_state_key_get_utf8, a1, uintptr(a2), uintptr(a3), a4, 0, 0) | |||
| 2979 | + | return int32(r) | |||
| 2980 | + | } | |||
| 2981 | + | ||||
| 2982 | + | // Xkb_keysym_get_name calls xkb_keysym_get_name without allocating. | |||
| 2983 | + | func (f *Funcs) Xkb_keysym_get_name(a1 uint32, a2 unsafe.Pointer, a3 uintptr) int32 { | |||
| 2984 | + | r, _, _ := purego.Syscall6(f.xkb_keysym_get_name, uintptr(a1), uintptr(a2), a3, 0, 0, 0) | |||
| 2985 | + | return int32(r) | |||
| 2986 | + | } | |||
| 2987 | + | ||||
| 2988 | + | // Xkb_keysym_from_name calls xkb_keysym_from_name without allocating. | |||
| 2989 | + | func (f *Funcs) Xkb_keysym_from_name(a1 unsafe.Pointer, a2 uint32) uint32 { | |||
| 2990 | + | r, _, _ := purego.Syscall6(f.xkb_keysym_from_name, uintptr(a1), uintptr(a2), 0, 0, 0, 0) | |||
| 2991 | + | return uint32(r) | |||
| 2992 | + | } | |||
| 2993 | + | ||||
| 2994 | + | // Xkb_compose_table_new_from_locale calls xkb_compose_table_new_from_locale without allocating. | |||
| 2995 | + | func (f *Funcs) Xkb_compose_table_new_from_locale(a1 uintptr, a2 unsafe.Pointer, a3 uint32) uintptr { | |||
| 2996 | + | r, _, _ := purego.Syscall6(f.xkb_compose_table_new_from_locale, a1, uintptr(a2), uintptr(a3), 0, 0, 0) | |||
| 2997 | + | return r | |||
| 2998 | + | } | |||
| 2999 | + | ||||
| 3000 | + | // Xkb_compose_table_ref calls xkb_compose_table_ref without allocating. | |||
| 3001 | + | func (f *Funcs) Xkb_compose_table_ref(a1 uintptr) uintptr { | |||
| 3002 | + | r, _, _ := purego.Syscall6(f.xkb_compose_table_ref, a1, 0, 0, 0, 0, 0) | |||
| 3003 | + | return r | |||
| 3004 | + | } | |||
| 3005 | + | ||||
| 3006 | + | // Xkb_compose_table_unref calls xkb_compose_table_unref without allocating. | |||
| 3007 | + | func (f *Funcs) Xkb_compose_table_unref(a1 uintptr) { | |||
| 3008 | + | purego.Syscall6(f.xkb_compose_table_unref, a1, 0, 0, 0, 0, 0) | |||
| 3009 | + | } | |||
| 3010 | + | ||||
| 3011 | + | // Xkb_compose_state_new calls xkb_compose_state_new without allocating. | |||
| 3012 | + | func (f *Funcs) Xkb_compose_state_new(a1 uintptr, a2 uint32) uintptr { | |||
| 3013 | + | r, _, _ := purego.Syscall6(f.xkb_compose_state_new, a1, uintptr(a2), 0, 0, 0, 0) | |||
| 3014 | + | return r | |||
| 3015 | + | } | |||
| 3016 | + | ||||
| 3017 | + | // Xkb_compose_state_ref calls xkb_compose_state_ref without allocating. | |||
| 3018 | + | func (f *Funcs) Xkb_compose_state_ref(a1 uintptr) uintptr { | |||
| 3019 | + | r, _, _ := purego.Syscall6(f.xkb_compose_state_ref, a1, 0, 0, 0, 0, 0) | |||
| 3020 | + | return r | |||
| 3021 | + | } | |||
| 3022 | + | ||||
| 3023 | + | // Xkb_compose_state_unref calls xkb_compose_state_unref without allocating. | |||
| 3024 | + | func (f *Funcs) Xkb_compose_state_unref(a1 uintptr) { | |||
| 3025 | + | purego.Syscall6(f.xkb_compose_state_unref, a1, 0, 0, 0, 0, 0) | |||
| 3026 | + | } | |||
| 3027 | + | ||||
| 3028 | + | // Xkb_compose_state_feed calls xkb_compose_state_feed without allocating. | |||
| 3029 | + | func (f *Funcs) Xkb_compose_state_feed(a1 uintptr, a2 uint32) uint32 { | |||
| 3030 | + | r, _, _ := purego.Syscall6(f.xkb_compose_state_feed, a1, uintptr(a2), 0, 0, 0, 0) | |||
| 3031 | + | return uint32(r) | |||
| 3032 | + | } | |||
| 3033 | + | ||||
| 3034 | + | // Xkb_compose_state_get_status calls xkb_compose_state_get_status without allocating. | |||
| 3035 | + | func (f *Funcs) Xkb_compose_state_get_status(a1 uintptr) uint32 { | |||
| 3036 | + | r, _, _ := purego.Syscall6(f.xkb_compose_state_get_status, a1, 0, 0, 0, 0, 0) | |||
| 3037 | + | return uint32(r) | |||
| 3038 | + | } | |||
| 3039 | + | ||||
| 3040 | + | // Xkb_compose_state_reset calls xkb_compose_state_reset without allocating. | |||
| 3041 | + | func (f *Funcs) Xkb_compose_state_reset(a1 uintptr) { | |||
| 3042 | + | purego.Syscall6(f.xkb_compose_state_reset, a1, 0, 0, 0, 0, 0) | |||
| 3043 | + | } | |||
| 3044 | + | ||||
| 3045 | + | // Xkb_compose_state_get_utf8 calls xkb_compose_state_get_utf8 without allocating. | |||
| 3046 | + | func (f *Funcs) Xkb_compose_state_get_utf8(a1 uintptr, a2 unsafe.Pointer, a3 uintptr) int32 { | |||
| 3047 | + | r, _, _ := purego.Syscall6(f.xkb_compose_state_get_utf8, a1, uintptr(a2), a3, 0, 0, 0) | |||
| 3048 | + | return int32(r) | |||
| 3049 | + | } | |||
| 3050 | + | ||||
| 3051 | + | // Xkb_compose_state_get_one_sym calls xkb_compose_state_get_one_sym without allocating. | |||
| 3052 | + | func (f *Funcs) Xkb_compose_state_get_one_sym(a1 uintptr) uint32 { | |||
| 3053 | + | r, _, _ := purego.Syscall6(f.xkb_compose_state_get_one_sym, a1, 0, 0, 0, 0, 0) | |||
| 3054 | + | return uint32(r) | |||
| 2834 | } | 3055 | } | ||
M xkbcommon.go +89 -18
| @@ -18,11 +18,15 @@ import ( | |||
| 18 | 18 | var ErrClosed = errors.New("xkbcommon: closed handle") | |
| 19 | 19 | ||
| 20 | 20 | func cstr(s string) []byte { return append([]byte(s), 0) } | |
| 21 | - | func ptr(b []byte) uintptr { | |
| 21 | + | ||
| 22 | + | // ptr returns the address of b's first byte, or nil. The result is only valid | |
| 23 | + | // as an argument to a raw.Funcs method, whose uintptr conversion keeps the | |
| 24 | + | // backing array alive and (via //go:uintptrescapes) on the heap for the call. | |
| 25 | + | func ptr(b []byte) unsafe.Pointer { | |
| 22 | 26 | if len(b) == 0 { | |
| 23 | - | return 0 | |
| 27 | + | return nil | |
| 24 | 28 | } | |
| 25 | - | return uintptr(unsafe.Pointer(&b[0])) | |
| 29 | + | return unsafe.Pointer(&b[0]) | |
| 26 | 30 | } | |
| 27 | 31 | func validString(s string) error { | |
| 28 | 32 | if strings.IndexByte(s, 0) >= 0 { | |
| @@ -112,7 +116,7 @@ func (c *Context) NewKeymapRules(rules, model, layout, variant, options string) | |||
| 112 | 116 | args := []string{rules, model, layout, variant, options} | |
| 113 | 117 | var b [5][]byte | |
| 114 | 118 | var n raw.RuleNames | |
| 115 | - | p := []*uintptr{&n.Rules, &n.Model, &n.Layout, &n.Variant, &n.Options} | |
| 119 | + | p := []*unsafe.Pointer{&n.Rules, &n.Model, &n.Layout, &n.Variant, &n.Options} | |
| 116 | 120 | for i, s := range args { | |
| 117 | 121 | if e := validString(s); e != nil { | |
| 118 | 122 | return nil, e | |
| @@ -123,7 +127,7 @@ func (c *Context) NewKeymapRules(rules, model, layout, variant, options string) | |||
| 123 | 127 | } | |
| 124 | 128 | } | |
| 125 | 129 | return newKeymap(c, func(h uintptr) uintptr { | |
| 126 | - | v := loader.f.Xkb_keymap_new_from_names(h, uintptr(unsafe.Pointer(&n)), 0) | |
| 130 | + | v := loader.f.Xkb_keymap_new_from_names(h, unsafe.Pointer(&n), 0) | |
| 127 | 131 | runtime.KeepAlive(b) | |
| 128 | 132 | runtime.KeepAlive(n) | |
| 129 | 133 | return v | |
| @@ -213,6 +217,19 @@ func (k *Keymap) ModIndex(name string) (uint32, error) { | |||
| 213 | 217 | runtime.KeepAlive(b) | |
| 214 | 218 | return v, nil | |
| 215 | 219 | } | |
| 220 | + | ||
| 221 | + | // KeyRepeats reports whether the key repeats when held. It does not allocate. | |
| 222 | + | func (k *Keymap) KeyRepeats(keycode uint32) (bool, error) { | |
| 223 | + | if k == nil { | |
| 224 | + | return false, ErrClosed | |
| 225 | + | } | |
| 226 | + | k.mu.Lock() | |
| 227 | + | defer k.mu.Unlock() | |
| 228 | + | if k.h == 0 { | |
| 229 | + | return false, ErrClosed | |
| 230 | + | } | |
| 231 | + | return loader.f.Xkb_keymap_key_repeats(k.h, keycode) > 0, nil | |
| 232 | + | } | |
| 216 | 233 | func (k *Keymap) LayoutCount() (uint32, error) { | |
| 217 | 234 | if k == nil { | |
| 218 | 235 | return 0, ErrClosed | |
| @@ -240,6 +257,7 @@ func (k *Keymap) NewState() (*State, error) { | |||
| 240 | 257 | type State struct { | |
| 241 | 258 | mu sync.Mutex | |
| 242 | 259 | h, keymap uintptr | |
| 260 | + | name [64]byte // NUL-terminated modifier-name scratch, guarded by mu | |
| 243 | 261 | } | |
| 244 | 262 | ||
| 245 | 263 | func (s *State) Close() error { | |
| @@ -284,6 +302,50 @@ func (s *State) Layout() (uint32, error) { | |||
| 284 | 302 | } | |
| 285 | 303 | return loader.f.Xkb_state_serialize_layout(s.h, raw.XKB_STATE_LAYOUT_EFFECTIVE), nil | |
| 286 | 304 | } | |
| 305 | + | ||
| 306 | + | // ModIndexActive reports whether the modifier at idx is effectively active. | |
| 307 | + | // It does not allocate. An unknown index reports false. | |
| 308 | + | func (s *State) ModIndexActive(idx uint32) (bool, error) { | |
| 309 | + | if s == nil { | |
| 310 | + | return false, ErrClosed | |
| 311 | + | } | |
| 312 | + | s.mu.Lock() | |
| 313 | + | defer s.mu.Unlock() | |
| 314 | + | if s.h == 0 { | |
| 315 | + | return false, ErrClosed | |
| 316 | + | } | |
| 317 | + | return loader.f.Xkb_state_mod_index_is_active(s.h, idx, raw.XKB_STATE_MODS_EFFECTIVE) > 0, nil | |
| 318 | + | } | |
| 319 | + | ||
| 320 | + | // ModNameActive reports whether the named modifier (for example | |
| 321 | + | // raw.XKB_MOD_NAME_CTRL) is effectively active. Names up to 63 bytes do not | |
| 322 | + | // allocate. An unknown name reports false. | |
| 323 | + | func (s *State) ModNameActive(name string) (bool, error) { | |
| 324 | + | if e := validString(name); e != nil { | |
| 325 | + | return false, e | |
| 326 | + | } | |
| 327 | + | if s == nil { | |
| 328 | + | return false, ErrClosed | |
| 329 | + | } | |
| 330 | + | s.mu.Lock() | |
| 331 | + | defer s.mu.Unlock() | |
| 332 | + | if s.h == 0 { | |
| 333 | + | return false, ErrClosed | |
| 334 | + | } | |
| 335 | + | var b []byte | |
| 336 | + | if len(name) < len(s.name) { | |
| 337 | + | // Heap-resident scratch owned by State and guarded by s.mu. | |
| 338 | + | b = s.name[:len(name)+1] | |
| 339 | + | copy(b, name) | |
| 340 | + | b[len(name)] = 0 | |
| 341 | + | } else { | |
| 342 | + | b = cstr(name) | |
| 343 | + | } | |
| 344 | + | v := loader.f.Xkb_state_mod_name_is_active(s.h, ptr(b), raw.XKB_STATE_MODS_EFFECTIVE) | |
| 345 | + | runtime.KeepAlive(b) | |
| 346 | + | return v > 0, nil | |
| 347 | + | } | |
| 348 | + | ||
| 287 | 349 | func (s *State) Mods() (uint32, error) { | |
| 288 | 350 | if s == nil { | |
| 289 | 351 | return 0, ErrClosed | |
| @@ -308,8 +370,8 @@ func (s *State) KeySym(keycode uint32) (uint32, error) { | |||
| 308 | 370 | } | |
| 309 | 371 | ||
| 310 | 372 | // sizedUTF8 probes the required byte count (excluding NUL) and retries if it grows. | |
| 311 | - | func sizedUTF8(call func(uintptr, uintptr) int32) (string, error) { | |
| 312 | - | n := call(0, 0) | |
| 373 | + | func sizedUTF8(call func(unsafe.Pointer, uintptr) int32) (string, error) { | |
| 374 | + | n := call(nil, 0) | |
| 313 | 375 | if n < 0 { | |
| 314 | 376 | return "", errors.New("xkbcommon: UTF-8 conversion failed") | |
| 315 | 377 | } | |
| @@ -343,16 +405,14 @@ const MaxUTF8Buffer = 512 | |||
| 343 | 405 | var ErrUTF8Buffer = fmt.Errorf("xkbcommon: UTF-8 buffer length must be 1..%d", MaxUTF8Buffer) | |
| 344 | 406 | ||
| 345 | 407 | // utf8Into never creates a string or keeps the caller's storage. Native writes | |
| 346 | - | // are synchronous; the backing array is pinned only for the duration of the call. | |
| 408 | + | // are synchronous; the Syscall uintptr conversion keeps dst's backing array | |
| 409 | + | // alive and unmoved for the duration of the call (go:uintptrescapes). | |
| 347 | 410 | // Short buffers are wiped rather than exposing truncated UTF-8 or credentials. | |
| 348 | 411 | // Callers clear dst before any early return. | |
| 349 | - | func utf8Into(dst []byte, call func(uintptr, uintptr) int32) (int, error) { | |
| 412 | + | func utf8Into(dst []byte, call func(unsafe.Pointer, uintptr) int32) (int, error) { | |
| 350 | 413 | if len(dst) < 1 || len(dst) > MaxUTF8Buffer { | |
| 351 | 414 | return 0, ErrUTF8Buffer | |
| 352 | 415 | } | |
| 353 | - | var pin runtime.Pinner | |
| 354 | - | pin.Pin(&dst[0]) | |
| 355 | - | defer pin.Unpin() | |
| 356 | 416 | n := call(ptr(dst), uintptr(len(dst))) | |
| 357 | 417 | runtime.KeepAlive(dst) | |
| 358 | 418 | if n < 0 { | |
| @@ -368,7 +428,8 @@ func utf8Into(dst []byte, call func(uintptr, uintptr) int32) (int, error) { | |||
| 368 | 428 | } | |
| 369 | 429 | ||
| 370 | 430 | // UTF8Into writes into caller-owned mutable storage without constructing a | |
| 371 | - | // string. n excludes NUL; on io.ErrShortBuffer n is the required payload size, | |
| 431 | + | // string. dst is passed to C through a uintptr-escaping call, so its backing | |
| 432 | + | // array lives on the heap (a stack array sliced into dst is moved there once). n excludes NUL; on io.ErrShortBuffer n is the required payload size, | |
| 372 | 433 | // all of dst is cleared, and the caller needs n+1 bytes. dst must have length | |
| 373 | 434 | // 1..MaxUTF8Buffer. No storage is retained; the caller owns wiping after use. | |
| 374 | 435 | func (s *State) UTF8Into(keycode uint32, dst []byte) (int, error) { | |
| @@ -381,7 +442,7 @@ func (s *State) UTF8Into(keycode uint32, dst []byte) (int, error) { | |||
| 381 | 442 | if s.h == 0 { | |
| 382 | 443 | return 0, ErrClosed | |
| 383 | 444 | } | |
| 384 | - | return utf8Into(dst, func(p, n uintptr) int32 { return loader.f.Xkb_state_key_get_utf8(s.h, keycode, p, n) }) | |
| 445 | + | return utf8Into(dst, func(p unsafe.Pointer, n uintptr) int32 { return loader.f.Xkb_state_key_get_utf8(s.h, keycode, p, n) }) | |
| 385 | 446 | } | |
| 386 | 447 | ||
| 387 | 448 | func (s *State) UTF8(keycode uint32) (string, error) { | |
| @@ -393,13 +454,23 @@ func (s *State) UTF8(keycode uint32) (string, error) { | |||
| 393 | 454 | if s.h == 0 { | |
| 394 | 455 | return "", ErrClosed | |
| 395 | 456 | } | |
| 396 | - | return sizedUTF8(func(p, n uintptr) int32 { return loader.f.Xkb_state_key_get_utf8(s.h, keycode, p, n) }) | |
| 457 | + | return sizedUTF8(func(p unsafe.Pointer, n uintptr) int32 { return loader.f.Xkb_state_key_get_utf8(s.h, keycode, p, n) }) | |
| 397 | 458 | } | |
| 398 | 459 | func KeysymName(sym uint32) (string, error) { | |
| 399 | 460 | if e := Available(); e != nil { | |
| 400 | 461 | return "", e | |
| 401 | 462 | } | |
| 402 | - | return sizedUTF8(func(p, n uintptr) int32 { return loader.f.Xkb_keysym_get_name(sym, p, n) }) | |
| 463 | + | return sizedUTF8(func(p unsafe.Pointer, n uintptr) int32 { return loader.f.Xkb_keysym_get_name(sym, p, n) }) | |
| 464 | + | } | |
| 465 | + | ||
| 466 | + | // KeysymNameInto writes the keysym name into dst without allocating, with the | |
| 467 | + | // same bounds and short-buffer semantics as State.UTF8Into. | |
| 468 | + | func KeysymNameInto(sym uint32, dst []byte) (int, error) { | |
| 469 | + | clear(dst) | |
| 470 | + | if e := Available(); e != nil { | |
| 471 | + | return 0, e | |
| 472 | + | } | |
| 473 | + | return utf8Into(dst, func(p unsafe.Pointer, n uintptr) int32 { return loader.f.Xkb_keysym_get_name(sym, p, n) }) | |
| 403 | 474 | } | |
| 404 | 475 | func KeysymFromName(name string) (uint32, error) { | |
| 405 | 476 | if e := Available(); e != nil { | |
| @@ -529,7 +600,7 @@ func (s *ComposeState) UTF8Into(dst []byte) (int, error) { | |||
| 529 | 600 | if s.h == 0 { | |
| 530 | 601 | return 0, ErrClosed | |
| 531 | 602 | } | |
| 532 | - | return utf8Into(dst, func(p, n uintptr) int32 { return loader.f.Xkb_compose_state_get_utf8(s.h, p, n) }) | |
| 603 | + | return utf8Into(dst, func(p unsafe.Pointer, n uintptr) int32 { return loader.f.Xkb_compose_state_get_utf8(s.h, p, n) }) | |
| 533 | 604 | } | |
| 534 | 605 | ||
| 535 | 606 | func (s *ComposeState) UTF8() (string, error) { | |
| @@ -541,7 +612,7 @@ func (s *ComposeState) UTF8() (string, error) { | |||
| 541 | 612 | if s.h == 0 { | |
| 542 | 613 | return "", ErrClosed | |
| 543 | 614 | } | |
| 544 | - | return sizedUTF8(func(p, n uintptr) int32 { return loader.f.Xkb_compose_state_get_utf8(s.h, p, n) }) | |
| 615 | + | return sizedUTF8(func(p unsafe.Pointer, n uintptr) int32 { return loader.f.Xkb_compose_state_get_utf8(s.h, p, n) }) | |
| 545 | 616 | } | |
| 546 | 617 | func (s *ComposeState) KeySym() (uint32, error) { | |
| 547 | 618 | if s == nil { | |
| @@ -18,11 +18,15 @@ import ( | |||||
| 18 | var ErrClosed = errors.New("xkbcommon: closed handle") | 18 | var ErrClosed = errors.New("xkbcommon: closed handle") | ||
| 19 | 19 | ||||
| 20 | func cstr(s string) []byte { return append([]byte(s), 0) } | 20 | func cstr(s string) []byte { return append([]byte(s), 0) } | ||
| 21 | - | func ptr(b []byte) uintptr { | 21 | + | |
| 22 | + | // ptr returns the address of b's first byte, or nil. The result is only valid | |||
| 23 | + | // as an argument to a raw.Funcs method, whose uintptr conversion keeps the | |||
| 24 | + | // backing array alive and (via //go:uintptrescapes) on the heap for the call. | |||
| 25 | + | func ptr(b []byte) unsafe.Pointer { | |||
| 22 | if len(b) == 0 { | 26 | if len(b) == 0 { | ||
| 23 | - | return 0 | 27 | + | return nil |
| 24 | } | 28 | } | ||
| 25 | - | return uintptr(unsafe.Pointer(&b[0])) | 29 | + | return unsafe.Pointer(&b[0]) |
| 26 | } | 30 | } | ||
| 27 | func validString(s string) error { | 31 | func validString(s string) error { | ||
| 28 | if strings.IndexByte(s, 0) >= 0 { | 32 | if strings.IndexByte(s, 0) >= 0 { | ||
| @@ -112,7 +116,7 @@ func (c *Context) NewKeymapRules(rules, model, layout, variant, options string) | |||||
| 112 | args := []string{rules, model, layout, variant, options} | 116 | args := []string{rules, model, layout, variant, options} | ||
| 113 | var b [5][]byte | 117 | var b [5][]byte | ||
| 114 | var n raw.RuleNames | 118 | var n raw.RuleNames | ||
| 115 | - | p := []*uintptr{&n.Rules, &n.Model, &n.Layout, &n.Variant, &n.Options} | 119 | + | p := []*unsafe.Pointer{&n.Rules, &n.Model, &n.Layout, &n.Variant, &n.Options} |
| 116 | for i, s := range args { | 120 | for i, s := range args { | ||
| 117 | if e := validString(s); e != nil { | 121 | if e := validString(s); e != nil { | ||
| 118 | return nil, e | 122 | return nil, e | ||
| @@ -123,7 +127,7 @@ func (c *Context) NewKeymapRules(rules, model, layout, variant, options string) | |||||
| 123 | } | 127 | } | ||
| 124 | } | 128 | } | ||
| 125 | return newKeymap(c, func(h uintptr) uintptr { | 129 | return newKeymap(c, func(h uintptr) uintptr { | ||
| 126 | - | v := loader.f.Xkb_keymap_new_from_names(h, uintptr(unsafe.Pointer(&n)), 0) | 130 | + | v := loader.f.Xkb_keymap_new_from_names(h, unsafe.Pointer(&n), 0) |
| 127 | runtime.KeepAlive(b) | 131 | runtime.KeepAlive(b) | ||
| 128 | runtime.KeepAlive(n) | 132 | runtime.KeepAlive(n) | ||
| 129 | return v | 133 | return v | ||
| @@ -213,6 +217,19 @@ func (k *Keymap) ModIndex(name string) (uint32, error) { | |||||
| 213 | runtime.KeepAlive(b) | 217 | runtime.KeepAlive(b) | ||
| 214 | return v, nil | 218 | return v, nil | ||
| 215 | } | 219 | } | ||
| 220 | + | ||||
| 221 | + | // KeyRepeats reports whether the key repeats when held. It does not allocate. | |||
| 222 | + | func (k *Keymap) KeyRepeats(keycode uint32) (bool, error) { | |||
| 223 | + | if k == nil { | |||
| 224 | + | return false, ErrClosed | |||
| 225 | + | } | |||
| 226 | + | k.mu.Lock() | |||
| 227 | + | defer k.mu.Unlock() | |||
| 228 | + | if k.h == 0 { | |||
| 229 | + | return false, ErrClosed | |||
| 230 | + | } | |||
| 231 | + | return loader.f.Xkb_keymap_key_repeats(k.h, keycode) > 0, nil | |||
| 232 | + | } | |||
| 216 | func (k *Keymap) LayoutCount() (uint32, error) { | 233 | func (k *Keymap) LayoutCount() (uint32, error) { | ||
| 217 | if k == nil { | 234 | if k == nil { | ||
| 218 | return 0, ErrClosed | 235 | return 0, ErrClosed | ||
| @@ -240,6 +257,7 @@ func (k *Keymap) NewState() (*State, error) { | |||||
| 240 | type State struct { | 257 | type State struct { | ||
| 241 | mu sync.Mutex | 258 | mu sync.Mutex | ||
| 242 | h, keymap uintptr | 259 | h, keymap uintptr | ||
| 260 | + | name [64]byte // NUL-terminated modifier-name scratch, guarded by mu | |||
| 243 | } | 261 | } | ||
| 244 | 262 | ||||
| 245 | func (s *State) Close() error { | 263 | func (s *State) Close() error { | ||
| @@ -284,6 +302,50 @@ func (s *State) Layout() (uint32, error) { | |||||
| 284 | } | 302 | } | ||
| 285 | return loader.f.Xkb_state_serialize_layout(s.h, raw.XKB_STATE_LAYOUT_EFFECTIVE), nil | 303 | return loader.f.Xkb_state_serialize_layout(s.h, raw.XKB_STATE_LAYOUT_EFFECTIVE), nil | ||
| 286 | } | 304 | } | ||
| 305 | + | ||||
| 306 | + | // ModIndexActive reports whether the modifier at idx is effectively active. | |||
| 307 | + | // It does not allocate. An unknown index reports false. | |||
| 308 | + | func (s *State) ModIndexActive(idx uint32) (bool, error) { | |||
| 309 | + | if s == nil { | |||
| 310 | + | return false, ErrClosed | |||
| 311 | + | } | |||
| 312 | + | s.mu.Lock() | |||
| 313 | + | defer s.mu.Unlock() | |||
| 314 | + | if s.h == 0 { | |||
| 315 | + | return false, ErrClosed | |||
| 316 | + | } | |||
| 317 | + | return loader.f.Xkb_state_mod_index_is_active(s.h, idx, raw.XKB_STATE_MODS_EFFECTIVE) > 0, nil | |||
| 318 | + | } | |||
| 319 | + | ||||
| 320 | + | // ModNameActive reports whether the named modifier (for example | |||
| 321 | + | // raw.XKB_MOD_NAME_CTRL) is effectively active. Names up to 63 bytes do not | |||
| 322 | + | // allocate. An unknown name reports false. | |||
| 323 | + | func (s *State) ModNameActive(name string) (bool, error) { | |||
| 324 | + | if e := validString(name); e != nil { | |||
| 325 | + | return false, e | |||
| 326 | + | } | |||
| 327 | + | if s == nil { | |||
| 328 | + | return false, ErrClosed | |||
| 329 | + | } | |||
| 330 | + | s.mu.Lock() | |||
| 331 | + | defer s.mu.Unlock() | |||
| 332 | + | if s.h == 0 { | |||
| 333 | + | return false, ErrClosed | |||
| 334 | + | } | |||
| 335 | + | var b []byte | |||
| 336 | + | if len(name) < len(s.name) { | |||
| 337 | + | // Heap-resident scratch owned by State and guarded by s.mu. | |||
| 338 | + | b = s.name[:len(name)+1] | |||
| 339 | + | copy(b, name) | |||
| 340 | + | b[len(name)] = 0 | |||
| 341 | + | } else { | |||
| 342 | + | b = cstr(name) | |||
| 343 | + | } | |||
| 344 | + | v := loader.f.Xkb_state_mod_name_is_active(s.h, ptr(b), raw.XKB_STATE_MODS_EFFECTIVE) | |||
| 345 | + | runtime.KeepAlive(b) | |||
| 346 | + | return v > 0, nil | |||
| 347 | + | } | |||
| 348 | + | ||||
| 287 | func (s *State) Mods() (uint32, error) { | 349 | func (s *State) Mods() (uint32, error) { | ||
| 288 | if s == nil { | 350 | if s == nil { | ||
| 289 | return 0, ErrClosed | 351 | return 0, ErrClosed | ||
| @@ -308,8 +370,8 @@ func (s *State) KeySym(keycode uint32) (uint32, error) { | |||||
| 308 | } | 370 | } | ||
| 309 | 371 | ||||
| 310 | // sizedUTF8 probes the required byte count (excluding NUL) and retries if it grows. | 372 | // sizedUTF8 probes the required byte count (excluding NUL) and retries if it grows. | ||
| 311 | - | func sizedUTF8(call func(uintptr, uintptr) int32) (string, error) { | 373 | + | func sizedUTF8(call func(unsafe.Pointer, uintptr) int32) (string, error) { |
| 312 | - | n := call(0, 0) | 374 | + | n := call(nil, 0) |
| 313 | if n < 0 { | 375 | if n < 0 { | ||
| 314 | return "", errors.New("xkbcommon: UTF-8 conversion failed") | 376 | return "", errors.New("xkbcommon: UTF-8 conversion failed") | ||
| 315 | } | 377 | } | ||
| @@ -343,16 +405,14 @@ const MaxUTF8Buffer = 512 | |||||
| 343 | var ErrUTF8Buffer = fmt.Errorf("xkbcommon: UTF-8 buffer length must be 1..%d", MaxUTF8Buffer) | 405 | var ErrUTF8Buffer = fmt.Errorf("xkbcommon: UTF-8 buffer length must be 1..%d", MaxUTF8Buffer) | ||
| 344 | 406 | ||||
| 345 | // utf8Into never creates a string or keeps the caller's storage. Native writes | 407 | // utf8Into never creates a string or keeps the caller's storage. Native writes | ||
| 346 | - | // are synchronous; the backing array is pinned only for the duration of the call. | 408 | + | // are synchronous; the Syscall uintptr conversion keeps dst's backing array |
| 409 | + | // alive and unmoved for the duration of the call (go:uintptrescapes). | |||
| 347 | // Short buffers are wiped rather than exposing truncated UTF-8 or credentials. | 410 | // Short buffers are wiped rather than exposing truncated UTF-8 or credentials. | ||
| 348 | // Callers clear dst before any early return. | 411 | // Callers clear dst before any early return. | ||
| 349 | - | func utf8Into(dst []byte, call func(uintptr, uintptr) int32) (int, error) { | 412 | + | func utf8Into(dst []byte, call func(unsafe.Pointer, uintptr) int32) (int, error) { |
| 350 | if len(dst) < 1 || len(dst) > MaxUTF8Buffer { | 413 | if len(dst) < 1 || len(dst) > MaxUTF8Buffer { | ||
| 351 | return 0, ErrUTF8Buffer | 414 | return 0, ErrUTF8Buffer | ||
| 352 | } | 415 | } | ||
| 353 | - | var pin runtime.Pinner | |||
| 354 | - | pin.Pin(&dst[0]) | |||
| 355 | - | defer pin.Unpin() | |||
| 356 | n := call(ptr(dst), uintptr(len(dst))) | 416 | n := call(ptr(dst), uintptr(len(dst))) | ||
| 357 | runtime.KeepAlive(dst) | 417 | runtime.KeepAlive(dst) | ||
| 358 | if n < 0 { | 418 | if n < 0 { | ||
| @@ -368,7 +428,8 @@ func utf8Into(dst []byte, call func(uintptr, uintptr) int32) (int, error) { | |||||
| 368 | } | 428 | } | ||
| 369 | 429 | ||||
| 370 | // UTF8Into writes into caller-owned mutable storage without constructing a | 430 | // UTF8Into writes into caller-owned mutable storage without constructing a | ||
| 371 | - | // string. n excludes NUL; on io.ErrShortBuffer n is the required payload size, | 431 | + | // string. dst is passed to C through a uintptr-escaping call, so its backing |
| 432 | + | // array lives on the heap (a stack array sliced into dst is moved there once). n excludes NUL; on io.ErrShortBuffer n is the required payload size, | |||
| 372 | // all of dst is cleared, and the caller needs n+1 bytes. dst must have length | 433 | // all of dst is cleared, and the caller needs n+1 bytes. dst must have length | ||
| 373 | // 1..MaxUTF8Buffer. No storage is retained; the caller owns wiping after use. | 434 | // 1..MaxUTF8Buffer. No storage is retained; the caller owns wiping after use. | ||
| 374 | func (s *State) UTF8Into(keycode uint32, dst []byte) (int, error) { | 435 | func (s *State) UTF8Into(keycode uint32, dst []byte) (int, error) { | ||
| @@ -381,7 +442,7 @@ func (s *State) UTF8Into(keycode uint32, dst []byte) (int, error) { | |||||
| 381 | if s.h == 0 { | 442 | if s.h == 0 { | ||
| 382 | return 0, ErrClosed | 443 | return 0, ErrClosed | ||
| 383 | } | 444 | } | ||
| 384 | - | return utf8Into(dst, func(p, n uintptr) int32 { return loader.f.Xkb_state_key_get_utf8(s.h, keycode, p, n) }) | 445 | + | return utf8Into(dst, func(p unsafe.Pointer, n uintptr) int32 { return loader.f.Xkb_state_key_get_utf8(s.h, keycode, p, n) }) |
| 385 | } | 446 | } | ||
| 386 | 447 | ||||
| 387 | func (s *State) UTF8(keycode uint32) (string, error) { | 448 | func (s *State) UTF8(keycode uint32) (string, error) { | ||
| @@ -393,13 +454,23 @@ func (s *State) UTF8(keycode uint32) (string, error) { | |||||
| 393 | if s.h == 0 { | 454 | if s.h == 0 { | ||
| 394 | return "", ErrClosed | 455 | return "", ErrClosed | ||
| 395 | } | 456 | } | ||
| 396 | - | return sizedUTF8(func(p, n uintptr) int32 { return loader.f.Xkb_state_key_get_utf8(s.h, keycode, p, n) }) | 457 | + | return sizedUTF8(func(p unsafe.Pointer, n uintptr) int32 { return loader.f.Xkb_state_key_get_utf8(s.h, keycode, p, n) }) |
| 397 | } | 458 | } | ||
| 398 | func KeysymName(sym uint32) (string, error) { | 459 | func KeysymName(sym uint32) (string, error) { | ||
| 399 | if e := Available(); e != nil { | 460 | if e := Available(); e != nil { | ||
| 400 | return "", e | 461 | return "", e | ||
| 401 | } | 462 | } | ||
| 402 | - | return sizedUTF8(func(p, n uintptr) int32 { return loader.f.Xkb_keysym_get_name(sym, p, n) }) | 463 | + | return sizedUTF8(func(p unsafe.Pointer, n uintptr) int32 { return loader.f.Xkb_keysym_get_name(sym, p, n) }) |
| 464 | + | } | |||
| 465 | + | ||||
| 466 | + | // KeysymNameInto writes the keysym name into dst without allocating, with the | |||
| 467 | + | // same bounds and short-buffer semantics as State.UTF8Into. | |||
| 468 | + | func KeysymNameInto(sym uint32, dst []byte) (int, error) { | |||
| 469 | + | clear(dst) | |||
| 470 | + | if e := Available(); e != nil { | |||
| 471 | + | return 0, e | |||
| 472 | + | } | |||
| 473 | + | return utf8Into(dst, func(p unsafe.Pointer, n uintptr) int32 { return loader.f.Xkb_keysym_get_name(sym, p, n) }) | |||
| 403 | } | 474 | } | ||
| 404 | func KeysymFromName(name string) (uint32, error) { | 475 | func KeysymFromName(name string) (uint32, error) { | ||
| 405 | if e := Available(); e != nil { | 476 | if e := Available(); e != nil { | ||
| @@ -529,7 +600,7 @@ func (s *ComposeState) UTF8Into(dst []byte) (int, error) { | |||||
| 529 | if s.h == 0 { | 600 | if s.h == 0 { | ||
| 530 | return 0, ErrClosed | 601 | return 0, ErrClosed | ||
| 531 | } | 602 | } | ||
| 532 | - | return utf8Into(dst, func(p, n uintptr) int32 { return loader.f.Xkb_compose_state_get_utf8(s.h, p, n) }) | 603 | + | return utf8Into(dst, func(p unsafe.Pointer, n uintptr) int32 { return loader.f.Xkb_compose_state_get_utf8(s.h, p, n) }) |
| 533 | } | 604 | } | ||
| 534 | 605 | ||||
| 535 | func (s *ComposeState) UTF8() (string, error) { | 606 | func (s *ComposeState) UTF8() (string, error) { | ||
| @@ -541,7 +612,7 @@ func (s *ComposeState) UTF8() (string, error) { | |||||
| 541 | if s.h == 0 { | 612 | if s.h == 0 { | ||
| 542 | return "", ErrClosed | 613 | return "", ErrClosed | ||
| 543 | } | 614 | } | ||
| 544 | - | return sizedUTF8(func(p, n uintptr) int32 { return loader.f.Xkb_compose_state_get_utf8(s.h, p, n) }) | 615 | + | return sizedUTF8(func(p unsafe.Pointer, n uintptr) int32 { return loader.f.Xkb_compose_state_get_utf8(s.h, p, n) }) |
| 545 | } | 616 | } | ||
| 546 | func (s *ComposeState) KeySym() (uint32, error) { | 617 | func (s *ComposeState) KeySym() (uint32, error) { | ||
| 547 | if s == nil { | 618 | if s == nil { | ||
M xkbcommon_test.go +3 -3
| @@ -198,7 +198,7 @@ func TestLayoutsAndDifferential(t *testing.T) { | |||
| 198 | 198 | if nativeSym != sym { | |
| 199 | 199 | t.Fatalf("native sym %d != %d", nativeSym, sym) | |
| 200 | 200 | } | |
| 201 | - | nativeN := loader.f.Xkb_state_key_get_utf8(s.h, tc.code, 0, 0) | |
| 201 | + | nativeN := loader.f.Xkb_state_key_get_utf8(s.h, tc.code, nil, 0) | |
| 202 | 202 | if nativeN != int32(len(got)) { | |
| 203 | 203 | t.Fatalf("native bytes %d != %d", nativeN, len(got)) | |
| 204 | 204 | } | |
| @@ -387,11 +387,11 @@ func TestCompose(t *testing.T) { | |||
| 387 | 387 | } | |
| 388 | 388 | } | |
| 389 | 389 | func TestSizing(t *testing.T) { | |
| 390 | - | v, e := sizedUTF8(func(p, n uintptr) int32 { return 0 }) | |
| 390 | + | v, e := sizedUTF8(func(p unsafe.Pointer, n uintptr) int32 { return 0 }) | |
| 391 | 391 | if e != nil || v != "" { | |
| 392 | 392 | t.Fatalf("%q %v", v, e) | |
| 393 | 393 | } | |
| 394 | - | _, e = sizedUTF8(func(p, n uintptr) int32 { return -1 }) | |
| 394 | + | _, e = sizedUTF8(func(p unsafe.Pointer, n uintptr) int32 { return -1 }) | |
| 395 | 395 | if e == nil { | |
| 396 | 396 | t.Fatal("negative result accepted") | |
| 397 | 397 | } | |
| @@ -198,7 +198,7 @@ func TestLayoutsAndDifferential(t *testing.T) { | |||||
| 198 | if nativeSym != sym { | 198 | if nativeSym != sym { | ||
| 199 | t.Fatalf("native sym %d != %d", nativeSym, sym) | 199 | t.Fatalf("native sym %d != %d", nativeSym, sym) | ||
| 200 | } | 200 | } | ||
| 201 | - | nativeN := loader.f.Xkb_state_key_get_utf8(s.h, tc.code, 0, 0) | 201 | + | nativeN := loader.f.Xkb_state_key_get_utf8(s.h, tc.code, nil, 0) |
| 202 | if nativeN != int32(len(got)) { | 202 | if nativeN != int32(len(got)) { | ||
| 203 | t.Fatalf("native bytes %d != %d", nativeN, len(got)) | 203 | t.Fatalf("native bytes %d != %d", nativeN, len(got)) | ||
| 204 | } | 204 | } | ||
| @@ -387,11 +387,11 @@ func TestCompose(t *testing.T) { | |||||
| 387 | } | 387 | } | ||
| 388 | } | 388 | } | ||
| 389 | func TestSizing(t *testing.T) { | 389 | func TestSizing(t *testing.T) { | ||
| 390 | - | v, e := sizedUTF8(func(p, n uintptr) int32 { return 0 }) | 390 | + | v, e := sizedUTF8(func(p unsafe.Pointer, n uintptr) int32 { return 0 }) |
| 391 | if e != nil || v != "" { | 391 | if e != nil || v != "" { | ||
| 392 | t.Fatalf("%q %v", v, e) | 392 | t.Fatalf("%q %v", v, e) | ||
| 393 | } | 393 | } | ||
| 394 | - | _, e = sizedUTF8(func(p, n uintptr) int32 { return -1 }) | 394 | + | _, e = sizedUTF8(func(p unsafe.Pointer, n uintptr) int32 { return -1 }) |
| 395 | if e == nil { | 395 | if e == nil { | ||
| 396 | t.Fatal("negative result accepted") | 396 | t.Fatal("negative result accepted") | ||
| 397 | } | 397 | } | ||