github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/app/internal/xkb/xkb_unix.go (about) 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/cybriq/giocore/io/event" 18 "github.com/cybriq/giocore/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 }