xkb_unix.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 // +build linux,!android freebsd openbsd
4
5 // Package xkb implements a Go interface for the X Keyboard Extension library.
6 package xkb
7
8 import (
9 "errors"
10 "fmt"
11 "os"
12 "syscall"
13 "unicode"
14 "unicode/utf8"
15 "unsafe"
16
17 "github.com/p9c/p9/pkg/gel/gio/io/event"
18 "github.com/p9c/p9/pkg/gel/gio/io/key"
19 )
20
21 /*
22 #cgo linux pkg-config: xkbcommon
23 #cgo freebsd openbsd CFLAGS: -I/usr/local/include
24 #cgo freebsd openbsd LDFLAGS: -L/usr/local/lib -lxkbcommon
25
26 #include <stdlib.h>
27 #include <xkbcommon/xkbcommon.h>
28 #include <xkbcommon/xkbcommon-compose.h>
29 */
30 import "C"
31
32 type Context struct {
33 Ctx *C.struct_xkb_context
34 keyMap *C.struct_xkb_keymap
35 state *C.struct_xkb_state
36 compTable *C.struct_xkb_compose_table
37 compState *C.struct_xkb_compose_state
38 utf8Buf []byte
39 }
40
41 var (
42 _XKB_MOD_NAME_CTRL = []byte("Control\x00")
43 _XKB_MOD_NAME_SHIFT = []byte("Shift\x00")
44 _XKB_MOD_NAME_ALT = []byte("Mod1\x00")
45 _XKB_MOD_NAME_LOGO = []byte("Mod4\x00")
46 )
47
48 func (x *Context) Destroy() {
49 if x.compState != nil {
50 C.xkb_compose_state_unref(x.compState)
51 x.compState = nil
52 }
53 if x.compTable != nil {
54 C.xkb_compose_table_unref(x.compTable)
55 x.compTable = nil
56 }
57 x.DestroyKeymapState()
58 if x.Ctx != nil {
59 C.xkb_context_unref(x.Ctx)
60 x.Ctx = nil
61 }
62 }
63
64 func New() (*Context, error) {
65 ctx := &Context{
66 Ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS),
67 }
68 if ctx.Ctx == nil {
69 return nil, errors.New("newXKB: xkb_context_new failed")
70 }
71 locale := os.Getenv("LC_ALL")
72 if locale == "" {
73 locale = os.Getenv("LC_CTYPE")
74 }
75 if locale == "" {
76 locale = os.Getenv("LANG")
77 }
78 if locale == "" {
79 locale = "C"
80 }
81 cloc := C.CString(locale)
82 defer C.free(unsafe.Pointer(cloc))
83 ctx.compTable = C.xkb_compose_table_new_from_locale(ctx.Ctx, cloc, C.XKB_COMPOSE_COMPILE_NO_FLAGS)
84 if ctx.compTable == nil {
85 ctx.Destroy()
86 return nil, errors.New("newXKB: xkb_compose_table_new_from_locale failed")
87 }
88 ctx.compState = C.xkb_compose_state_new(ctx.compTable, C.XKB_COMPOSE_STATE_NO_FLAGS)
89 if ctx.compState == nil {
90 ctx.Destroy()
91 return nil, errors.New("newXKB: xkb_compose_state_new failed")
92 }
93 return ctx, nil
94 }
95
96 func (x *Context) DestroyKeymapState() {
97 if x.state != nil {
98 C.xkb_state_unref(x.state)
99 x.state = nil
100 }
101 if x.keyMap != nil {
102 C.xkb_keymap_unref(x.keyMap)
103 x.keyMap = nil
104 }
105 }
106
107 // SetKeymap sets the keymap and state. The context takes ownership of the
108 // keymap and state and frees them in Destroy.
109 func (x *Context) SetKeymap(xkbKeyMap, xkbState unsafe.Pointer) {
110 x.DestroyKeymapState()
111 x.keyMap = (*C.struct_xkb_keymap)(xkbKeyMap)
112 x.state = (*C.struct_xkb_state)(xkbState)
113 }
114
115 func (x *Context) LoadKeymap(format int, fd int, size int) error {
116 x.DestroyKeymapState()
117 mapData, err := syscall.Mmap(int(fd), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
118 if err != nil {
119 return fmt.Errorf("newXKB: mmap of keymap failed: %v", err)
120 }
121 defer syscall.Munmap(mapData)
122 keyMap := C.xkb_keymap_new_from_buffer(x.Ctx, (*C.char)(unsafe.Pointer(&mapData[0])), C.size_t(size-1), C.XKB_KEYMAP_FORMAT_TEXT_V1, C.XKB_KEYMAP_COMPILE_NO_FLAGS)
123 if keyMap == nil {
124 return errors.New("newXKB: xkb_keymap_new_from_buffer failed")
125 }
126 state := C.xkb_state_new(keyMap)
127 if state == nil {
128 C.xkb_keymap_unref(keyMap)
129 return errors.New("newXKB: xkb_state_new failed")
130 }
131 x.keyMap = keyMap
132 x.state = state
133 return nil
134 }
135
136 func (x *Context) Modifiers() key.Modifiers {
137 var mods key.Modifiers
138 if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_CTRL[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
139 mods |= key.ModCtrl
140 }
141 if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_SHIFT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
142 mods |= key.ModShift
143 }
144 if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_ALT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
145 mods |= key.ModAlt
146 }
147 if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_LOGO[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
148 mods |= key.ModSuper
149 }
150 return mods
151 }
152
153 func (x *Context) DispatchKey(keyCode uint32, state key.State) (events []event.Event) {
154 if x.state == nil {
155 return
156 }
157 kc := C.xkb_keycode_t(keyCode)
158 if len(x.utf8Buf) == 0 {
159 x.utf8Buf = make([]byte, 1)
160 }
161 sym := C.xkb_state_key_get_one_sym(x.state, kc)
162 if name, ok := convertKeysym(sym); ok {
163 cmd := key.Event{
164 Name: name,
165 Modifiers: x.Modifiers(),
166 State: state,
167 }
168 // Ensure that a physical backtab key is translated to
169 // Shift-Tab.
170 if sym == C.XKB_KEY_ISO_Left_Tab {
171 cmd.Modifiers |= key.ModShift
172 }
173 events = append(events, cmd)
174 }
175 C.xkb_compose_state_feed(x.compState, sym)
176 var str []byte
177 switch C.xkb_compose_state_get_status(x.compState) {
178 case C.XKB_COMPOSE_CANCELLED, C.XKB_COMPOSE_COMPOSING:
179 return
180 case C.XKB_COMPOSE_COMPOSED:
181 size := C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
182 if int(size) >= len(x.utf8Buf) {
183 x.utf8Buf = make([]byte, size+1)
184 size = C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
185 }
186 C.xkb_compose_state_reset(x.compState)
187 str = x.utf8Buf[:size]
188 case C.XKB_COMPOSE_NOTHING:
189 mod := x.Modifiers()
190 if mod&(key.ModCtrl|key.ModAlt|key.ModSuper) == 0 {
191 str = x.charsForKeycode(kc)
192 }
193 }
194 // Report only printable runes.
195 var n int
196 for n < len(str) {
197 r, s := utf8.DecodeRune(str)
198 if unicode.IsPrint(r) {
199 n += s
200 } else {
201 copy(str[n:], str[n+s:])
202 str = str[:len(str)-s]
203 }
204 }
205 if state == key.Press && len(str) > 0 {
206 events = append(events, key.EditEvent{Text: string(str)})
207 }
208 return
209 }
210
211 func (x *Context) charsForKeycode(keyCode C.xkb_keycode_t) []byte {
212 size := C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
213 if int(size) >= len(x.utf8Buf) {
214 x.utf8Buf = make([]byte, size+1)
215 size = C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
216 }
217 return x.utf8Buf[:size]
218 }
219
220 func (x *Context) IsRepeatKey(keyCode uint32) bool {
221 kc := C.xkb_keycode_t(keyCode)
222 return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1
223 }
224
225 func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latchedGroup, lockedGroup uint32) {
226 if x.state == nil {
227 return
228 }
229 C.xkb_state_update_mask(x.state, C.xkb_mod_mask_t(depressed), C.xkb_mod_mask_t(latched), C.xkb_mod_mask_t(locked),
230 C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
231 }
232
233 func convertKeysym(s C.xkb_keysym_t) (string, bool) {
234 if 'a' <= s && s <= 'z' {
235 return string(rune(s - 'a' + 'A')), true
236 }
237 if ' ' < s && s <= '~' {
238 return string(rune(s)), true
239 }
240 var n string
241 switch s {
242 case C.XKB_KEY_Escape:
243 n = key.NameEscape
244 case C.XKB_KEY_Left:
245 n = key.NameLeftArrow
246 case C.XKB_KEY_Right:
247 n = key.NameRightArrow
248 case C.XKB_KEY_Return:
249 n = key.NameReturn
250 case C.XKB_KEY_KP_Enter:
251 n = key.NameEnter
252 case C.XKB_KEY_Up:
253 n = key.NameUpArrow
254 case C.XKB_KEY_Down:
255 n = key.NameDownArrow
256 case C.XKB_KEY_Home:
257 n = key.NameHome
258 case C.XKB_KEY_End:
259 n = key.NameEnd
260 case C.XKB_KEY_BackSpace:
261 n = key.NameDeleteBackward
262 case C.XKB_KEY_Delete:
263 n = key.NameDeleteForward
264 case C.XKB_KEY_Page_Up:
265 n = key.NamePageUp
266 case C.XKB_KEY_Page_Down:
267 n = key.NamePageDown
268 case C.XKB_KEY_F1:
269 n = "F1"
270 case C.XKB_KEY_F2:
271 n = "F2"
272 case C.XKB_KEY_F3:
273 n = "F3"
274 case C.XKB_KEY_F4:
275 n = "F4"
276 case C.XKB_KEY_F5:
277 n = "F5"
278 case C.XKB_KEY_F6:
279 n = "F6"
280 case C.XKB_KEY_F7:
281 n = "F7"
282 case C.XKB_KEY_F8:
283 n = "F8"
284 case C.XKB_KEY_F9:
285 n = "F9"
286 case C.XKB_KEY_F10:
287 n = "F10"
288 case C.XKB_KEY_F11:
289 n = "F11"
290 case C.XKB_KEY_F12:
291 n = "F12"
292 case C.XKB_KEY_Tab, C.XKB_KEY_KP_Tab, C.XKB_KEY_ISO_Left_Tab:
293 n = key.NameTab
294 case 0x20, C.XKB_KEY_KP_Space:
295 n = key.NameSpace
296 default:
297 return "", false
298 }
299 return n, true
300 }
301